Skip to content

Commit

Permalink
drm/i915/pmu: Connect engine busyness stats from GuC to pmu
Browse files Browse the repository at this point in the history
With GuC handling scheduling, i915 is not aware of the time that a
context is scheduled in and out of the engine. Since i915 pmu relies on
this info to provide engine busyness to the user, GuC shares this info
with i915 for all engines using shared memory. For each engine, this
info contains:

- total busyness: total time that the context was running (total)
- id: id of the running context (id)
- start timestamp: timestamp when the context started running (start)

At the time (now) of sampling the engine busyness, if the id is valid
(!= ~0), and start is non-zero, then the context is considered to be
active and the engine busyness is calculated using the below equation

	engine busyness = total + (now - start)

All times are obtained from the gt clock base. For inactive contexts,
engine busyness is just equal to the total.

The start and total values provided by GuC are 32 bits and wrap around
in a few minutes. Since perf pmu provides busyness as 64 bit
monotonically increasing values, there is a need for this implementation
to account for overflows and extend the time to 64 bits before returning
busyness to the user. In order to do that, a worker runs periodically at
frequency = 1/8th the time it takes for the timestamp to wrap. As an
example, that would be once in 27 seconds for a gt clock frequency of
19.2 MHz.

Note:
There might be an over-accounting of busyness due to the fact that GuC
may be updating the total and start values while kmd is reading them.
(i.e kmd may read the updated total and the stale start). In such a
case, user may see higher busyness value followed by smaller ones which
would eventually catch up to the higher value.

v2: (Tvrtko)
- Include details in commit message
- Move intel engine busyness function into execlist code
- Use union inside engine->stats
- Use natural type for ping delay jiffies
- Drop active_work condition checks
- Use for_each_engine if iterating all engines
- Drop seq locking, use spinlock at GuC level to update engine stats
- Document worker specific details

v3: (Tvrtko/Umesh)
- Demarcate GuC and execlist stat objects with comments
- Document known over-accounting issue in commit
- Provide a consistent view of GuC state
- Add hooks to gt park/unpark for GuC busyness
- Stop/start worker in gt park/unpark path
- Drop inline
- Move spinlock and worker inits to GuC initialization
- Drop helpers that are called only once

v4: (Tvrtko/Matt/Umesh)
- Drop addressed opens from commit message
- Get runtime pm in ping, remove from the park path
- Use cancel_delayed_work_sync in disable_submission path
- Update stats during reset prepare
- Skip ping if reset in progress
- Explicitly name execlists and GuC stats objects
- Since disable_submission is called from many places, move resetting
  stats to intel_guc_submission_reset_prepare

v5: (Tvrtko)
- Add a trylock helper that does not sleep and synchronize PMU event
  callbacks and worker with gt reset

v6: (CI BAT failures)
- DUTs using execlist submission failed to boot since __gt_unpark is
  called during i915 load. This ends up calling the GuC busyness unpark
  hook and results in kick-starting an uninitialized worker. Let
  park/unpark hooks check if GuC submission has been initialized.
- drop cant_sleep() from trylock helper since rcu_read_lock takes care
  of that.

v7: (CI) Fix igt@i915_selftest@live@gt_engines
- For GuC mode of submission the engine busyness is derived from gt time
  domain. Use gt time elapsed as reference in the selftest.
- Increase busyness calculation to 10ms duration to ensure batch runs
  longer and falls within the busyness tolerances in selftest.

v8:
- Use ktime_get in selftest as before
- intel_reset_trylock_no_wait results in a lockdep splat that is not
  trivial to fix since the PMU callback runs in irq context and the
  reset paths are tightly knit into the driver. The test that uncovers
  this is igt@perf_pmu@faulting-read. Drop intel_reset_trylock_no_wait,
  instead use the reset_count to synchronize with gt reset during pmu
  callback. For the ping, continue to use intel_reset_trylock since ping
  is not run in irq context.

- GuC PM timestamp does not tick when GuC is idle. This can potentially
  result in wrong busyness values when a context is active on the
  engine, but GuC is idle. Use the RING TIMESTAMP as GPU timestamp to
  process the GuC busyness stats. This works since both GuC timestamp and
  RING timestamp are synced with the same clock.

- The busyness stats may get updated after the batch starts running.
  This delay causes the busyness reported for 100us duration to fall
  below 95% in the selftest. The only option at this time is to wait for
  GuC busyness to change from idle to active before we sample busyness
  over a 100us period.

