perf,x86: Fix shared register mutual exclusion enforcement
Subsystems (2)
PATCH v2 1/1 perf,x86: Fix shared register mutual exclusion enforcement
This patch fixes a problem with the shared registers mutual exclusion code and incremental event scheduling by the generic perf_event code.
There was a bug whereby the mutual exclusion on the shared registers was not enforced because of incremental scheduling abort due to event constraints. As an example on Intel Nehalem, consider the following events:
group1= L1D_CACHE_LD:E_STATE,OFFCORE_RESPONSE_0:PF_RFO,L1D_CACHE_LD:I_STATE group2= L1D_CACHE_LD:I_STATE
The L1D_CACHE_LD event can only be measured by 2 counters. Yet, there are 3 instances here. The first group can be scheduled and is committed. Then, the generic code tries to schedule group2 and this fails (because there is no more counter to support the 3rd instance of L1D_CACHE_LD). But in x86_schedule_events() error path, put_event_contraints() is invoked on ALL the events and not just the ones that just failed. That causes the "lock" on the shared offcore_response MSR to be released. Yet the first group is actually scheduled and is exposed to reprogramming of that shared msr by the sibling HT thread. In other words, there is no guarantee on what is measured.
This patch fixes the problem by tagging committed events with the PERF_X86_EVENT_COMMITTED tag. In the error path of x86_schedule_events(), only the events NOT tagged have their constraint released. The tag is eventually removed when the event in descheduled.
--- arch/x86/kernel/cpu/perf_event.c | 26 +++++++++++++++++++++++++- arch/x86/kernel/cpu/perf_event.h | 3 ++- arch/x86/kernel/cpu/perf_event_intel.c | 2 -- 3 files changed, 27 insertions(+), 4 deletions(-)
arch/x86/kernel/cpu/perf_event.c +25 -1
@@ -726,6 +726,7 @@ int x86_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign) {struct event_constraint *c;unsigned long used_mask[BITS_TO_LONGS (X86_PMC_IDX_MAX )]; +struct perf_event *e;int i, wmin, wmax, num =0 ;struct hw_perf_event *hwc;
@@ -770,13 +771,31 @@ int x86_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign) wmax, assign);/* +* Mark the event as committed, so we do not put_constraint() +* in case new events are added and fail scheduling. +*/ +if (!num && assign) { +for (i =0 ; i < n; i++) { + e = cpuc->event_list[i]; + e->hw.flags |=PERF_X86_EVENT_COMMITTED ; + } + } +/* * scheduling failed or is just a simulation, * free resources if necessary */ if (!assign || num) {for (i =0 ; i < n; i++) { + e = cpuc->event_list[i]; +/* +* do not put_constraint() on comitted events, +* because they are good to go +*/ +if ((e->hw.flags &PERF_X86_EVENT_COMMITTED )) +continue ; +if (x86_pmu.put_event_constraints) - x86_pmu.put_event_constraints (cpuc, cpuc->event_list[i]); + x86_pmu.put_event_constraints (cpuc, e); } }return num ? -EINVAL :0 ;
@@ -1156,6 +1175,11 @@ static void x86_pmu_del(struct perf_event *event, int flags)int i;/* +* event is descheduled +*/ + event->hw.flags &= ~PERF_X86_EVENT_COMMITTED ; + +/* * If we're called during a txn, we don't need to do anything. * The events never got scheduled and ->cancel_txn will truncate * the event_list.
arch/x86/kernel/cpu/perf_event.h +2 -1
@@ -63,11 +63,12 @@ struct event_constraint {int flags; };/* -* struct event_constraint flags +* struct hw_perf_event.flags flags */ #define PERF_X86_EVENT_PEBS_LDLAT 0x1/* ld+ldlat data address sampling */ #define PERF_X86_EVENT_PEBS_ST 0x2/* st data address sampling */ #define PERF_X86_EVENT_PEBS_ST_HSW 0x4/* haswell style st data sampling */ +#define PERF_X86_EVENT_COMMITTED 0x8/* event passed commit_txn */ struct amd_nb {int nb_id;/* NorthBridge id */
arch/x86/kernel/cpu/perf_event_intel.c +0 -2
@@ -1450,7 +1450,6 @@ x86_get_event_constraints(struct cpu_hw_events *cpuc, struct perf_event *event)if (x86_pmu.event_constraints) {for_each_event_constraint (c, x86_pmu.event_constraints) {if ((event->hw.config & c->cmask) == c->code) { -/* hw.flags zeroed at initialization */ event->hw.flags |= c->flags;return c; }
@@ -1498,7 +1497,6 @@ intel_put_shared_regs_event_constraints(struct cpu_hw_events *cpuc,static void intel_put_event_constraints (struct cpu_hw_events *cpuc,struct perf_event *event) { - event->hw.flags =0 ;intel_put_shared_regs_event_constraints (cpuc, event); }
Signature · 1 line
1.7.10.4
Peter Zijlstra
maintainerQuoted 24 lines · Stephane Eranian
This patch fixes a problem with the shared registers mutual exclusion code and incremental event scheduling by the generic
perf_eventcode.There was a bug whereby the mutual exclusion on the shared registers was not enforced because of incremental scheduling abort due to event constraints. As an example on Intel Nehalem, consider the following events:
group1= L1D_CACHE_LD:E_STATE,OFFCORE_RESPONSE_0:PF_RFO,L1D_CACHE_LD:I_STATE group2= L1D_CACHE_LD:I_STATE
The
L1D_CACHE_LDevent can only be measured by 2 counters. Yet, there are 3 instances here. The first group can be scheduled and is committed. Then, the generic code tries to schedule group2 and this fails (because there is no more counter to support the 3rd instance ofL1D_CACHE_LD). But inx86_schedule_events()error path,put_event_contraints()is invoked on ALL the events and not just the ones that just failed. That causes the "lock" on the sharedoffcore_responseMSR to be released. Yet the first group is actually scheduled and is exposed to reprogramming of that shared msr by the sibling HT thread. In other words, there is no guarantee on what is measured.
Quoted 6 lines
This patch fixes the problem by tagging committed events with the
PERF_X86_EVENT_COMMITTEDtag. In the error path ofx86_schedule_events(), only the events NOT tagged have their constraint released. The tag is eventually removed when the event in descheduled.Signed-off-by Stephane Eranian <[email protected]>
OK, so I 'accidentally' read the patch again; and noticed something.
In your case above; the get/put constraints are still fully matched. That is; the first group, which was successful, will not have done a put yet. So a subsequent get+put should still leave us with a positive 'ref' count and not undo things.
Only once these events pass through x86_pmu_del() will they get a final put and the 'ref' count will drop to 0.
Now the problem seems to be the get/put things don't actually count properly.
However, if we look at __intel_shared_reg_{get,put}_constraints() there is a refcount in there; namely era->ref; however we don't appear to clear reg->alloc based on it.
Should we?
Quoted 30 lines · Peter Zijlstra
Quoted 29 lines · Stephane Eranian
This patch fixes a problem with the shared registers mutual exclusion code and incremental event scheduling by the generic
perf_eventcode.There was a bug whereby the mutual exclusion on the shared registers was not enforced because of incremental scheduling abort due to event constraints. As an example on Intel Nehalem, consider the following events:
group1= L1D_CACHE_LD:E_STATE,OFFCORE_RESPONSE_0:PF_RFO,L1D_CACHE_LD:I_STATE group2= L1D_CACHE_LD:I_STATE
The
L1D_CACHE_LDevent can only be measured by 2 counters. Yet, there are 3 instances here. The first group can be scheduled and is committed. Then, the generic code tries to schedule group2 and this fails (because there is no more counter to support the 3rd instance ofL1D_CACHE_LD). But inx86_schedule_events()error path,put_event_contraints()is invoked on ALL the events and not just the ones that just failed. That causes the "lock" on the sharedoffcore_responseMSR to be released. Yet the first group is actually scheduled and is exposed to reprogramming of that shared msr by the sibling HT thread. In other words, there is no guarantee on what is measured.This patch fixes the problem by tagging committed events with the
PERF_X86_EVENT_COMMITTEDtag. In the error path ofx86_schedule_events(), only the events NOT tagged have their constraint released. The tag is eventually removed when the event in descheduled.
Quoted 9 lines
Quoted 1 line
Signed-off-by Stephane Eranian <[email protected]>OK, so I 'accidentally' read the patch again; and noticed something.
In your case above; the get/put constraints are still fully matched. That is; the first group, which was successful, will not have done a put yet. So a subsequent get+put should still leave us with a positive 'ref' count and not undo things.
You are missing the error path in schedule_events():
if (!assign || num) {
for (i = 0; i < n; i++) {
if (x86_pmu.put_event_constraints)
x86_pmu.put_event_constraints(cpuc,
cpuc->event_list[i]);
}
}That one wipes out on get() even on events that were correctly schedule in the previous
invocation. So here group2 fails, but it should not release the constraints from group1.
Quoted 10 lines
Only once these events pass through
x86_pmu_del()will they get a final put and the 'ref' count will drop to 0.Now the problem seems to be the get/put things don't actually count properly.
However, if we look at __intel_shared_reg_{get,put}_constraints() there is a refcount in there; namely
era->ref; however we don't appear to clearreg->allocbased on it.
The era->ref is not used to ref count the number of successful attempts at scheduling. It is used to count the number of CPU sharing the resource. So it goes from 0, 1, to 2. You can invoke schedule_events() many more times. The reg->alloc is a bypass, to avoid checking the shared reg again and again if it succeeded once.
For a while I thought I could leverage the era->ref to account the get/put. But it does not work. Because the of the put().
Quoted 1 line
Should we?
Peter Zijlstra
maintainerQuoted 12 lines · Stephane Eranian
You are missing the error path in
schedule_events():if (!assign || num) {
for (i = 0; i < n; i++) { if (x86_pmu.put_event_constraints) x86_pmu.put_event_constraints(cpuc, cpuc->event_list[i]); } }
Quoted 4 lines
That one wipes out on
get()even on events that were correctly schedule in the previous
invocation. So here group2 fails, but it should not release the constraints from group1.
What I was saying:
schedule(group1)
get_event_constraints() +1
no error path, no puts
schedule(group2)
get_event_constraints() +1
*fail*
put_event_constraints() -1
This leaves the constraints of group1 with a net +1 'ref' count and thus if we were to treat the get/put as such, the put wouldn't be the last and thus shouldn't release resources.
Quoted 10 lines
Quoted 10 lines
Only once these events pass through
x86_pmu_del()will they get a final put and the 'ref' count will drop to 0.Now the problem seems to be the get/put things don't actually count properly.
However, if we look at __intel_shared_reg_{get,put}_constraints() there is a refcount in there; namely
era->ref; however we don't appear to clearreg->allocbased on it.
Quoted 5 lines
The
era->refis not used to ref count the number of successful attempts at scheduling. It is used to count the number of CPU sharing the resource. So it goes from 0, 1, to 2. You can invokeschedule_events()many more times. Thereg->allocis a bypass, to avoid checking the shared reg again and again if it succeeded once.
Oh right, I knew I was missing something.. :/
Quoted 2 lines
For a while I thought I could leverage the
era->refto account the get/put. But it does not work. Because the of theput().
Crud, right you are.
Also, I don't think we could even use them as I outlined; suppose it would have worked; then we'd have:
schedule(group1)
get_event_constraints() +1
schedule(group2)
get_event_constraints() +1
And we'd be stuck with a ref of 2, the put at x86_pmu_del() would never be sufficient to drop them back to 0 again.
A well, your patch does indeed make it work so I'll grab that.
Quoted 60 lines · Peter Zijlstra
Quoted 12 lines · Stephane Eranian
You are missing the error path in
schedule_events():if (!assign || num) {
for (i = 0; i < n; i++) { if (x86_pmu.put_event_constraints) x86_pmu.put_event_constraints(cpuc, cpuc->event_list[i]); } }Quoted 4 lines
That one wipes out on
get()even on events that were correctly schedule in the previous
invocation. So here group2 fails, but it should not release the constraints from group1.What I was saying:
schedule(group1)
get_event_constraints()+1
no error path, no putsschedule(group2)
get_event_constraints()+1
*fail*
put_event_constraints()-1This leaves the constraints of group1 with a net +1 'ref' count and thus if we were to treat the get/put as such, the put wouldn't be the last and thus shouldn't release resources.
Quoted 10 lines
Quoted 10 lines
Only once these events pass through
x86_pmu_del()will they get a final put and the 'ref' count will drop to 0.Now the problem seems to be the get/put things don't actually count properly.
However, if we look at __intel_shared_reg_{get,put}_constraints() there is a refcount in there; namely
era->ref; however we don't appear to clearreg->allocbased on it.Quoted 5 lines
The
era->refis not used to ref count the number of successful attempts at scheduling. It is used to count the number of CPU sharing the resource. So it goes from 0, 1, to 2. You can invokeschedule_events()many more times. Thereg->allocis a bypass, to avoid checking the shared reg again and again if it succeeded once.Oh right, I knew I was missing something.. :/
Quoted 2 lines
For a while I thought I could leverage the
era->refto account the get/put. But it does not work. Because the of theput().Crud, right you are.
Also, I don't think we could even use them as I outlined; suppose it would have worked; then we'd have:
Quoted 9 lines
schedule(group1)
get_event_constraints()+1schedule(group2)
get_event_constraints()+1And we'd be stuck with a ref of 2, the put at
x86_pmu_del()would never be sufficient to drop them back to 0 again.
Yes, imagine we add 3 groups:
schedule(grp1) grp1=1
schedule(grp2) grp1=2, grp2 =1schedule(grp3) -> *failed*, put, grp1=2, grp2=1, grp3=0
And then in the del side, we'd have no way of knowing that to get the current assignment we incrementally scheduled twice, and thus we have to do grp1 -=2 and not just -=1.
Quoted 1 line
A well, your patch does indeed make it work so I'll grab that.
Thanks,
tip-bot for Stephane Eranian
merge bot[tip:perf/core] perf/x86: Fix shared register mutual exclusion enforcement
In reply to Stephane Eranianperf/x86: Fix shared register mutual exclusion enforcement
Commit-ID: 2f7f73a52078b667d64df16eaebdb97d98c9a410 Gitweb: http://git.kernel.org/tip/2f7f73a52078b667d64df16eaebdb97d98c9a410 Author: Stephane Eranian <[email protected]> AuthorDate: Thu, 20 Jun 2013 18:42:54 +0200 Committer: Ingo Molnar <[email protected]> CommitDate: Wed, 26 Jun 2013 21:58:49 +0200
perf/x86: Fix shared register mutual exclusion enforcement
This patch fixes a problem with the shared registers mutual exclusion code and incremental event scheduling by the generic perf_event code.
There was a bug whereby the mutual exclusion on the shared registers was not enforced because of incremental scheduling abort due to event constraints. As an example on Intel Nehalem, consider the following events:
group1= L1D_CACHE_LD:E_STATE,OFFCORE_RESPONSE_0:PF_RFO,L1D_CACHE_LD:I_STATE group2= L1D_CACHE_LD:I_STATE
The L1D_CACHE_LD event can only be measured by 2 counters. Yet, there are 3 instances here. The first group can be scheduled and is committed. Then, the generic code tries to schedule group2 and this fails (because there is no more counter to support the 3rd instance of L1D_CACHE_LD). But in x86_schedule_events() error path, put_event_contraints() is invoked on ALL the events and not just the ones that just failed. That causes the "lock" on the shared offcore_response MSR to be released. Yet the first group is actually scheduled and is exposed to reprogramming of that shared msr by the sibling HT thread. In other words, there is no guarantee on what is measured.
This patch fixes the problem by tagging committed events with the PERF_X86_EVENT_COMMITTED tag. In the error path of x86_schedule_events(), only the events NOT tagged have their constraint released. The tag is eventually removed when the event in descheduled.
--- arch/x86/kernel/cpu/perf_event.c | 26 +++++++++++++++++++++++++- arch/x86/kernel/cpu/perf_event.h | 3 ++- arch/x86/kernel/cpu/perf_event_intel.c | 2 -- 3 files changed, 27 insertions(+), 4 deletions(-)
arch/x86/kernel/cpu/perf_event.c +25 -1
@@ -726,6 +726,7 @@ int x86_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign) {struct event_constraint *c;unsigned long used_mask[BITS_TO_LONGS (X86_PMC_IDX_MAX )]; +struct perf_event *e;int i, wmin, wmax, num =0 ;struct hw_perf_event *hwc;
@@ -770,13 +771,31 @@ int x86_schedule_events(struct cpu_hw_events *cpuc, int n, int *assign) wmax, assign);/* +* Mark the event as committed, so we do not put_constraint() +* in case new events are added and fail scheduling. +*/ +if (!num && assign) { +for (i =0 ; i < n; i++) { + e = cpuc->event_list[i]; + e->hw.flags |=PERF_X86_EVENT_COMMITTED ; + } + } +/* * scheduling failed or is just a simulation, * free resources if necessary */ if (!assign || num) {for (i =0 ; i < n; i++) { + e = cpuc->event_list[i]; +/* +* do not put_constraint() on comitted events, +* because they are good to go +*/ +if ((e->hw.flags &PERF_X86_EVENT_COMMITTED )) +continue ; +if (x86_pmu.put_event_constraints) - x86_pmu.put_event_constraints (cpuc, cpuc->event_list[i]); + x86_pmu.put_event_constraints (cpuc, e); } }return num ? -EINVAL :0 ;
@@ -1156,6 +1175,11 @@ static void x86_pmu_del(struct perf_event *event, int flags)int i;/* +* event is descheduled +*/ + event->hw.flags &= ~PERF_X86_EVENT_COMMITTED ; + +/* * If we're called during a txn, we don't need to do anything. * The events never got scheduled and ->cancel_txn will truncate * the event_list.
arch/x86/kernel/cpu/perf_event.h +2 -1
@@ -63,11 +63,12 @@ struct event_constraint {int flags; };/* -* struct event_constraint flags +* struct hw_perf_event.flags flags */ #define PERF_X86_EVENT_PEBS_LDLAT 0x1/* ld+ldlat data address sampling */ #define PERF_X86_EVENT_PEBS_ST 0x2/* st data address sampling */ #define PERF_X86_EVENT_PEBS_ST_HSW 0x4/* haswell style st data sampling */ +#define PERF_X86_EVENT_COMMITTED 0x8/* event passed commit_txn */ struct amd_nb {int nb_id;/* NorthBridge id */
arch/x86/kernel/cpu/perf_event_intel.c +0 -2
@@ -1450,7 +1450,6 @@ x86_get_event_constraints(struct cpu_hw_events *cpuc, struct perf_event *event)if (x86_pmu.event_constraints) {for_each_event_constraint (c, x86_pmu.event_constraints) {if ((event->hw.config & c->cmask) == c->code) { -/* hw.flags zeroed at initialization */ event->hw.flags |= c->flags;return c; }
@@ -1498,7 +1497,6 @@ intel_put_shared_regs_event_constraints(struct cpu_hw_events *cpuc,static void intel_put_event_constraints (struct cpu_hw_events *cpuc,struct perf_event *event) { - event->hw.flags =0 ;intel_put_shared_regs_event_constraints (cpuc, event); }