perf/x86/intel: Support TopDown metrics on Ice Lake
[linux-2.6-microblaze.git] / arch / x86 / events / intel / core.c
index 5096347..db83334 100644 (file)
@@ -247,6 +247,10 @@ static struct event_constraint intel_icl_event_constraints[] = {
        FIXED_EVENT_CONSTRAINT(0x003c, 1),      /* CPU_CLK_UNHALTED.CORE */
        FIXED_EVENT_CONSTRAINT(0x0300, 2),      /* CPU_CLK_UNHALTED.REF */
        FIXED_EVENT_CONSTRAINT(0x0400, 3),      /* SLOTS */
+       METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_RETIRING, 0),
+       METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_BAD_SPEC, 1),
+       METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_FE_BOUND, 2),
+       METRIC_EVENT_CONSTRAINT(INTEL_TD_METRIC_BE_BOUND, 3),
        INTEL_EVENT_CONSTRAINT_RANGE(0x03, 0x0a, 0xf),
        INTEL_EVENT_CONSTRAINT_RANGE(0x1f, 0x28, 0xf),
        INTEL_EVENT_CONSTRAINT(0x32, 0xf),      /* SW_PREFETCH_ACCESS.* */
@@ -309,6 +313,12 @@ EVENT_ATTR_STR_HT(topdown-recovery-bubbles, td_recovery_bubbles,
 EVENT_ATTR_STR_HT(topdown-recovery-bubbles.scale, td_recovery_bubbles_scale,
        "4", "2");
 
+EVENT_ATTR_STR(slots,                  slots,          "event=0x00,umask=0x4");
+EVENT_ATTR_STR(topdown-retiring,       td_retiring,    "event=0x00,umask=0x80");
+EVENT_ATTR_STR(topdown-bad-spec,       td_bad_spec,    "event=0x00,umask=0x81");
+EVENT_ATTR_STR(topdown-fe-bound,       td_fe_bound,    "event=0x00,umask=0x82");
+EVENT_ATTR_STR(topdown-be-bound,       td_be_bound,    "event=0x00,umask=0x83");
+
 static struct attribute *snb_events_attrs[] = {
        EVENT_PTR(td_slots_issued),
        EVENT_PTR(td_slots_retired),
@@ -2165,11 +2175,24 @@ static inline void intel_clear_masks(struct perf_event *event, int idx)
 static void intel_pmu_disable_fixed(struct perf_event *event)
 {
        struct hw_perf_event *hwc = &event->hw;
-       int idx = hwc->idx - INTEL_PMC_IDX_FIXED;
        u64 ctrl_val, mask;
+       int idx = hwc->idx;
 
-       mask = 0xfULL << (idx * 4);
+       if (is_topdown_idx(idx)) {
+               struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
+
+               /*
+                * When there are other active TopDown events,
+                * don't disable the fixed counter 3.
+                */
+               if (*(u64 *)cpuc->active_mask & INTEL_PMC_OTHER_TOPDOWN_BITS(idx))
+                       return;
+               idx = INTEL_PMC_IDX_FIXED_SLOTS;
+       }
 
+       intel_clear_masks(event, idx);
+
+       mask = 0xfULL << ((idx - INTEL_PMC_IDX_FIXED) * 4);
        rdmsrl(hwc->config_base, ctrl_val);
        ctrl_val &= ~mask;
        wrmsrl(hwc->config_base, ctrl_val);
@@ -2180,17 +2203,28 @@ static void intel_pmu_disable_event(struct perf_event *event)
        struct hw_perf_event *hwc = &event->hw;
        int idx = hwc->idx;
 
-       if (idx < INTEL_PMC_IDX_FIXED) {
+       switch (idx) {
+       case 0 ... INTEL_PMC_IDX_FIXED - 1:
                intel_clear_masks(event, idx);
                x86_pmu_disable_event(event);
-       } else if (idx < INTEL_PMC_IDX_FIXED_BTS) {
-               intel_clear_masks(event, idx);
+               break;
+       case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED_BTS - 1:
+       case INTEL_PMC_IDX_METRIC_BASE ... INTEL_PMC_IDX_METRIC_END:
                intel_pmu_disable_fixed(event);
-       } else if (idx == INTEL_PMC_IDX_FIXED_BTS) {
+               break;
+       case INTEL_PMC_IDX_FIXED_BTS:
                intel_pmu_disable_bts();
                intel_pmu_drain_bts_buffer();
-       } else if (idx == INTEL_PMC_IDX_FIXED_VLBR)
+               return;
+       case INTEL_PMC_IDX_FIXED_VLBR:
+               intel_clear_masks(event, idx);
+               break;
+       default:
                intel_clear_masks(event, idx);
+               pr_warn("Failed to disable the event with invalid index %d\n",
+                       idx);
+               return;
+       }
 
        /*
         * Needs to be called after x86_pmu_disable_event,
@@ -2208,10 +2242,119 @@ static void intel_pmu_del_event(struct perf_event *event)
                intel_pmu_pebs_del(event);
 }
 
+static int icl_set_topdown_event_period(struct perf_event *event)
+{
+       struct hw_perf_event *hwc = &event->hw;
+       s64 left = local64_read(&hwc->period_left);
+
+       /*
+        * The values in PERF_METRICS MSR are derived from fixed counter 3.
+        * Software should start both registers, PERF_METRICS and fixed
+        * counter 3, from zero.
+        * Clear PERF_METRICS and Fixed counter 3 in initialization.
+        * After that, both MSRs will be cleared for each read.
+        * Don't need to clear them again.
+        */
+       if (left == x86_pmu.max_period) {
+               wrmsrl(MSR_CORE_PERF_FIXED_CTR3, 0);
+               wrmsrl(MSR_PERF_METRICS, 0);
+               local64_set(&hwc->period_left, 0);
+       }
+
+       perf_event_update_userpage(event);
+
+       return 0;
+}
+
+static inline u64 icl_get_metrics_event_value(u64 metric, u64 slots, int idx)
+{
+       u32 val;
+
+       /*
+        * The metric is reported as an 8bit integer fraction
+        * suming up to 0xff.
+        * slots-in-metric = (Metric / 0xff) * slots
+        */
+       val = (metric >> ((idx - INTEL_PMC_IDX_METRIC_BASE) * 8)) & 0xff;
+       return  mul_u64_u32_div(slots, val, 0xff);
+}
+
+static void __icl_update_topdown_event(struct perf_event *event,
+                                      u64 slots, u64 metrics)
+{
+       int idx = event->hw.idx;
+       u64 delta;
+
+       if (is_metric_idx(idx))
+               delta = icl_get_metrics_event_value(metrics, slots, idx);
+       else
+               delta = slots;
+
+       local64_add(delta, &event->count);
+}
+
+/*
+ * Update all active Topdown events.
+ *
+ * The PERF_METRICS and Fixed counter 3 are read separately. The values may be
+ * modify by a NMI. PMU has to be disabled before calling this function.
+ */
+static u64 icl_update_topdown_event(struct perf_event *event)
+{
+       struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
+       struct perf_event *other;
+       u64 slots, metrics;
+       int idx;
+
+       /* read Fixed counter 3 */
+       rdpmcl((3 | INTEL_PMC_FIXED_RDPMC_BASE), slots);
+       if (!slots)
+               return 0;
+
+       /* read PERF_METRICS */
+       rdpmcl(INTEL_PMC_FIXED_RDPMC_METRICS, metrics);
+
+       for_each_set_bit(idx, cpuc->active_mask, INTEL_PMC_IDX_TD_BE_BOUND + 1) {
+               if (!is_topdown_idx(idx))
+                       continue;
+               other = cpuc->events[idx];
+               __icl_update_topdown_event(other, slots, metrics);
+       }
+
+       /*
+        * Check and update this event, which may have been cleared
+        * in active_mask e.g. x86_pmu_stop()
+        */
+       if (event && !test_bit(event->hw.idx, cpuc->active_mask))
+               __icl_update_topdown_event(event, slots, metrics);
+
+       /* The fixed counter 3 has to be written before the PERF_METRICS. */
+       wrmsrl(MSR_CORE_PERF_FIXED_CTR3, 0);
+       wrmsrl(MSR_PERF_METRICS, 0);
+
+       return slots;
+}
+
+static void intel_pmu_read_topdown_event(struct perf_event *event)
+{
+       struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
+
+       /* Only need to call update_topdown_event() once for group read. */
+       if ((cpuc->txn_flags & PERF_PMU_TXN_READ) &&
+           !is_slots_event(event))
+               return;
+
+       perf_pmu_disable(event->pmu);
+       x86_pmu.update_topdown_event(event);
+       perf_pmu_enable(event->pmu);
+}
+
 static void intel_pmu_read_event(struct perf_event *event)
 {
        if (event->hw.flags & PERF_X86_EVENT_AUTO_RELOAD)
                intel_pmu_auto_reload_read(event);
+       else if (is_topdown_count(event) && x86_pmu.update_topdown_event)
+               intel_pmu_read_topdown_event(event);
        else
                x86_perf_event_update(event);
 }
@@ -2219,8 +2362,22 @@ static void intel_pmu_read_event(struct perf_event *event)
 static void intel_pmu_enable_fixed(struct perf_event *event)
 {
        struct hw_perf_event *hwc = &event->hw;
-       int idx = hwc->idx - INTEL_PMC_IDX_FIXED;
        u64 ctrl_val, mask, bits = 0;
+       int idx = hwc->idx;
+
+       if (is_topdown_idx(idx)) {
+               struct cpu_hw_events *cpuc = this_cpu_ptr(&cpu_hw_events);
+               /*
+                * When there are other active TopDown events,
+                * don't enable the fixed counter 3 again.
+                */
+               if (*(u64 *)cpuc->active_mask & INTEL_PMC_OTHER_TOPDOWN_BITS(idx))
+                       return;
+
+               idx = INTEL_PMC_IDX_FIXED_SLOTS;
+       }
+
+       intel_set_masks(event, idx);
 
        /*
         * Enable IRQ generation (0x8), if not PEBS,
@@ -2240,6 +2397,7 @@ static void intel_pmu_enable_fixed(struct perf_event *event)
        if (x86_pmu.version > 2 && hwc->config & ARCH_PERFMON_EVENTSEL_ANY)
                bits |= 0x4;
 
+       idx -= INTEL_PMC_IDX_FIXED;
        bits <<= (idx * 4);
        mask = 0xfULL << (idx * 4);
 
@@ -2262,18 +2420,27 @@ static void intel_pmu_enable_event(struct perf_event *event)
        if (unlikely(event->attr.precise_ip))
                intel_pmu_pebs_enable(event);
 
-       if (idx < INTEL_PMC_IDX_FIXED) {
+       switch (idx) {
+       case 0 ... INTEL_PMC_IDX_FIXED - 1:
                intel_set_masks(event, idx);
                __x86_pmu_enable_event(hwc, ARCH_PERFMON_EVENTSEL_ENABLE);
-       } else if (idx < INTEL_PMC_IDX_FIXED_BTS) {
-               intel_set_masks(event, idx);
+               break;
+       case INTEL_PMC_IDX_FIXED ... INTEL_PMC_IDX_FIXED_BTS - 1:
+       case INTEL_PMC_IDX_METRIC_BASE ... INTEL_PMC_IDX_METRIC_END:
                intel_pmu_enable_fixed(event);
-       } else if (idx == INTEL_PMC_IDX_FIXED_BTS) {
+               break;
+       case INTEL_PMC_IDX_FIXED_BTS:
                if (!__this_cpu_read(cpu_hw_events.enabled))
                        return;
                intel_pmu_enable_bts(hwc->config);
-       } else if (idx == INTEL_PMC_IDX_FIXED_VLBR)
+               break;
+       case INTEL_PMC_IDX_FIXED_VLBR:
                intel_set_masks(event, idx);
+               break;
+       default:
+               pr_warn("Failed to enable the event with invalid index %d\n",
+                       idx);
+       }
 }
 
 static void intel_pmu_add_event(struct perf_event *event)
@@ -2389,7 +2556,7 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status)
        /*
         * PEBS overflow sets bit 62 in the global status register
         */
-       if (__test_and_clear_bit(62, (unsigned long *)&status)) {
+       if (__test_and_clear_bit(GLOBAL_STATUS_BUFFER_OVF_BIT, (unsigned long *)&status)) {
                u64 pebs_enabled = cpuc->pebs_enabled;
 
                handled++;
@@ -2410,7 +2577,7 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status)
        /*
         * Intel PT
         */
-       if (__test_and_clear_bit(55, (unsigned long *)&status)) {
+       if (__test_and_clear_bit(GLOBAL_STATUS_TRACE_TOPAPMI_BIT, (unsigned long *)&status)) {
                handled++;
                if (unlikely(perf_guest_cbs && perf_guest_cbs->is_in_guest() &&
                        perf_guest_cbs->handle_intel_pt_intr))
@@ -2419,6 +2586,15 @@ static int handle_pmi_common(struct pt_regs *regs, u64 status)
                        intel_pt_interrupt();
        }
 
+       /*
+        * Intel Perf mertrics
+        */
+       if (__test_and_clear_bit(GLOBAL_STATUS_PERF_METRICS_OVF_BIT, (unsigned long *)&status)) {
+               handled++;
+               if (x86_pmu.update_topdown_event)
+                       x86_pmu.update_topdown_event(NULL);
+       }
+
        /*
         * Checkpointed counters can lead to 'spurious' PMIs because the
         * rollback caused by the PMI will have cleared the overflow status
@@ -3355,6 +3531,58 @@ static int intel_pmu_hw_config(struct perf_event *event)
        if (event->attr.type != PERF_TYPE_RAW)
                return 0;
 
+       /*
+        * Config Topdown slots and metric events
+        *
+        * The slots event on Fixed Counter 3 can support sampling,
+        * which will be handled normally in x86_perf_event_update().
+        *
+        * Metric events don't support sampling and require being paired
+        * with a slots event as group leader. When the slots event
+        * is used in a metrics group, it too cannot support sampling.
+        */
+       if (x86_pmu.intel_cap.perf_metrics && is_topdown_event(event)) {
+               if (event->attr.config1 || event->attr.config2)
+                       return -EINVAL;
+
+               /*
+                * The TopDown metrics events and slots event don't
+                * support any filters.
+                */
+               if (event->attr.config & X86_ALL_EVENT_FLAGS)
+                       return -EINVAL;
+
+               if (is_metric_event(event)) {
+                       struct perf_event *leader = event->group_leader;
+
+                       /* The metric events don't support sampling. */
+                       if (is_sampling_event(event))
+                               return -EINVAL;
+
+                       /* The metric events require a slots group leader. */
+                       if (!is_slots_event(leader))
+                               return -EINVAL;
+
+                       /*
+                        * The leader/SLOTS must not be a sampling event for
+                        * metric use; hardware requires it starts at 0 when used
+                        * in conjunction with MSR_PERF_METRICS.
+                        */
+                       if (is_sampling_event(leader))
+                               return -EINVAL;
+
+                       event->event_caps |= PERF_EV_CAP_SIBLING;
+                       /*
+                        * Only once we have a METRICs sibling do we
+                        * need TopDown magic.
+                        */
+                       leader->hw.flags |= PERF_X86_EVENT_TOPDOWN;
+                       event->hw.flags  |= PERF_X86_EVENT_TOPDOWN;
+
+                       event->hw.flags &= ~PERF_X86_EVENT_RDPMC_ALLOWED;
+               }
+       }
+
        if (!(event->attr.config & ARCH_PERFMON_EVENTSEL_ANY))
                return 0;
 
@@ -4355,6 +4583,15 @@ static struct attribute *icl_events_attrs[] = {
        NULL,
 };
 
+static struct attribute *icl_td_events_attrs[] = {
+       EVENT_PTR(slots),
+       EVENT_PTR(td_retiring),
+       EVENT_PTR(td_bad_spec),
+       EVENT_PTR(td_fe_bound),
+       EVENT_PTR(td_be_bound),
+       NULL,
+};
+
 static struct attribute *icl_tsx_events_attrs[] = {
        EVENT_PTR(tx_start),
        EVENT_PTR(tx_abort),
@@ -5139,10 +5376,13 @@ __init int intel_pmu_init(void)
                        hsw_format_attr : nhm_format_attr;
                extra_skl_attr = skl_format_attr;
                mem_attr = icl_events_attrs;
+               td_attr = icl_td_events_attrs;
                tsx_attr = icl_tsx_events_attrs;
                x86_pmu.rtm_abort_event = X86_CONFIG(.event=0xca, .umask=0x02);
                x86_pmu.lbr_pt_coexist = true;
                intel_pmu_pebs_data_source_skl(pmem);
+               x86_pmu.update_topdown_event = icl_update_topdown_event;
+               x86_pmu.set_topdown_event_period = icl_set_topdown_event_period;
                pr_cont("Icelake events, ");
                name = "icelake";
                break;
@@ -5198,6 +5438,15 @@ __init int intel_pmu_init(void)
                 * counter, so do not extend mask to generic counters
                 */
                for_each_event_constraint(c, x86_pmu.event_constraints) {
+                       /*
+                        * Don't extend the topdown slots and metrics
+                        * events to the generic counters.
+                        */
+                       if (c->idxmsk64 & INTEL_PMC_MSK_TOPDOWN) {
+                               c->weight = hweight64(c->idxmsk64);
+                               continue;
+                       }
+
                        if (c->cmask == FIXED_EVENT_FLAGS
                            && c->idxmsk64 != INTEL_PMC_MSK_FIXED_REF_CYCLES) {
                                c->idxmsk64 |= (1ULL << x86_pmu.num_counters) - 1;
@@ -5253,6 +5502,9 @@ __init int intel_pmu_init(void)
        if (x86_pmu.counter_freezing)
                x86_pmu.handle_irq = intel_pmu_handle_irq_v4;
 
+       if (x86_pmu.intel_cap.perf_metrics)
+               x86_pmu.intel_ctrl |= 1ULL << GLOBAL_CTRL_EN_PERF_METRICS;
+
        return 0;
 }