@@ -494,12 +494,12 @@ enum pmc_type {
struct kvm_pmc {
enum pmc_type type;
u8 idx;
+ u8 max_nr_events;
bool is_paused;
bool intr;
u64 counter;
u64 prev_counter;
u64 eventsel;
- struct perf_event *perf_event;
struct kvm_vcpu *vcpu;
/*
* only for creating or reusing perf_event,
@@ -507,6 +507,10 @@ struct kvm_pmc {
* ctrl value for fixed counters.
*/
u64 current_config;
+ union {
+ struct perf_event *perf_event;
+ DECLARE_FLEX_ARRAY(struct perf_event *, perf_events);
+ };
};
/* More counters may conflict with other existing Architectural MSRs */
@@ -160,7 +160,7 @@ static int pmc_reprogram_counter(struct kvm_pmc *pmc, u32 type, u64 config,
bool intr)
{
struct kvm_pmu *pmu = pmc_to_pmu(pmc);
- struct perf_event *event;
+ struct perf_event *event, *group_leader;
struct perf_event_attr attr = {
.type = type,
.size = sizeof(attr),
@@ -172,6 +172,7 @@ static int pmc_reprogram_counter(struct kvm_pmc *pmc, u32 type, u64 config,
.config = config,
};
bool pebs = test_bit(pmc->idx, (unsigned long *)&pmu->pebs_enable);
+ unsigned int i;
attr.sample_period = get_sample_period(pmc, pmc->counter);
@@ -204,18 +205,39 @@ static int pmc_reprogram_counter(struct kvm_pmc *pmc, u32 type, u64 config,
attr.precise_ip = 3;
}
- event = perf_event_create_kernel_counter(&attr, -1, current, NULL,
- kvm_perf_overflow, pmc);
- if (IS_ERR(event)) {
- pr_debug_ratelimited("kvm_pmu: event creation failed %ld for pmc->idx = %d\n",
- PTR_ERR(event), pmc->idx);
- return PTR_ERR(event);
+ /*
+ * To create grouped events, the first created perf_event doesn't
+ * know it will be the group_leader and may move to an unexpected
+ * enabling path, thus delay all enablement until after creation,
+ * not affecting non-grouped events to save one perf interface call.
+ */
+ if (pmc->max_nr_events > 1)
+ attr.disabled = 1;
+
+ for (i = 0; i < pmc->max_nr_events; i++) {
+ group_leader = i ? pmc->perf_event : NULL;
+ event = perf_event_create_kernel_counter(&attr, -1, current, group_leader,
+ kvm_perf_overflow, pmc);
+ if (IS_ERR(event)) {
+ pr_debug_ratelimited("kvm_pmu: event creation failed %ld for pmc->idx = %d\n",
+ PTR_ERR(event), pmc->idx);
+ return PTR_ERR(event);
+ }
+
+ pmc->perf_events[i] = event;
+ pmc_to_pmu(pmc)->event_count++;
}
- pmc->perf_event = event;
- pmc_to_pmu(pmc)->event_count++;
pmc->is_paused = false;
pmc->intr = intr || pebs;
+
+ if (!attr.disabled)
+ return 0;
+
+ /* Enable grouped events in order. */
+ for (i = 0; i < pmc->max_nr_events; i++)
+ perf_event_enable(pmc->perf_events[i]);
+
return 0;
}
@@ -223,6 +245,10 @@ static void pmc_pause_counter(struct kvm_pmc *pmc)
{
u64 counter = pmc->counter;
+ /* The perf_event_pause() is not suitable for grouped events. */
+ if (pmc->max_nr_events > 1)
+ pmc_stop_counter(pmc);
+
if (!pmc->perf_event || pmc->is_paused)
return;
@@ -64,12 +64,19 @@ static inline u64 pmc_read_counter(struct kvm_pmc *pmc)
static inline void pmc_release_perf_event(struct kvm_pmc *pmc)
{
- if (pmc->perf_event) {
- perf_event_release_kernel(pmc->perf_event);
- pmc->perf_event = NULL;
- pmc->current_config = 0;
+ unsigned int i;
+
+ for (i = 0; i < pmc->max_nr_events; i++) {
+ if (!pmc->perf_events[i])
+ continue;
+
+ perf_event_release_kernel(pmc->perf_events[i]);
+ pmc->perf_events[i] = NULL;
pmc_to_pmu(pmc)->event_count--;
}
+
+ pmc->current_config = 0;
+ pmc->max_nr_events = 1;
}
static inline void pmc_stop_counter(struct kvm_pmc *pmc)
@@ -200,6 +200,7 @@ static void amd_pmu_init(struct kvm_vcpu *vcpu)
pmu->gp_counters[i].vcpu = vcpu;
pmu->gp_counters[i].idx = i;
pmu->gp_counters[i].current_config = 0;
+ pmu->gp_counters[i].max_nr_events = 1;
}
}
@@ -642,6 +642,7 @@ static void intel_pmu_init(struct kvm_vcpu *vcpu)
pmu->gp_counters[i].vcpu = vcpu;
pmu->gp_counters[i].idx = i;
pmu->gp_counters[i].current_config = 0;
+ pmu->gp_counters[i].max_nr_events = 1;
}
for (i = 0; i < KVM_PMC_MAX_FIXED; i++) {
@@ -649,6 +650,7 @@ static void intel_pmu_init(struct kvm_vcpu *vcpu)
pmu->fixed_counters[i].vcpu = vcpu;
pmu->fixed_counters[i].idx = i + INTEL_PMC_IDX_FIXED;
pmu->fixed_counters[i].current_config = 0;
+ pmu->fixed_counters[i].max_nr_events = 1;
}
lbr_desc->records.nr = 0;