[PATCH v2 2/2] drm/xe/pmu: Add GT frequency events
Lucas De Marchi
lucas.demarchi at intel.com
Tue Feb 4 23:34:59 UTC 2025
On Tue, Feb 04, 2025 at 10:09:05AM -0800, Vinay Belgaumkar wrote:
>Define PMU events for GT frequency (actual and requested). This is
>a port from the i915 driver implementation, where an internal timer
>is used to aggregate GT frequencies over certain fixed interval.
>Following PMU events are being added-
>
> xe_0000_00_02.0/gt-actual-frequency/ [Kernel PMU event]
> xe_0000_00_02.0/gt-requested-frequency/ [Kernel PMU event]
>
>Standard perf commands can be used to monitor GT frequency-
> $ perf stat -e xe_0000_00_02.0/gt-requested-frequency,gt=0/ -I1000
>
> 1.001175175 700 M xe/gt-requested-frequency,gt=0/
> 2.005891881 703 M xe/gt-requested-frequency,gt=0/
> 3.007318169 700 M xe/gt-requested-frequency,gt=0/
>
>Actual frequencies will be reported as 0 when GT is suspended.
>
>v2: Use locks while storing samples, keep track of multiple clients (Lucas)
>
>Cc: Riana Tauro <riana.tauro at intel.com>
>Cc: Lucas De Marchi <lucas.demarchi at intel.com>
>Cc: Rodrigo Vivi <rodrigo.vivi at intel.com>
>Signed-off-by: Vinay Belgaumkar <vinay.belgaumkar at intel.com>
>---
> drivers/gpu/drm/xe/xe_pmu.c | 140 +++++++++++++++++++++++++++++-
> drivers/gpu/drm/xe/xe_pmu_types.h | 16 ++++
> 2 files changed, 153 insertions(+), 3 deletions(-)
>
>diff --git a/drivers/gpu/drm/xe/xe_pmu.c b/drivers/gpu/drm/xe/xe_pmu.c
>index 10603e7c3eff..95ebdbd40930 100644
>--- a/drivers/gpu/drm/xe/xe_pmu.c
>+++ b/drivers/gpu/drm/xe/xe_pmu.c
>@@ -8,6 +8,7 @@
>
> #include "xe_device.h"
> #include "xe_gt_idle.h"
>+#include "xe_guc_pc.h"
> #include "xe_macros.h"
> #include "xe_pm.h"
> #include "xe_pmu.h"
>@@ -38,6 +39,7 @@
>
> #define XE_PMU_EVENT_GT_MASK GENMASK_ULL(63, 60)
> #define XE_PMU_EVENT_ID_MASK GENMASK_ULL(11, 0)
>+#define SAMPLING_TIMER_FREQUENCY_HZ 200
>
> static struct xe_pmu *event_to_pmu(struct perf_event *event)
> {
>@@ -55,6 +57,8 @@ static unsigned int config_to_gt_id(u64 config)
> }
>
> #define XE_PMU_EVENT_GT_C6_RESIDENCY 0x01
>+#define XE_PMU_EVENT_GT_ACTUAL_FREQUENCY 0x02
>+#define XE_PMU_EVENT_GT_REQUESTED_FREQUENCY 0x03
>
> static struct xe_gt *event_to_gt(struct perf_event *event)
> {
>@@ -119,16 +123,45 @@ static int xe_pmu_event_init(struct perf_event *event)
> return 0;
> }
>
>+static u64 get_stored_sample(struct xe_pmu *pmu, unsigned int gt_id, int sample)
>+{
>+ return pmu->event_sample[gt_id][sample];
lock?
>+}
>+
>+static void
>+add_sample_mult(struct xe_pmu *pmu, unsigned int gt_id, int sample, u32 val, u32 mul)
>+{
>+ unsigned long flags;
>+
>+ raw_spin_lock_irqsave(&pmu->lock, flags);
>+ pmu->event_sample[gt_id][sample] += mul_u32_u32(val, mul);
>+ raw_spin_unlock_irqrestore(&pmu->lock, flags);
>+}
>+
> static u64 __xe_pmu_event_read(struct perf_event *event)
> {
> struct xe_gt *gt = event_to_gt(event);
>+ struct xe_device *xe =
>+ container_of(event->pmu, typeof(*xe), pmu.base);
>+ struct xe_pmu *pmu = &xe->pmu;
>+ unsigned long event_id;
>+ u64 gt_id;
>
> if (!gt)
> return 0;
>+ else
>+ gt_id = gt->info.id;
>+
>+ event_id = config_to_event_id(event->attr.config);
>
>- switch (config_to_event_id(event->attr.config)) {
>+ switch (event_id) {
> case XE_PMU_EVENT_GT_C6_RESIDENCY:
> return xe_gt_idle_residency_msec(>->gtidle);
>+ case XE_PMU_EVENT_GT_ACTUAL_FREQUENCY:
>+ case XE_PMU_EVENT_GT_REQUESTED_FREQUENCY:
>+ return div_u64(get_stored_sample(pmu, gt_id,
>+ event_id),
>+ USEC_PER_SEC /* to MHz */);
why do we do intermediary conversions to usec?
> }
>
> return 0;
>@@ -160,6 +193,87 @@ static void xe_pmu_event_read(struct perf_event *event)
> xe_pmu_event_update(event);
> }
>
>+#define CONFIG_EVENT_ENABLED(config, event) \
>+ (FIELD_GET(XE_PMU_EVENT_ID_MASK, config) & event)
>+
>+#define CONFIG_EVENT_ENABLED_GT(config, event, gt) \
>+ (CONFIG_EVENT_ENABLED(config, event) && \
>+ (FIELD_GET(XE_PMU_EVENT_GT_MASK, config) == gt))
yeah, like I said in patch 1, I don't think this will work.
... FIELD_GET(XE_PMU_EVENT_GT_MASK, pmu->enable) can be
anything.
if you are sampling gt0 and gt1, it will actually be 3. Then if you stop
sampling gt0, it will be 2.
>+
>+static bool pmu_needs_timer(struct xe_pmu *pmu)
>+{
>+ return CONFIG_EVENT_ENABLED(pmu->enable,
>+ (XE_PMU_EVENT_GT_ACTUAL_FREQUENCY |
>+ XE_PMU_EVENT_GT_REQUESTED_FREQUENCY));
>+}
>+
>+static void xe_pmu_start_timer(struct xe_pmu *pmu)
>+{
>+ u64 period = max_t(u64, 10000, NSEC_PER_SEC / SAMPLING_TIMER_FREQUENCY_HZ);
>+
>+ if (!pmu->timer_enabled && pmu_needs_timer(pmu)) {
>+ pmu->timer_enabled = true;
>+ pmu->timer_last = ktime_get();
>+ hrtimer_start_range_ns(&pmu->timer,
>+ ns_to_ktime(period), 0,
>+ HRTIMER_MODE_REL_PINNED);
>+ }
>+}
>+
>+static void
>+frequency_sample(struct xe_gt *gt, unsigned int period_ns)
>+{
>+ struct xe_device *xe = gt_to_xe(gt);
>+ struct xe_pmu *pmu = &xe->pmu;
>+ int ret;
>+ u32 cur_freq;
>+
>+ if (CONFIG_EVENT_ENABLED_GT(pmu->enable, XE_PMU_EVENT_GT_ACTUAL_FREQUENCY, gt->info.id)) {
>+ u32 val;
>+
>+ /* Actual freq will be 0 when GT is in C6 */
>+ val = xe_guc_pc_get_act_freq(>->uc.guc.pc);
>+ add_sample_mult(pmu, gt->info.id, XE_PMU_EVENT_GT_ACTUAL_FREQUENCY,
>+ val, period_ns / 1000);
>+ }
>+
>+ if (CONFIG_EVENT_ENABLED_GT(pmu->enable, XE_PMU_EVENT_GT_REQUESTED_FREQUENCY,
>+ gt->info.id)) {
>+ ret = xe_guc_pc_get_cur_freq(>->uc.guc.pc, &cur_freq);
>+ if (!ret)
>+ add_sample_mult(pmu, gt->info.id, XE_PMU_EVENT_GT_REQUESTED_FREQUENCY,
>+ cur_freq, period_ns / 1000);
>+ }
>+}
>+
>+static enum hrtimer_restart xe_sample(struct hrtimer *hrtimer)
>+{
>+ struct xe_pmu *pmu = container_of(hrtimer, struct xe_pmu, timer);
>+ struct xe_device *xe = container_of(pmu, typeof(*xe), pmu);
>+ u64 period = max_t(u64, 10000, NSEC_PER_SEC / SAMPLING_TIMER_FREQUENCY_HZ);
>+ unsigned int period_ns;
>+ struct xe_gt *gt;
>+ ktime_t now;
>+ unsigned int i;
>+
>+ if (!READ_ONCE(pmu->timer_enabled))
>+ return HRTIMER_NORESTART;
>+
>+ now = ktime_get();
>+ period_ns = ktime_to_ns(ktime_sub(now, pmu->timer_last));
>+ pmu->timer_last = now;
>+
>+ /*
>+ * The passed in period includes the time needed for grabbing the forcewake.
>+ */
>+ for_each_gt(gt, xe, i)
>+ frequency_sample(gt, period_ns);
>+
>+ hrtimer_forward(hrtimer, now, ns_to_ktime(period));
>+
>+ return HRTIMER_RESTART;
>+}
>+
> static void xe_pmu_enable(struct perf_event *event)
> {
> struct xe_pmu *pmu = event_to_pmu(event);
>@@ -176,6 +290,9 @@ static void xe_pmu_enable(struct perf_event *event)
> pmu->enable |= event->attr.config;
> pmu->enable_count[event_bit]++;
>
>+ /* Start a timer, if needed, to collect samples */
>+ xe_pmu_start_timer(pmu);
>+
> raw_spin_unlock_irqrestore(&pmu->lock, flags);
> /*
> * Store the current counter value so we can report the correct delta
>@@ -205,10 +322,13 @@ static void xe_pmu_disable(struct perf_event *event)
> unsigned int event_bit = config_to_event_id(event->attr.config);
>
> XE_WARN_ON(event_bit >= XE_PMU_MAX_EVENT_TYPES);
>+
> raw_spin_lock_irqsave(&pmu->lock, flags);
>
>- if (--pmu->enable_count[event_bit] == 0)
>+ if (--pmu->enable_count[event_bit] == 0) {
> pmu->enable &= ~event->attr.config;
>+ pmu->timer_enabled &= pmu_needs_timer(pmu);
>+ }
>
> raw_spin_unlock_irqrestore(&pmu->lock, flags);
> }
>@@ -310,6 +430,10 @@ static ssize_t event_attr_show(struct device *dev,
> XE_EVENT_ATTR_GROUP(v_, id_, &pmu_event_ ##v_.attr.attr)
>
> XE_EVENT_ATTR_SIMPLE(gt-c6-residency, gt_c6_residency, XE_PMU_EVENT_GT_C6_RESIDENCY, "ms");
>+XE_EVENT_ATTR_SIMPLE(gt-actual-frequency, gt_actual_frequency,
>+ XE_PMU_EVENT_GT_ACTUAL_FREQUENCY, "Mhz");
>+XE_EVENT_ATTR_SIMPLE(gt-requested-frequency, gt_requested_frequency,
>+ XE_PMU_EVENT_GT_REQUESTED_FREQUENCY, "Mhz");
>
> static struct attribute *pmu_empty_event_attrs[] = {
> /* Empty - all events are added as groups with .attr_update() */
>@@ -323,6 +447,8 @@ static const struct attribute_group pmu_events_attr_group = {
>
> static const struct attribute_group *pmu_events_attr_update[] = {
> &pmu_group_gt_c6_residency,
>+ &pmu_group_gt_actual_frequency,
>+ &pmu_group_gt_requested_frequency,
> NULL,
> };
>
>@@ -330,8 +456,11 @@ static void set_supported_events(struct xe_pmu *pmu)
> {
> struct xe_device *xe = container_of(pmu, typeof(*xe), pmu);
>
>- if (!xe->info.skip_guc_pc)
>+ if (!xe->info.skip_guc_pc) {
> pmu->supported_events |= BIT_ULL(XE_PMU_EVENT_GT_C6_RESIDENCY);
>+ pmu->supported_events |= BIT_ULL(XE_PMU_EVENT_GT_ACTUAL_FREQUENCY);
>+ pmu->supported_events |= BIT_ULL(XE_PMU_EVENT_GT_REQUESTED_FREQUENCY);
>+ }
> }
>
> /**
>@@ -346,6 +475,8 @@ static void xe_pmu_unregister(void *arg)
> if (!pmu->registered)
> return;
>
>+ hrtimer_cancel(&pmu->timer);
>+
> pmu->registered = false;
>
> perf_pmu_unregister(&pmu->base);
>@@ -376,6 +507,9 @@ int xe_pmu_register(struct xe_pmu *pmu)
>
> raw_spin_lock_init(&pmu->lock);
>
>+ hrtimer_init(&pmu->timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
>+ pmu->timer.function = xe_sample;
>+
> name = kasprintf(GFP_KERNEL, "xe_%s",
> dev_name(xe->drm.dev));
> if (!name)
>diff --git a/drivers/gpu/drm/xe/xe_pmu_types.h b/drivers/gpu/drm/xe/xe_pmu_types.h
>index b15858d4d4a9..808d0e854f75 100644
>--- a/drivers/gpu/drm/xe/xe_pmu_types.h
>+++ b/drivers/gpu/drm/xe/xe_pmu_types.h
>@@ -48,6 +48,22 @@ struct xe_pmu {
> * @enable_count: Reference counts for the enabled events.
> */
> unsigned int enable_count[XE_PMU_MAX_EVENT_TYPES];
>+ /**
>+ * @timer: Timer for sampling GT frequency.
>+ */
>+ struct hrtimer timer;
>+ /**
>+ * @timer_last: Timestmap of the previous timer invocation.
>+ */
>+ ktime_t timer_last;
>+ /**
>+ * @timer_enabled: Should the internal sampling timer be running.
>+ */
>+ bool timer_enabled;
>+ /**
>+ * @event_sample: Store freq related counters.
>+ */
>+ u64 event_sample[XE_PMU_MAX_GT][XE_PMU_MAX_EVENT_TYPES];
> };
>
> #endif
>--
>2.38.1
>
More information about the Intel-xe
mailing list