Signed-off-by: John Harrison <John.C.Harrison@Intel.com>
Signed-off-by: Umesh Nerlige Ramappa <umesh.nerlige.ramappa@intel.com>
Acked-by: Tvrtko Ursulin <tvrtko.ursulin@intel.com>
Reviewed-by: Matthew Brost <matthew.brost@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20211027004821.66097-2-umesh.nerlige.ramappa@intel.com
  • Loading branch information
Umesh Nerlige Ramappa authored and John Harrison committed Oct 28, 2021
1 parent 344e694 commit 77cdd05
Show file tree
Hide file tree
Showing 13 changed files with 453 additions and 28 deletions.
28 changes: 1 addition & 27 deletions drivers/gpu/drm/i915/gt/intel_engine_cs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1915,23 +1915,6 @@ void intel_engine_dump(struct intel_engine_cs *engine,
intel_engine_print_breadcrumbs(engine, m);
}

static ktime_t __intel_engine_get_busy_time(struct intel_engine_cs *engine,
ktime_t *now)
{
struct intel_engine_execlists_stats *stats = &engine->stats.execlists;
ktime_t total = stats->total;

/*
* If the engine is executing something at the moment
* add it to the total.
*/
*now = ktime_get();
if (READ_ONCE(stats->active))
total = ktime_add(total, ktime_sub(*now, stats->start));

return total;
}

/**
* intel_engine_get_busy_time() - Return current accumulated engine busyness
* @engine: engine to report on
Expand All @@ -1941,16 +1924,7 @@ static ktime_t __intel_engine_get_busy_time(struct intel_engine_cs *engine,
*/
ktime_t intel_engine_get_busy_time(struct intel_engine_cs *engine, ktime_t *now)
{
struct intel_engine_execlists_stats *stats = &engine->stats.execlists;
unsigned int seq;
ktime_t total;

do {
seq = read_seqcount_begin(&stats->lock);
total = __intel_engine_get_busy_time(engine, now);
} while (read_seqcount_retry(&stats->lock, seq));

return total;
return engine->busyness(engine, now);
}

struct intel_context *
Expand Down
33 changes: 32 additions & 1 deletion drivers/gpu/drm/i915/gt/intel_engine_types.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,28 @@ struct intel_engine_execlists_stats {
ktime_t start;
};

struct intel_engine_guc_stats {
/**
* @running: Active state of the engine when busyness was last sampled.
*/
bool running;

/**
* @prev_total: Previous value of total runtime clock cycles.
*/
u32 prev_total;

/**
* @total_gt_clks: Total gt clock cycles this engine was busy.
*/
u64 total_gt_clks;

/**
* @start_gt_clk: GT clock time of last idle to active transition.
*/
u64 start_gt_clk;
};

struct intel_engine_cs {
struct drm_i915_private *i915;
struct intel_gt *gt;
Expand Down Expand Up @@ -466,6 +488,12 @@ struct intel_engine_cs {
void (*add_active_request)(struct i915_request *rq);
void (*remove_active_request)(struct i915_request *rq);

/*
* Get engine busyness and the time at which the busyness was sampled.
*/
ktime_t (*busyness)(struct intel_engine_cs *engine,
ktime_t *now);

struct intel_engine_execlists execlists;

/*
Expand Down Expand Up @@ -515,7 +543,10 @@ struct intel_engine_cs {
u32 (*get_cmd_length_mask)(u32 cmd_header);

struct {
struct intel_engine_execlists_stats execlists;
union {
struct intel_engine_execlists_stats execlists;
struct intel_engine_guc_stats guc;
};

/**
* @rps: Utilisation at last RPS sampling.
Expand Down
34 changes: 34 additions & 0 deletions drivers/gpu/drm/i915/gt/intel_execlists_submission.c
Original file line number Diff line number Diff line change
Expand Up @@ -3293,6 +3293,38 @@ static void execlists_release(struct intel_engine_cs *engine)
lrc_fini_wa_ctx(engine);
}

static ktime_t __execlists_engine_busyness(struct intel_engine_cs *engine,
ktime_t *now)
{
struct intel_engine_execlists_stats *stats = &engine->stats.execlists;
ktime_t total = stats->total;

/*
* If the engine is executing something at the moment
* add it to the total.
*/
*now = ktime_get();
if (READ_ONCE(stats->active))
total = ktime_add(total, ktime_sub(*now, stats->start));

return total;
}

static ktime_t execlists_engine_busyness(struct intel_engine_cs *engine,
ktime_t *now)
{
struct intel_engine_execlists_stats *stats = &engine->stats.execlists;
unsigned int seq;
ktime_t total;

do {
seq = read_seqcount_begin(&stats->lock);
total = __execlists_engine_busyness(engine, now);
} while (read_seqcount_retry(&stats->lock, seq));

return total;
}

static void
logical_ring_default_vfuncs(struct intel_engine_cs *engine)
{
Expand Down Expand Up @@ -3349,6 +3381,8 @@ logical_ring_default_vfuncs(struct intel_engine_cs *engine)
engine->emit_bb_start = gen8_emit_bb_start;
else
engine->emit_bb_start = gen8_emit_bb_start_noarb;

engine->busyness = execlists_engine_busyness;
}

static void logical_ring_default_irqs(struct intel_engine_cs *engine)
Expand Down
2 changes: 2 additions & 0 deletions drivers/gpu/drm/i915/gt/intel_gt_pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ static int __gt_unpark(struct intel_wakeref *wf)
intel_rc6_unpark(&gt->rc6);
intel_rps_unpark(&gt->rps);
i915_pmu_gt_unparked(i915);
intel_guc_busyness_unpark(gt);

intel_gt_unpark_requests(gt);
runtime_begin(gt);
Expand All @@ -104,6 +105,7 @@ static int __gt_park(struct intel_wakeref *wf)
runtime_end(gt);
intel_gt_park_requests(gt);

intel_guc_busyness_park(gt);
i915_vma_parked(gt);
i915_pmu_gt_parked(i915);
intel_rps_park(&gt->rps);
Expand Down
33 changes: 33 additions & 0 deletions drivers/gpu/drm/i915/gt/selftest_engine_pm.c
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,31 @@ static int live_engine_timestamps(void *arg)
return 0;
}

static int __spin_until_busier(struct intel_engine_cs *engine, ktime_t busyness)
{
ktime_t start, unused, dt;

if (!intel_engine_uses_guc(engine))
return 0;

/*
* In GuC mode of submission, the busyness stats may get updated after
* the batch starts running. Poll for a change in busyness and timeout
* after 500 us.
*/
start = ktime_get();
while (intel_engine_get_busy_time(engine, &unused) == busyness) {
dt = ktime_get() - start;
if (dt > 500000) {
pr_err("active wait timed out %lld\n", dt);
ENGINE_TRACE(engine, "active wait time out %lld\n", dt);
return -ETIME;
}
}

return 0;
}

static int live_engine_busy_stats(void *arg)
{
struct intel_gt *gt = arg;
Expand All @@ -232,6 +257,7 @@ static int live_engine_busy_stats(void *arg)
GEM_BUG_ON(intel_gt_pm_is_awake(gt));
for_each_engine(engine, gt, id) {
struct i915_request *rq;
ktime_t busyness, dummy;
ktime_t de, dt;
ktime_t t[2];

Expand Down Expand Up @@ -274,12 +300,19 @@ static int live_engine_busy_stats(void *arg)
}
i915_request_add(rq);

busyness = intel_engine_get_busy_time(engine, &dummy);
if (!igt_wait_for_spinner(&spin, rq)) {
intel_gt_set_wedged(engine->gt);
err = -ETIME;
goto end;
}

err = __spin_until_busier(engine, busyness);
if (err) {
GEM_TRACE_DUMP();
goto end;
}

ENGINE_TRACE(engine, "measuring busy time\n");
preempt_disable();
de = intel_engine_get_busy_time(engine, &t[0]);
Expand Down
1 change: 1 addition & 0 deletions drivers/gpu/drm/i915/gt/uc/abi/guc_actions_abi.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ enum intel_guc_action {
INTEL_GUC_ACTION_DEREGISTER_CONTEXT_DONE = 0x4600,
INTEL_GUC_ACTION_REGISTER_CONTEXT_MULTI_LRC = 0x4601,
INTEL_GUC_ACTION_RESET_CLIENT = 0x5507,
INTEL_GUC_ACTION_SET_ENG_UTIL_BUFF = 0x550A,
INTEL_GUC_ACTION_LIMIT
};

Expand Down
30 changes: 30 additions & 0 deletions drivers/gpu/drm/i915/gt/uc/intel_guc.h
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ struct intel_guc {
u32 ads_regset_size;
/** @ads_golden_ctxt_size: size of the golden contexts in the ADS */
u32 ads_golden_ctxt_size;
/** @ads_engine_usage_size: size of engine usage in the ADS */
u32 ads_engine_usage_size;

/** @lrc_desc_pool: object allocated to hold the GuC LRC descriptor pool */
struct i915_vma *lrc_desc_pool;
Expand Down Expand Up @@ -172,6 +174,34 @@ struct intel_guc {

/** @send_mutex: used to serialize the intel_guc_send actions */
struct mutex send_mutex;

/**
* @timestamp: GT timestamp object that stores a copy of the timestamp
* and adjusts it for overflow using a worker.
*/
struct {
/**
* @lock: Lock protecting the below fields and the engine stats.
*/
spinlock_t lock;

/**
* @gt_stamp: 64 bit extended value of the GT timestamp.
*/
u64 gt_stamp;

/**
* @ping_delay: Period for polling the GT timestamp for
* overflow.
*/
unsigned long ping_delay;

/**
* @work: Periodic work to adjust GT timestamp, engine and
* context usage for overflows.
*/
struct delayed_work work;
} timestamp;
};

static inline struct intel_guc *log_to_guc(struct intel_guc_log *log)
Expand Down
21 changes: 21 additions & 0 deletions drivers/gpu/drm/i915/gt/uc/intel_guc_ads.c
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
* | guc_policies |
* +---------------------------------------+
* | guc_gt_system_info |
* +---------------------------------------+
* | guc_engine_usage |
* +---------------------------------------+ <== static
* | guc_mmio_reg[countA] (engine 0.0) |
* | guc_mmio_reg[countB] (engine 0.1) |
Expand All @@ -47,6 +49,7 @@ struct __guc_ads_blob {
struct guc_ads ads;
struct guc_policies policies;
struct guc_gt_system_info system_info;
struct guc_engine_usage engine_usage;
/* From here on, location is dynamic! Refer to above diagram. */
struct guc_mmio_reg regset[0];
} __packed;
Expand Down Expand Up @@ -628,3 +631,21 @@ void intel_guc_ads_reset(struct intel_guc *guc)

guc_ads_private_data_reset(guc);
}

u32 intel_guc_engine_usage_offset(struct intel_guc *guc)
{
struct __guc_ads_blob *blob = guc->ads_blob;
u32 base = intel_guc_ggtt_offset(guc, guc->ads_vma);
u32 offset = base + ptr_offset(blob, engine_usage);

return offset;
}

struct guc_engine_usage_record *intel_guc_engine_usage(struct intel_engine_cs *engine)
{
struct intel_guc *guc = &engine->gt->uc.guc;
struct __guc_ads_blob *blob = guc->ads_blob;
u8 guc_class = engine_class_to_guc_class(engine->class);

return &blob->engine_usage.engines[guc_class][ilog2(engine->logical_mask)];
}
5 changes: 5 additions & 0 deletions drivers/gpu/drm/i915/gt/uc/intel_guc_ads.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,19 @@
#ifndef _INTEL_GUC_ADS_H_
#define _INTEL_GUC_ADS_H_

#include <linux/types.h>

struct intel_guc;
struct drm_printer;
struct intel_engine_cs;

int intel_guc_ads_create(struct intel_guc *guc);
void intel_guc_ads_destroy(struct intel_guc *guc);
void intel_guc_ads_init_late(struct intel_guc *guc);
void intel_guc_ads_reset(struct intel_guc *guc);
void intel_guc_ads_print_policy_info(struct intel_guc *guc,
struct drm_printer *p);
struct guc_engine_usage_record *intel_guc_engine_usage(struct intel_engine_cs *engine);
u32 intel_guc_engine_usage_offset(struct intel_guc *guc);

#endif
13 changes: 13 additions & 0 deletions drivers/gpu/drm/i915/gt/uc/intel_guc_fwif.h
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,19 @@ struct guc_ads {
u32 reserved[15];
} __packed;

/* Engine usage stats */
struct guc_engine_usage_record {
u32 current_context_index;
u32 last_switch_in_stamp;
u32 reserved0;
u32 total_runtime;
u32 reserved1[4];
} __packed;

struct guc_engine_usage {
struct guc_engine_usage_record engines[GUC_MAX_ENGINE_CLASSES][GUC_MAX_INSTANCES_PER_CLASS];
} __packed;

/* GuC logging structures */

enum guc_log_buffer_type {
Expand Down
Loading

0 comments on commit 77cdd05

Please sign in to comment.