Quellcodebibliothek Statistik Leitseite products/sources/formale Sprachen/C/Linux/drivers/gpu/drm/i915/gt/uc/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 166 kB image not shown  

Quelle  intel_guc_submission.c   Sprache: C

 
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2014 Intel Corporation
 */


#include <linux/circ_buf.h>

#include "gem/i915_gem_context.h"
#include "gem/i915_gem_lmem.h"
#include "gt/gen8_engine_cs.h"
#include "gt/intel_breadcrumbs.h"
#include "gt/intel_context.h"
#include "gt/intel_engine_heartbeat.h"
#include "gt/intel_engine_pm.h"
#include "gt/intel_engine_regs.h"
#include "gt/intel_gpu_commands.h"
#include "gt/intel_gt.h"
#include "gt/intel_gt_clock_utils.h"
#include "gt/intel_gt_irq.h"
#include "gt/intel_gt_pm.h"
#include "gt/intel_gt_regs.h"
#include "gt/intel_gt_requests.h"
#include "gt/intel_lrc.h"
#include "gt/intel_lrc_reg.h"
#include "gt/intel_mocs.h"
#include "gt/intel_ring.h"

#include "intel_guc_ads.h"
#include "intel_guc_capture.h"
#include "intel_guc_print.h"
#include "intel_guc_submission.h"

#include "i915_drv.h"
#include "i915_reg.h"
#include "i915_irq.h"
#include "i915_trace.h"

/**
 * DOC: GuC-based command submission
 *
 * The Scratch registers:
 * There are 16 MMIO-based registers start from 0xC180. The kernel driver writes
 * a value to the action register (SOFT_SCRATCH_0) along with any data. It then
 * triggers an interrupt on the GuC via another register write (0xC4C8).
 * Firmware writes a success/fail code back to the action register after
 * processes the request. The kernel driver polls waiting for this update and
 * then proceeds.
 *
 * Command Transport buffers (CTBs):
 * Covered in detail in other sections but CTBs (Host to GuC - H2G, GuC to Host
 * - G2H) are a message interface between the i915 and GuC.
 *
 * Context registration:
 * Before a context can be submitted it must be registered with the GuC via a
 * H2G. A unique guc_id is associated with each context. The context is either
 * registered at request creation time (normal operation) or at submission time
 * (abnormal operation, e.g. after a reset).
 *
 * Context submission:
 * The i915 updates the LRC tail value in memory. The i915 must enable the
 * scheduling of the context within the GuC for the GuC to actually consider it.
 * Therefore, the first time a disabled context is submitted we use a schedule
 * enable H2G, while follow up submissions are done via the context submit H2G,
 * which informs the GuC that a previously enabled context has new work
 * available.
 *
 * Context unpin:
 * To unpin a context a H2G is used to disable scheduling. When the
 * corresponding G2H returns indicating the scheduling disable operation has
 * completed it is safe to unpin the context. While a disable is in flight it
 * isn't safe to resubmit the context so a fence is used to stall all future
 * requests of that context until the G2H is returned. Because this interaction
 * with the GuC takes a non-zero amount of time we delay the disabling of
 * scheduling after the pin count goes to zero by a configurable period of time
 * (see SCHED_DISABLE_DELAY_MS). The thought is this gives the user a window of
 * time to resubmit something on the context before doing this costly operation.
 * This delay is only done if the context isn't closed and the guc_id usage is
 * less than a threshold (see NUM_SCHED_DISABLE_GUC_IDS_THRESHOLD).
 *
 * Context deregistration:
 * Before a context can be destroyed or if we steal its guc_id we must
 * deregister the context with the GuC via H2G. If stealing the guc_id it isn't
 * safe to submit anything to this guc_id until the deregister completes so a
 * fence is used to stall all requests associated with this guc_id until the
 * corresponding G2H returns indicating the guc_id has been deregistered.
 *
 * submission_state.guc_ids:
 * Unique number associated with private GuC context data passed in during
 * context registration / submission / deregistration. 64k available. Simple ida
 * is used for allocation.
 *
 * Stealing guc_ids:
 * If no guc_ids are available they can be stolen from another context at
 * request creation time if that context is unpinned. If a guc_id can't be found
 * we punt this problem to the user as we believe this is near impossible to hit
 * during normal use cases.
 *
 * Locking:
 * In the GuC submission code we have 3 basic spin locks which protect
 * everything. Details about each below.
 *
 * sched_engine->lock
 * This is the submission lock for all contexts that share an i915 schedule
 * engine (sched_engine), thus only one of the contexts which share a
 * sched_engine can be submitting at a time. Currently only one sched_engine is
 * used for all of GuC submission but that could change in the future.
 *
 * guc->submission_state.lock
 * Global lock for GuC submission state. Protects guc_ids and destroyed contexts
 * list.
 *
 * ce->guc_state.lock
 * Protects everything under ce->guc_state. Ensures that a context is in the
 * correct state before issuing a H2G. e.g. We don't issue a schedule disable
 * on a disabled context (bad idea), we don't issue a schedule enable when a
 * schedule disable is in flight, etc... Also protects list of inflight requests
 * on the context and the priority management state. Lock is individual to each
 * context.
 *
 * Lock ordering rules:
 * sched_engine->lock -> ce->guc_state.lock
 * guc->submission_state.lock -> ce->guc_state.lock
 *
 * Reset races:
 * When a full GT reset is triggered it is assumed that some G2H responses to
 * H2Gs can be lost as the GuC is also reset. Losing these G2H can prove to be
 * fatal as we do certain operations upon receiving a G2H (e.g. destroy
 * contexts, release guc_ids, etc...). When this occurs we can scrub the
 * context state and cleanup appropriately, however this is quite racey.
 * To avoid races, the reset code must disable submission before scrubbing for
 * the missing G2H, while the submission code must check for submission being
 * disabled and skip sending H2Gs and updating context states when it is. Both
 * sides must also make sure to hold the relevant locks.
 */


/* GuC Virtual Engine */
struct guc_virtual_engine {
 struct intel_engine_cs base;
 struct intel_context context;
};

static struct intel_context *
guc_create_virtual(struct intel_engine_cs **siblings, unsigned int count,
     unsigned long flags);

static struct intel_context *
guc_create_parallel(struct intel_engine_cs **engines,
      unsigned int num_siblings,
      unsigned int width);

#define GUC_REQUEST_SIZE 64 /* bytes */

/*
 * We reserve 1/16 of the guc_ids for multi-lrc as these need to be contiguous
 * per the GuC submission interface. A different allocation algorithm is used
 * (bitmap vs. ida) between multi-lrc and single-lrc hence the reason to
 * partition the guc_id space. We believe the number of multi-lrc contexts in
 * use should be low and 1/16 should be sufficient. Minimum of 32 guc_ids for
 * multi-lrc.
 */

#define NUMBER_MULTI_LRC_GUC_ID(guc) \
 ((guc)->submission_state.num_guc_ids / 16)

/*
 * Below is a set of functions which control the GuC scheduling state which
 * require a lock.
 */

#define SCHED_STATE_WAIT_FOR_DEREGISTER_TO_REGISTER BIT(0)
#define SCHED_STATE_DESTROYED    BIT(1)
#define SCHED_STATE_PENDING_DISABLE   BIT(2)
#define SCHED_STATE_BANNED    BIT(3)
#define SCHED_STATE_ENABLED    BIT(4)
#define SCHED_STATE_PENDING_ENABLE   BIT(5)
#define SCHED_STATE_REGISTERED    BIT(6)
#define SCHED_STATE_POLICY_REQUIRED   BIT(7)
#define SCHED_STATE_CLOSED    BIT(8)
#define SCHED_STATE_BLOCKED_SHIFT   9
#define SCHED_STATE_BLOCKED  BIT(SCHED_STATE_BLOCKED_SHIFT)
#define SCHED_STATE_BLOCKED_MASK (0xfff << SCHED_STATE_BLOCKED_SHIFT)

static inline void init_sched_state(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= SCHED_STATE_BLOCKED_MASK;
}

/*
 * Kernel contexts can have SCHED_STATE_REGISTERED after suspend.
 * A context close can race with the submission path, so SCHED_STATE_CLOSED
 * can be set immediately before we try to register.
 */

#define SCHED_STATE_VALID_INIT \
 (SCHED_STATE_BLOCKED_MASK | \
  SCHED_STATE_CLOSED | \
  SCHED_STATE_REGISTERED)

__maybe_unused
static bool sched_state_is_init(struct intel_context *ce)
{
 return !(ce->guc_state.sched_state & ~SCHED_STATE_VALID_INIT);
}

static inline bool
context_wait_for_deregister_to_register(struct intel_context *ce)
{
 return ce->guc_state.sched_state &
  SCHED_STATE_WAIT_FOR_DEREGISTER_TO_REGISTER;
}

static inline void
set_context_wait_for_deregister_to_register(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |=
  SCHED_STATE_WAIT_FOR_DEREGISTER_TO_REGISTER;
}

static inline void
clr_context_wait_for_deregister_to_register(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &=
  ~SCHED_STATE_WAIT_FOR_DEREGISTER_TO_REGISTER;
}

static inline bool
context_destroyed(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_DESTROYED;
}

static inline void
set_context_destroyed(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_DESTROYED;
}

static inline void
clr_context_destroyed(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= ~SCHED_STATE_DESTROYED;
}

static inline bool context_pending_disable(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_PENDING_DISABLE;
}

static inline void set_context_pending_disable(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_PENDING_DISABLE;
}

static inline void clr_context_pending_disable(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= ~SCHED_STATE_PENDING_DISABLE;
}

static inline bool context_banned(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_BANNED;
}

static inline void set_context_banned(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_BANNED;
}

static inline void clr_context_banned(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= ~SCHED_STATE_BANNED;
}

static inline bool context_enabled(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_ENABLED;
}

static inline void set_context_enabled(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_ENABLED;
}

static inline void clr_context_enabled(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= ~SCHED_STATE_ENABLED;
}

static inline bool context_pending_enable(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_PENDING_ENABLE;
}

static inline void set_context_pending_enable(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_PENDING_ENABLE;
}

static inline void clr_context_pending_enable(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= ~SCHED_STATE_PENDING_ENABLE;
}

static inline bool context_registered(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_REGISTERED;
}

static inline void set_context_registered(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_REGISTERED;
}

static inline void clr_context_registered(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= ~SCHED_STATE_REGISTERED;
}

static inline bool context_policy_required(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_POLICY_REQUIRED;
}

static inline void set_context_policy_required(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_POLICY_REQUIRED;
}

static inline void clr_context_policy_required(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state &= ~SCHED_STATE_POLICY_REQUIRED;
}

static inline bool context_close_done(struct intel_context *ce)
{
 return ce->guc_state.sched_state & SCHED_STATE_CLOSED;
}

static inline void set_context_close_done(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);
 ce->guc_state.sched_state |= SCHED_STATE_CLOSED;
}

static inline u32 context_blocked(struct intel_context *ce)
{
 return (ce->guc_state.sched_state & SCHED_STATE_BLOCKED_MASK) >>
  SCHED_STATE_BLOCKED_SHIFT;
}

static inline void incr_context_blocked(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);

 ce->guc_state.sched_state += SCHED_STATE_BLOCKED;

 GEM_BUG_ON(!context_blocked(ce)); /* Overflow check */
}

static inline void decr_context_blocked(struct intel_context *ce)
{
 lockdep_assert_held(&ce->guc_state.lock);

 GEM_BUG_ON(!context_blocked(ce)); /* Underflow check */

 ce->guc_state.sched_state -= SCHED_STATE_BLOCKED;
}

static struct intel_context *
request_to_scheduling_context(struct i915_request *rq)
{
 return intel_context_to_parent(rq->context);
}

static inline bool context_guc_id_invalid(struct intel_context *ce)
{
 return ce->guc_id.id == GUC_INVALID_CONTEXT_ID;
}

static inline void set_context_guc_id_invalid(struct intel_context *ce)
{
 ce->guc_id.id = GUC_INVALID_CONTEXT_ID;
}

static inline struct intel_guc *ce_to_guc(struct intel_context *ce)
{
 return gt_to_guc(ce->engine->gt);
}

static inline struct i915_priolist *to_priolist(struct rb_node *rb)
{
 return rb_entry(rb, struct i915_priolist, node);
}

/*
 * When using multi-lrc submission a scratch memory area is reserved in the
 * parent's context state for the process descriptor, work queue, and handshake
 * between the parent + children contexts to insert safe preemption points
 * between each of the BBs. Currently the scratch area is sized to a page.
 *
 * The layout of this scratch area is below:
 * 0 guc_process_desc
 * + sizeof(struct guc_process_desc) child go
 * + CACHELINE_BYTES child join[0]
 * ...
 * + CACHELINE_BYTES child join[n - 1]
 * ... unused
 * PARENT_SCRATCH_SIZE / 2 work queue start
 * ... work queue
 * PARENT_SCRATCH_SIZE - 1 work queue end
 */

#define WQ_SIZE   (PARENT_SCRATCH_SIZE / 2)
#define WQ_OFFSET  (PARENT_SCRATCH_SIZE - WQ_SIZE)

struct sync_semaphore {
 u32 semaphore;
 u8 unused[CACHELINE_BYTES - sizeof(u32)];
};

struct parent_scratch {
 union guc_descs {
  struct guc_sched_wq_desc wq_desc;
  struct guc_process_desc_v69 pdesc;
 } descs;

 struct sync_semaphore go;
 struct sync_semaphore join[MAX_ENGINE_INSTANCE + 1];

 u8 unused[WQ_OFFSET - sizeof(union guc_descs) -
  sizeof(struct sync_semaphore) * (MAX_ENGINE_INSTANCE + 2)];

 u32 wq[WQ_SIZE / sizeof(u32)];
};

static u32 __get_parent_scratch_offset(struct intel_context *ce)
{
 GEM_BUG_ON(!ce->parallel.guc.parent_page);

 return ce->parallel.guc.parent_page * PAGE_SIZE;
}

static u32 __get_wq_offset(struct intel_context *ce)
{
 BUILD_BUG_ON(offsetof(struct parent_scratch, wq) != WQ_OFFSET);

 return __get_parent_scratch_offset(ce) + WQ_OFFSET;
}

static struct parent_scratch *
__get_parent_scratch(struct intel_context *ce)
{
 BUILD_BUG_ON(sizeof(struct parent_scratch) != PARENT_SCRATCH_SIZE);
 BUILD_BUG_ON(sizeof(struct sync_semaphore) != CACHELINE_BYTES);

 /*
 * Need to subtract LRC_STATE_OFFSET here as the
 * parallel.guc.parent_page is the offset into ce->state while
 * ce->lrc_reg_reg is ce->state + LRC_STATE_OFFSET.
 */

 return (struct parent_scratch *)
  (ce->lrc_reg_state +
   ((__get_parent_scratch_offset(ce) -
     LRC_STATE_OFFSET) / sizeof(u32)));
}

static struct guc_process_desc_v69 *
__get_process_desc_v69(struct intel_context *ce)
{
 struct parent_scratch *ps = __get_parent_scratch(ce);

 return &ps->descs.pdesc;
}

static struct guc_sched_wq_desc *
__get_wq_desc_v70(struct intel_context *ce)
{
 struct parent_scratch *ps = __get_parent_scratch(ce);

 return &ps->descs.wq_desc;
}

static u32 *get_wq_pointer(struct intel_context *ce, u32 wqi_size)
{
 /*
 * Check for space in work queue. Caching a value of head pointer in
 * intel_context structure in order reduce the number accesses to shared
 * GPU memory which may be across a PCIe bus.
 */

#define AVAILABLE_SPACE \
 CIRC_SPACE(ce->parallel.guc.wqi_tail, ce->parallel.guc.wqi_head, WQ_SIZE)
 if (wqi_size > AVAILABLE_SPACE) {
  ce->parallel.guc.wqi_head = READ_ONCE(*ce->parallel.guc.wq_head);

  if (wqi_size > AVAILABLE_SPACE)
   return NULL;
 }
#undef AVAILABLE_SPACE

 return &__get_parent_scratch(ce)->wq[ce->parallel.guc.wqi_tail / sizeof(u32)];
}

static inline struct intel_context *__get_context(struct intel_guc *guc, u32 id)
{
 struct intel_context *ce = xa_load(&guc->context_lookup, id);

 GEM_BUG_ON(id >= GUC_MAX_CONTEXT_ID);

 return ce;
}

static struct guc_lrc_desc_v69 *__get_lrc_desc_v69(struct intel_guc *guc, u32 index)
{
 struct guc_lrc_desc_v69 *base = guc->lrc_desc_pool_vaddr_v69;

 if (!base)
  return NULL;

 GEM_BUG_ON(index >= GUC_MAX_CONTEXT_ID);

 return &base[index];
}

static int guc_lrc_desc_pool_create_v69(struct intel_guc *guc)
{
 u32 size;
 int ret;

 size = PAGE_ALIGN(sizeof(struct guc_lrc_desc_v69) *
     GUC_MAX_CONTEXT_ID);
 ret = intel_guc_allocate_and_map_vma(guc, size, &guc->lrc_desc_pool_v69,
          (void **)&guc->lrc_desc_pool_vaddr_v69);
 if (ret)
  return ret;

 return 0;
}

static void guc_lrc_desc_pool_destroy_v69(struct intel_guc *guc)
{
 if (!guc->lrc_desc_pool_vaddr_v69)
  return;

 guc->lrc_desc_pool_vaddr_v69 = NULL;
 i915_vma_unpin_and_release(&guc->lrc_desc_pool_v69, I915_VMA_RELEASE_MAP);
}

static inline bool guc_submission_initialized(struct intel_guc *guc)
{
 return guc->submission_initialized;
}

static inline void _reset_lrc_desc_v69(struct intel_guc *guc, u32 id)
{
 struct guc_lrc_desc_v69 *desc = __get_lrc_desc_v69(guc, id);

 if (desc)
  memset(desc, 0, sizeof(*desc));
}

static inline bool ctx_id_mapped(struct intel_guc *guc, u32 id)
{
 return __get_context(guc, id);
}

static inline void set_ctx_id_mapping(struct intel_guc *guc, u32 id,
          struct intel_context *ce)
{
 unsigned long flags;

 /*
 * xarray API doesn't have xa_save_irqsave wrapper, so calling the
 * lower level functions directly.
 */

 xa_lock_irqsave(&guc->context_lookup, flags);
 __xa_store(&guc->context_lookup, id, ce, GFP_ATOMIC);
 xa_unlock_irqrestore(&guc->context_lookup, flags);
}

static inline void clr_ctx_id_mapping(struct intel_guc *guc, u32 id)
{
 unsigned long flags;

 if (unlikely(!guc_submission_initialized(guc)))
  return;

 _reset_lrc_desc_v69(guc, id);

 /*
 * xarray API doesn't have xa_erase_irqsave wrapper, so calling
 * the lower level functions directly.
 */

 xa_lock_irqsave(&guc->context_lookup, flags);
 __xa_erase(&guc->context_lookup, id);
 xa_unlock_irqrestore(&guc->context_lookup, flags);
}

static void decr_outstanding_submission_g2h(struct intel_guc *guc)
{
 if (atomic_dec_and_test(&guc->outstanding_submission_g2h))
  wake_up_all(&guc->ct.wq);
}

static int guc_submission_send_busy_loop(struct intel_guc *guc,
      const u32 *action,
      u32 len,
      u32 g2h_len_dw,
      bool loop)
{
 int ret;

 /*
 * We always loop when a send requires a reply (i.e. g2h_len_dw > 0),
 * so we don't handle the case where we don't get a reply because we
 * aborted the send due to the channel being busy.
 */

 GEM_BUG_ON(g2h_len_dw && !loop);

 if (g2h_len_dw)
  atomic_inc(&guc->outstanding_submission_g2h);

 ret = intel_guc_send_busy_loop(guc, action, len, g2h_len_dw, loop);
 if (ret && g2h_len_dw)
  atomic_dec(&guc->outstanding_submission_g2h);

 return ret;
}

int intel_guc_wait_for_pending_msg(struct intel_guc *guc,
       atomic_t *wait_var,
       bool interruptible,
       long timeout)
{
 const int state = interruptible ?
  TASK_INTERRUPTIBLE : TASK_UNINTERRUPTIBLE;
 DEFINE_WAIT(wait);

 might_sleep();
 GEM_BUG_ON(timeout < 0);

 if (!atomic_read(wait_var))
  return 0;

 if (!timeout)
  return -ETIME;

 for (;;) {
  prepare_to_wait(&guc->ct.wq, &wait, state);

  if (!atomic_read(wait_var))
   break;

  if (signal_pending_state(state, current)) {
   timeout = -EINTR;
   break;
  }

  if (!timeout) {
   timeout = -ETIME;
   break;
  }

  timeout = io_schedule_timeout(timeout);
 }
 finish_wait(&guc->ct.wq, &wait);

 return (timeout < 0) ? timeout : 0;
}

int intel_guc_wait_for_idle(struct intel_guc *guc, long timeout)
{
 if (!intel_uc_uses_guc_submission(&guc_to_gt(guc)->uc))
  return 0;

 return intel_guc_wait_for_pending_msg(guc,
           &guc->outstanding_submission_g2h,
           true, timeout);
}

static int guc_context_policy_init_v70(struct intel_context *ce, bool loop);
static int try_context_registration(struct intel_context *ce, bool loop);

static int __guc_add_request(struct intel_guc *guc, struct i915_request *rq)
{
 int err = 0;
 struct intel_context *ce = request_to_scheduling_context(rq);
 u32 action[3];
 int len = 0;
 u32 g2h_len_dw = 0;
 bool enabled;

 lockdep_assert_held(&rq->engine->sched_engine->lock);

 /*
 * Corner case where requests were sitting in the priority list or a
 * request resubmitted after the context was banned.
 */

 if (unlikely(!intel_context_is_schedulable(ce))) {
  i915_request_put(i915_request_mark_eio(rq));
  intel_engine_signal_breadcrumbs(ce->engine);
  return 0;
 }

 GEM_BUG_ON(!atomic_read(&ce->guc_id.ref));
 GEM_BUG_ON(context_guc_id_invalid(ce));

 if (context_policy_required(ce)) {
  err = guc_context_policy_init_v70(ce, false);
  if (err)
   return err;
 }

 spin_lock(&ce->guc_state.lock);

 /*
 * The request / context will be run on the hardware when scheduling
 * gets enabled in the unblock. For multi-lrc we still submit the
 * context to move the LRC tails.
 */

 if (unlikely(context_blocked(ce) && !intel_context_is_parent(ce)))
  goto out;

 enabled = context_enabled(ce) || context_blocked(ce);

 if (!enabled) {
  action[len++] = INTEL_GUC_ACTION_SCHED_CONTEXT_MODE_SET;
  action[len++] = ce->guc_id.id;
  action[len++] = GUC_CONTEXT_ENABLE;
  set_context_pending_enable(ce);
  intel_context_get(ce);
  g2h_len_dw = G2H_LEN_DW_SCHED_CONTEXT_MODE_SET;
 } else {
  action[len++] = INTEL_GUC_ACTION_SCHED_CONTEXT;
  action[len++] = ce->guc_id.id;
 }

 err = intel_guc_send_nb(guc, action, len, g2h_len_dw);
 if (!enabled && !err) {
  trace_intel_context_sched_enable(ce);
  atomic_inc(&guc->outstanding_submission_g2h);
  set_context_enabled(ce);

  /*
 * Without multi-lrc KMD does the submission step (moving the
 * lrc tail) so enabling scheduling is sufficient to submit the
 * context. This isn't the case in multi-lrc submission as the
 * GuC needs to move the tails, hence the need for another H2G
 * to submit a multi-lrc context after enabling scheduling.
 */

  if (intel_context_is_parent(ce)) {
   action[0] = INTEL_GUC_ACTION_SCHED_CONTEXT;
   err = intel_guc_send_nb(guc, action, len - 1, 0);
  }
 } else if (!enabled) {
  clr_context_pending_enable(ce);
  intel_context_put(ce);
 }
 if (likely(!err))
  trace_i915_request_guc_submit(rq);

out:
 spin_unlock(&ce->guc_state.lock);
 return err;
}

static int guc_add_request(struct intel_guc *guc, struct i915_request *rq)
{
 int ret = __guc_add_request(guc, rq);

 if (unlikely(ret == -EBUSY)) {
  guc->stalled_request = rq;
  guc->submission_stall_reason = STALL_ADD_REQUEST;
 }

 return ret;
}

static inline void guc_set_lrc_tail(struct i915_request *rq)
{
 rq->context->lrc_reg_state[CTX_RING_TAIL] =
  intel_ring_set_tail(rq->ring, rq->tail);
}

static inline int rq_prio(const struct i915_request *rq)
{
 return rq->sched.attr.priority;
}

static bool is_multi_lrc_rq(struct i915_request *rq)
{
 return intel_context_is_parallel(rq->context);
}

static bool can_merge_rq(struct i915_request *rq,
    struct i915_request *last)
{
 return request_to_scheduling_context(rq) ==
  request_to_scheduling_context(last);
}

static u32 wq_space_until_wrap(struct intel_context *ce)
{
 return (WQ_SIZE - ce->parallel.guc.wqi_tail);
}

static void write_wqi(struct intel_context *ce, u32 wqi_size)
{
 BUILD_BUG_ON(!is_power_of_2(WQ_SIZE));

 /*
 * Ensure WQI are visible before updating tail
 */

 intel_guc_write_barrier(ce_to_guc(ce));

 ce->parallel.guc.wqi_tail = (ce->parallel.guc.wqi_tail + wqi_size) &
  (WQ_SIZE - 1);
 WRITE_ONCE(*ce->parallel.guc.wq_tail, ce->parallel.guc.wqi_tail);
}

static int guc_wq_noop_append(struct intel_context *ce)
{
 u32 *wqi = get_wq_pointer(ce, wq_space_until_wrap(ce));
 u32 len_dw = wq_space_until_wrap(ce) / sizeof(u32) - 1;

 if (!wqi)
  return -EBUSY;

 GEM_BUG_ON(!FIELD_FIT(WQ_LEN_MASK, len_dw));

 *wqi = FIELD_PREP(WQ_TYPE_MASK, WQ_TYPE_NOOP) |
  FIELD_PREP(WQ_LEN_MASK, len_dw);
 ce->parallel.guc.wqi_tail = 0;

 return 0;
}

static int __guc_wq_item_append(struct i915_request *rq)
{
 struct intel_context *ce = request_to_scheduling_context(rq);
 struct intel_context *child;
 unsigned int wqi_size = (ce->parallel.number_children + 4) *
  sizeof(u32);
 u32 *wqi;
 u32 len_dw = (wqi_size / sizeof(u32)) - 1;
 int ret;

 /* Ensure context is in correct state updating work queue */
 GEM_BUG_ON(!atomic_read(&ce->guc_id.ref));
 GEM_BUG_ON(context_guc_id_invalid(ce));
 GEM_BUG_ON(context_wait_for_deregister_to_register(ce));
 GEM_BUG_ON(!ctx_id_mapped(ce_to_guc(ce), ce->guc_id.id));

 /* Insert NOOP if this work queue item will wrap the tail pointer. */
 if (wqi_size > wq_space_until_wrap(ce)) {
  ret = guc_wq_noop_append(ce);
  if (ret)
   return ret;
 }

 wqi = get_wq_pointer(ce, wqi_size);
 if (!wqi)
  return -EBUSY;

 GEM_BUG_ON(!FIELD_FIT(WQ_LEN_MASK, len_dw));

 *wqi++ = FIELD_PREP(WQ_TYPE_MASK, WQ_TYPE_MULTI_LRC) |
  FIELD_PREP(WQ_LEN_MASK, len_dw);
 *wqi++ = ce->lrc.lrca;
 *wqi++ = FIELD_PREP(WQ_GUC_ID_MASK, ce->guc_id.id) |
        FIELD_PREP(WQ_RING_TAIL_MASK, ce->ring->tail / sizeof(u64));
 *wqi++ = 0; /* fence_id */
 for_each_child(ce, child)
  *wqi++ = child->ring->tail / sizeof(u64);

 write_wqi(ce, wqi_size);

 return 0;
}

static int guc_wq_item_append(struct intel_guc *guc,
         struct i915_request *rq)
{
 struct intel_context *ce = request_to_scheduling_context(rq);
 int ret;

 if (unlikely(!intel_context_is_schedulable(ce)))
  return 0;

 ret = __guc_wq_item_append(rq);
 if (unlikely(ret == -EBUSY)) {
  guc->stalled_request = rq;
  guc->submission_stall_reason = STALL_MOVE_LRC_TAIL;
 }

 return ret;
}

static bool multi_lrc_submit(struct i915_request *rq)
{
 struct intel_context *ce = request_to_scheduling_context(rq);

 intel_ring_set_tail(rq->ring, rq->tail);

 /*
 * We expect the front end (execbuf IOCTL) to set this flag on the last
 * request generated from a multi-BB submission. This indicates to the
 * backend (GuC interface) that we should submit this context thus
 * submitting all the requests generated in parallel.
 */

 return test_bit(I915_FENCE_FLAG_SUBMIT_PARALLEL, &rq->fence.flags) ||
        !intel_context_is_schedulable(ce);
}

static int guc_dequeue_one_context(struct intel_guc *guc)
{
 struct i915_sched_engine * const sched_engine = guc->sched_engine;
 struct i915_request *last = NULL;
 bool submit = false;
 struct rb_node *rb;
 int ret;

 lockdep_assert_held(&sched_engine->lock);

 if (guc->stalled_request) {
  submit = true;
  last = guc->stalled_request;

  switch (guc->submission_stall_reason) {
  case STALL_REGISTER_CONTEXT:
   goto register_context;
  case STALL_MOVE_LRC_TAIL:
   goto move_lrc_tail;
  case STALL_ADD_REQUEST:
   goto add_request;
  default:
   MISSING_CASE(guc->submission_stall_reason);
  }
 }

 while ((rb = rb_first_cached(&sched_engine->queue))) {
  struct i915_priolist *p = to_priolist(rb);
  struct i915_request *rq, *rn;

  priolist_for_each_request_consume(rq, rn, p) {
   if (last && !can_merge_rq(rq, last))
    goto register_context;

   list_del_init(&rq->sched.link);

   __i915_request_submit(rq);

   trace_i915_request_in(rq, 0);
   last = rq;

   if (is_multi_lrc_rq(rq)) {
    /*
 * We need to coalesce all multi-lrc requests in
 * a relationship into a single H2G. We are
 * guaranteed that all of these requests will be
 * submitted sequentially.
 */

    if (multi_lrc_submit(rq)) {
     submit = true;
     goto register_context;
    }
   } else {
    submit = true;
   }
  }

  rb_erase_cached(&p->node, &sched_engine->queue);
  i915_priolist_free(p);
 }

register_context:
 if (submit) {
  struct intel_context *ce = request_to_scheduling_context(last);

  if (unlikely(!ctx_id_mapped(guc, ce->guc_id.id) &&
        intel_context_is_schedulable(ce))) {
   ret = try_context_registration(ce, false);
   if (unlikely(ret == -EPIPE)) {
    goto deadlk;
   } else if (ret == -EBUSY) {
    guc->stalled_request = last;
    guc->submission_stall_reason =
     STALL_REGISTER_CONTEXT;
    goto schedule_tasklet;
   } else if (ret != 0) {
    GEM_WARN_ON(ret); /* Unexpected */
    goto deadlk;
   }
  }

move_lrc_tail:
  if (is_multi_lrc_rq(last)) {
   ret = guc_wq_item_append(guc, last);
   if (ret == -EBUSY) {
    goto schedule_tasklet;
   } else if (ret != 0) {
    GEM_WARN_ON(ret); /* Unexpected */
    goto deadlk;
   }
  } else {
   guc_set_lrc_tail(last);
  }

add_request:
  ret = guc_add_request(guc, last);
  if (unlikely(ret == -EPIPE)) {
   goto deadlk;
  } else if (ret == -EBUSY) {
   goto schedule_tasklet;
  } else if (ret != 0) {
   GEM_WARN_ON(ret); /* Unexpected */
   goto deadlk;
  }
 }

 guc->stalled_request = NULL;
 guc->submission_stall_reason = STALL_NONE;
 return submit;

deadlk:
 sched_engine->tasklet.callback = NULL;
 tasklet_disable_nosync(&sched_engine->tasklet);
 return false;

schedule_tasklet:
 tasklet_schedule(&sched_engine->tasklet);
 return false;
}

static void guc_submission_tasklet(struct tasklet_struct *t)
{
 struct i915_sched_engine *sched_engine =
  from_tasklet(sched_engine, t, tasklet);
 unsigned long flags;
 bool loop;

 spin_lock_irqsave(&sched_engine->lock, flags);

 do {
  loop = guc_dequeue_one_context(sched_engine->private_data);
 } while (loop);

 i915_sched_engine_reset_on_empty(sched_engine);

 spin_unlock_irqrestore(&sched_engine->lock, flags);
}

static void cs_irq_handler(struct intel_engine_cs *engine, u16 iir)
{
 if (iir & GT_RENDER_USER_INTERRUPT)
  intel_engine_signal_breadcrumbs(engine);
}

static void __guc_context_destroy(struct intel_context *ce);
static void release_guc_id(struct intel_guc *guc, struct intel_context *ce);
static void guc_signal_context_fence(struct intel_context *ce);
static void guc_cancel_context_requests(struct intel_context *ce);
static void guc_blocked_fence_complete(struct intel_context *ce);

static void scrub_guc_desc_for_outstanding_g2h(struct intel_guc *guc)
{
 struct intel_context *ce;
 unsigned long index, flags;
 bool pending_disable, pending_enable, deregister, destroyed, banned;

 xa_lock_irqsave(&guc->context_lookup, flags);
 xa_for_each(&guc->context_lookup, index, ce) {
  /*
 * Corner case where the ref count on the object is zero but and
 * deregister G2H was lost. In this case we don't touch the ref
 * count and finish the destroy of the context.
 */

  bool do_put = kref_get_unless_zero(&ce->ref);

  xa_unlock(&guc->context_lookup);

  if (test_bit(CONTEXT_GUC_INIT, &ce->flags) &&
      (cancel_delayed_work(&ce->guc_state.sched_disable_delay_work))) {
   /* successful cancel so jump straight to close it */
   intel_context_sched_disable_unpin(ce);
  }

  spin_lock(&ce->guc_state.lock);

  /*
 * Once we are at this point submission_disabled() is guaranteed
 * to be visible to all callers who set the below flags (see above
 * flush and flushes in reset_prepare). If submission_disabled()
 * is set, the caller shouldn't set these flags.
 */


  destroyed = context_destroyed(ce);
  pending_enable = context_pending_enable(ce);
  pending_disable = context_pending_disable(ce);
  deregister = context_wait_for_deregister_to_register(ce);
  banned = context_banned(ce);
  init_sched_state(ce);

  spin_unlock(&ce->guc_state.lock);

  if (pending_enable || destroyed || deregister) {
   decr_outstanding_submission_g2h(guc);
   if (deregister)
    guc_signal_context_fence(ce);
   if (destroyed) {
    intel_gt_pm_put_async_untracked(guc_to_gt(guc));
    release_guc_id(guc, ce);
    __guc_context_destroy(ce);
   }
   if (pending_enable || deregister)
    intel_context_put(ce);
  }

  /* Not mutualy exclusive with above if statement. */
  if (pending_disable) {
   guc_signal_context_fence(ce);
   if (banned) {
    guc_cancel_context_requests(ce);
    intel_engine_signal_breadcrumbs(ce->engine);
   }
   intel_context_sched_disable_unpin(ce);
   decr_outstanding_submission_g2h(guc);

   spin_lock(&ce->guc_state.lock);
   guc_blocked_fence_complete(ce);
   spin_unlock(&ce->guc_state.lock);

   intel_context_put(ce);
  }

  if (do_put)
   intel_context_put(ce);
  xa_lock(&guc->context_lookup);
 }
 xa_unlock_irqrestore(&guc->context_lookup, flags);
}

/*
 * GuC stores busyness stats for each engine at context in/out boundaries. A
 * context 'in' logs execution start time, 'out' adds in -> out delta to total.
 * i915/kmd accesses 'start', 'total' and 'context id' from memory shared with
 * GuC.
 *
 * __i915_pmu_event_read samples engine busyness. When sampling, if context id
 * is valid (!= ~0) and start is non-zero, the engine is considered to be
 * active. For an active engine total busyness = total + (now - start), where
 * 'now' is the time at which the busyness is sampled. For inactive engine,
 * total busyness = total.
 *
 * All times are captured from GUCPMTIMESTAMP reg and are in gt clock domain.
 *
 * 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 ns values, there is a need for this implementation to account for
 * overflows and extend the GuC provided values 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 (i.e. once in
 * 27 seconds for a gt clock frequency of 19.2 MHz).
 */


#define WRAP_TIME_CLKS U32_MAX
#define POLL_TIME_CLKS (WRAP_TIME_CLKS >> 3)

static void
__extend_last_switch(struct intel_guc *guc, u64 *prev_start, u32 new_start)
{
 u32 gt_stamp_hi = upper_32_bits(guc->timestamp.gt_stamp);
 u32 gt_stamp_last = lower_32_bits(guc->timestamp.gt_stamp);

 if (new_start == lower_32_bits(*prev_start))
  return;

 /*
 * When gt is unparked, we update the gt timestamp and start the ping
 * worker that updates the gt_stamp every POLL_TIME_CLKS. As long as gt
 * is unparked, all switched in contexts will have a start time that is
 * within +/- POLL_TIME_CLKS of the most recent gt_stamp.
 *
 * If neither gt_stamp nor new_start has rolled over, then the
 * gt_stamp_hi does not need to be adjusted, however if one of them has
 * rolled over, we need to adjust gt_stamp_hi accordingly.
 *
 * The below conditions address the cases of new_start rollover and
 * gt_stamp_last rollover respectively.
 */

 if (new_start < gt_stamp_last &&
     (new_start - gt_stamp_last) <= POLL_TIME_CLKS)
  gt_stamp_hi++;

 if (new_start > gt_stamp_last &&
     (gt_stamp_last - new_start) <= POLL_TIME_CLKS && gt_stamp_hi)
  gt_stamp_hi--;

 *prev_start = ((u64)gt_stamp_hi << 32) | new_start;
}

#define record_read(map_, field_) \
 iosys_map_rd_field(map_, 0, struct guc_engine_usage_record, field_)

/*
 * GuC updates shared memory and KMD reads it. Since this is not synchronized,
 * we run into a race where the value read is inconsistent. Sometimes the
 * inconsistency is in reading the upper MSB bytes of the last_in value when
 * this race occurs. 2 types of cases are seen - upper 8 bits are zero and upper
 * 24 bits are zero. Since these are non-zero values, it is non-trivial to
 * determine validity of these values. Instead we read the values multiple times
 * until they are consistent. In test runs, 3 attempts results in consistent
 * values. The upper bound is set to 6 attempts and may need to be tuned as per
 * any new occurrences.
 */

static void __get_engine_usage_record(struct intel_engine_cs *engine,
          u32 *last_in, u32 *id, u32 *total)
{
 struct iosys_map rec_map = intel_guc_engine_usage_record_map(engine);
 int i = 0;

 do {
  *last_in = record_read(&rec_map, last_switch_in_stamp);
  *id = record_read(&rec_map, current_context_index);
  *total = record_read(&rec_map, total_runtime);

  if (record_read(&rec_map, last_switch_in_stamp) == *last_in &&
      record_read(&rec_map, current_context_index) == *id &&
      record_read(&rec_map, total_runtime) == *total)
   break;
 } while (++i < 6);
}

static void __set_engine_usage_record(struct intel_engine_cs *engine,
          u32 last_in, u32 id, u32 total)
{
 struct iosys_map rec_map = intel_guc_engine_usage_record_map(engine);

#define record_write(map_, field_, val_) \
 iosys_map_wr_field(map_, 0, struct guc_engine_usage_record, field_, val_)

 record_write(&rec_map, last_switch_in_stamp, last_in);
 record_write(&rec_map, current_context_index, id);
 record_write(&rec_map, total_runtime, total);

#undef record_write
}

static void guc_update_engine_gt_clks(struct intel_engine_cs *engine)
{
 struct intel_engine_guc_stats *stats = &engine->stats.guc;
 struct intel_guc *guc = gt_to_guc(engine->gt);
 u32 last_switch, ctx_id, total;

 lockdep_assert_held(&guc->timestamp.lock);

 __get_engine_usage_record(engine, &last_switch, &ctx_id, &total);

 stats->running = ctx_id != ~0U && last_switch;
 if (stats->running)
  __extend_last_switch(guc, &stats->start_gt_clk, last_switch);

 /*
 * Instead of adjusting the total for overflow, just add the
 * difference from previous sample stats->total_gt_clks
 */

 if (total && total != ~0U) {
  stats->total_gt_clks += (u32)(total - stats->prev_total);
  stats->prev_total = total;
 }
}

static u32 gpm_timestamp_shift(struct intel_gt *gt)
{
 intel_wakeref_t wakeref;
 u32 reg;

 with_intel_runtime_pm(gt->uncore->rpm, wakeref)
  reg = intel_uncore_read(gt->uncore, RPM_CONFIG0);

 return 3 - REG_FIELD_GET(GEN10_RPM_CONFIG0_CTC_SHIFT_PARAMETER_MASK, reg);
}

static void guc_update_pm_timestamp(struct intel_guc *guc, ktime_t *now)
{
 struct intel_gt *gt = guc_to_gt(guc);
 u32 gt_stamp_lo, gt_stamp_hi;
 u64 gpm_ts;

 lockdep_assert_held(&guc->timestamp.lock);

 gt_stamp_hi = upper_32_bits(guc->timestamp.gt_stamp);
 gpm_ts = intel_uncore_read64_2x32(gt->uncore, MISC_STATUS0,
       MISC_STATUS1) >> guc->timestamp.shift;
 gt_stamp_lo = lower_32_bits(gpm_ts);
 *now = ktime_get();

 if (gt_stamp_lo < lower_32_bits(guc->timestamp.gt_stamp))
  gt_stamp_hi++;

 guc->timestamp.gt_stamp = ((u64)gt_stamp_hi << 32) | gt_stamp_lo;
}

/*
 * Unlike the execlist mode of submission total and active times are in terms of
 * gt clocks. The *now parameter is retained to return the cpu time at which the
 * busyness was sampled.
 */

static ktime_t guc_engine_busyness(struct intel_engine_cs *engine, ktime_t *now)
{
 struct intel_engine_guc_stats stats_saved, *stats = &engine->stats.guc;
 struct i915_gpu_error *gpu_error = &engine->i915->gpu_error;
 struct intel_gt *gt = engine->gt;
 struct intel_guc *guc = gt_to_guc(gt);
 u64 total, gt_stamp_saved;
 unsigned long flags;
 u32 reset_count;
 bool in_reset;
 intel_wakeref_t wakeref;

 spin_lock_irqsave(&guc->timestamp.lock, flags);

 /*
 * If a reset happened, we risk reading partially updated engine
 * busyness from GuC, so we just use the driver stored copy of busyness.
 * Synchronize with gt reset using reset_count and the
 * I915_RESET_BACKOFF flag. Note that reset flow updates the reset_count
 * after I915_RESET_BACKOFF flag, so ensure that the reset_count is
 * usable by checking the flag afterwards.
 */

 reset_count = i915_reset_count(gpu_error);
 in_reset = test_bit(I915_RESET_BACKOFF, >->reset.flags);

 *now = ktime_get();

 /*
 * The active busyness depends on start_gt_clk and gt_stamp.
 * gt_stamp is updated by i915 only when gt is awake and the
 * start_gt_clk is derived from GuC state. To get a consistent
 * view of activity, we query the GuC state only if gt is awake.
 */

 wakeref = in_reset ? NULL : intel_gt_pm_get_if_awake(gt);
 if (wakeref) {
  stats_saved = *stats;
  gt_stamp_saved = guc->timestamp.gt_stamp;
  /*
 * Update gt_clks, then gt timestamp to simplify the 'gt_stamp -
 * start_gt_clk' calculation below for active engines.
 */

  guc_update_engine_gt_clks(engine);
  guc_update_pm_timestamp(guc, now);
  intel_gt_pm_put_async(gt, wakeref);
  if (i915_reset_count(gpu_error) != reset_count) {
   *stats = stats_saved;
   guc->timestamp.gt_stamp = gt_stamp_saved;
  }
 }

 total = intel_gt_clock_interval_to_ns(gt, stats->total_gt_clks);
 if (stats->running) {
  u64 clk = guc->timestamp.gt_stamp - stats->start_gt_clk;

  total += intel_gt_clock_interval_to_ns(gt, clk);
 }

 if (total > stats->total)
  stats->total = total;

 spin_unlock_irqrestore(&guc->timestamp.lock, flags);

 return ns_to_ktime(stats->total);
}

static void guc_enable_busyness_worker(struct intel_guc *guc)
{
 mod_delayed_work(system_highpri_wq, &guc->timestamp.work, guc->timestamp.ping_delay);
}

static void guc_cancel_busyness_worker(struct intel_guc *guc)
{
 /*
 * There are many different call stacks that can get here. Some of them
 * hold the reset mutex. The busyness worker also attempts to acquire the
 * reset mutex. Synchronously flushing a worker thread requires acquiring
 * the worker mutex. Lockdep sees this as a conflict. It thinks that the
 * flush can deadlock because it holds the worker mutex while waiting for
 * the reset mutex, but another thread is holding the reset mutex and might
 * attempt to use other worker functions.
 *
 * In practice, this scenario does not exist because the busyness worker
 * does not block waiting for the reset mutex. It does a try-lock on it and
 * immediately exits if the lock is already held. Unfortunately, the mutex
 * in question (I915_RESET_BACKOFF) is an i915 implementation which has lockdep
 * annotation but not to the extent of explaining the 'might lock' is also a
 * 'does not need to lock'. So one option would be to add more complex lockdep
 * annotations to ignore the issue (if at all possible). A simpler option is to
 * just not flush synchronously when a rest in progress. Given that the worker
 * will just early exit and re-schedule itself anyway, there is no advantage
 * to running it immediately.
 *
 * If a reset is not in progress, then the synchronous flush may be required.
 * As noted many call stacks lead here, some during suspend and driver unload
 * which do require a synchronous flush to make sure the worker is stopped
 * before memory is freed.
 *
 * Trying to pass a 'need_sync' or 'in_reset' flag all the way down through
 * every possible call stack is unfeasible. It would be too intrusive to many
 * areas that really don't care about the GuC backend. However, there is the
 * I915_RESET_BACKOFF flag and the gt->reset.mutex can be tested for is_locked.
 * So just use those. Note that testing both is required due to the hideously
 * complex nature of the i915 driver's reset code paths.
 *
 * And note that in the case of a reset occurring during driver unload
 * (wedged_on_fini), skipping the cancel in reset_prepare/reset_fini (when the
 * reset flag/mutex are set) is fine because there is another explicit cancel in
 * intel_guc_submission_fini (when the reset flag/mutex are not).
 */

 if (mutex_is_locked(&guc_to_gt(guc)->reset.mutex) ||
     test_bit(I915_RESET_BACKOFF, &guc_to_gt(guc)->reset.flags))
  cancel_delayed_work(&guc->timestamp.work);
 else
  cancel_delayed_work_sync(&guc->timestamp.work);
}

static void __reset_guc_busyness_stats(struct intel_guc *guc)
{
 struct intel_gt *gt = guc_to_gt(guc);
 struct intel_engine_cs *engine;
 enum intel_engine_id id;
 unsigned long flags;
 ktime_t unused;

 spin_lock_irqsave(&guc->timestamp.lock, flags);

 guc_update_pm_timestamp(guc, &unused);
 for_each_engine(engine, gt, id) {
  struct intel_engine_guc_stats *stats = &engine->stats.guc;

  guc_update_engine_gt_clks(engine);

  /*
 * If resetting a running context, accumulate the active
 * time as well since there will be no context switch.
 */

  if (stats->running) {
   u64 clk = guc->timestamp.gt_stamp - stats->start_gt_clk;

   stats->total_gt_clks += clk;
  }
  stats->prev_total = 0;
  stats->running = 0;
 }

 spin_unlock_irqrestore(&guc->timestamp.lock, flags);
}

static void __update_guc_busyness_running_state(struct intel_guc *guc)
{
 struct intel_gt *gt = guc_to_gt(guc);
 struct intel_engine_cs *engine;
 enum intel_engine_id id;
 unsigned long flags;

 spin_lock_irqsave(&guc->timestamp.lock, flags);
 for_each_engine(engine, gt, id)
  engine->stats.guc.running = false;
 spin_unlock_irqrestore(&guc->timestamp.lock, flags);
}

static void __update_guc_busyness_stats(struct intel_guc *guc)
{
 struct intel_gt *gt = guc_to_gt(guc);
 struct intel_engine_cs *engine;
 enum intel_engine_id id;
 unsigned long flags;
 ktime_t unused;

 guc->timestamp.last_stat_jiffies = jiffies;

 spin_lock_irqsave(&guc->timestamp.lock, flags);

 guc_update_pm_timestamp(guc, &unused);
 for_each_engine(engine, gt, id)
  guc_update_engine_gt_clks(engine);

 spin_unlock_irqrestore(&guc->timestamp.lock, flags);
}

static void __guc_context_update_stats(struct intel_context *ce)
{
 struct intel_guc *guc = ce_to_guc(ce);
 unsigned long flags;

 spin_lock_irqsave(&guc->timestamp.lock, flags);
 lrc_update_runtime(ce);
 spin_unlock_irqrestore(&guc->timestamp.lock, flags);
}

static void guc_context_update_stats(struct intel_context *ce)
{
 if (!intel_context_pin_if_active(ce))
  return;

 __guc_context_update_stats(ce);
 intel_context_unpin(ce);
}

static void guc_timestamp_ping(struct work_struct *wrk)
{
 struct intel_guc *guc = container_of(wrk, typeof(*guc),
          timestamp.work.work);
 struct intel_uc *uc = container_of(guc, typeof(*uc), guc);
 struct intel_gt *gt = guc_to_gt(guc);
 struct intel_context *ce;
 intel_wakeref_t wakeref;
 unsigned long index;
 int srcu, ret;

 /*
 * Ideally the busyness worker should take a gt pm wakeref because the
 * worker only needs to be active while gt is awake. However, the
 * gt_park path cancels the worker synchronously and this complicates
 * the flow if the worker is also running at the same time. The cancel
 * waits for the worker and when the worker releases the wakeref, that
 * would call gt_park and would lead to a deadlock.
 *
 * The resolution is to take the global pm wakeref if runtime pm is
 * already active. If not, we don't need to update the busyness stats as
 * the stats would already be updated when the gt was parked.
 *
 * Note:
 * - We do not requeue the worker if we cannot take a reference to runtime
 *   pm since intel_guc_busyness_unpark would requeue the worker in the
 *   resume path.
 *
 * - If the gt was parked longer than time taken for GT timestamp to roll
 *   over, we ignore those rollovers since we don't care about tracking
 *   the exact GT time. We only care about roll overs when the gt is
 *   active and running workloads.
 *
 * - There is a window of time between gt_park and runtime suspend,
 *   where the worker may run. This is acceptable since the worker will
 *   not find any new data to update busyness.
 */

 wakeref = intel_runtime_pm_get_if_active(>->i915->runtime_pm);
 if (!wakeref)
  return;

 /*
 * Synchronize with gt reset to make sure the worker does not
 * corrupt the engine/guc stats. NB: can't actually block waiting
 * for a reset to complete as the reset requires flushing out
 * this worker thread if started. So waiting would deadlock.
 */

 ret = intel_gt_reset_trylock(gt, &srcu);
 if (ret)
  goto err_trylock;

 __update_guc_busyness_stats(guc);

 /* adjust context stats for overflow */
 xa_for_each(&guc->context_lookup, index, ce)
  guc_context_update_stats(ce);

 intel_gt_reset_unlock(gt, srcu);

 guc_enable_busyness_worker(guc);

err_trylock:
 intel_runtime_pm_put(>->i915->runtime_pm, wakeref);
}

static int guc_action_enable_usage_stats(struct intel_guc *guc)
{
 struct intel_gt *gt = guc_to_gt(guc);
 struct intel_engine_cs *engine;
 enum intel_engine_id id;
 u32 offset = intel_guc_engine_usage_offset(guc);
 u32 action[] = {
  INTEL_GUC_ACTION_SET_ENG_UTIL_BUFF,
  offset,
  0,
 };

 for_each_engine(engine, gt, id)
  __set_engine_usage_record(engine, 0, 0xffffffff, 0);

 return intel_guc_send(guc, action, ARRAY_SIZE(action));
}

static int guc_init_engine_stats(struct intel_guc *guc)
{
 struct intel_gt *gt = guc_to_gt(guc);
 intel_wakeref_t wakeref;
 int ret;

 with_intel_runtime_pm(>->i915->runtime_pm, wakeref)
  ret = guc_action_enable_usage_stats(guc);

 if (ret)
  guc_err(guc, "Failed to enable usage stats: %pe\n", ERR_PTR(ret));
 else
  guc_enable_busyness_worker(guc);

 return ret;
}

static void guc_fini_engine_stats(struct intel_guc *guc)
{
 guc_cancel_busyness_worker(guc);
}

void intel_guc_busyness_park(struct intel_gt *gt)
{
 struct intel_guc *guc = gt_to_guc(gt);

 if (!guc_submission_initialized(guc))
  return;

 /* Assume no engines are running and set running state to false */
 __update_guc_busyness_running_state(guc);

 /*
 * There is a race with suspend flow where the worker runs after suspend
 * and causes an unclaimed register access warning. Cancel the worker
 * synchronously here.
 */

 guc_cancel_busyness_worker(guc);

 /*
 * Before parking, we should sample engine busyness stats if we need to.
 * We can skip it if we are less than half a ping from the last time we
 * sampled the busyness stats.
 */

 if (guc->timestamp.last_stat_jiffies &&
     !time_after(jiffies, guc->timestamp.last_stat_jiffies +
   (guc->timestamp.ping_delay / 2)))
  return;

 __update_guc_busyness_stats(guc);
}

void intel_guc_busyness_unpark(struct intel_gt *gt)
{
 struct intel_guc *guc = gt_to_guc(gt);
 unsigned long flags;
 ktime_t unused;

 if (!guc_submission_initialized(guc))
  return;

 spin_lock_irqsave(&guc->timestamp.lock, flags);
 guc_update_pm_timestamp(guc, &unused);
 spin_unlock_irqrestore(&guc->timestamp.lock, flags);
 guc_enable_busyness_worker(guc);
}

static inline bool
submission_disabled(struct intel_guc *guc)
{
 struct i915_sched_engine * const sched_engine = guc->sched_engine;

 return unlikely(!sched_engine ||
   !__tasklet_is_enabled(&sched_engine->tasklet) ||
   intel_gt_is_wedged(guc_to_gt(guc)));
}

static void disable_submission(struct intel_guc *guc)
{
 struct i915_sched_engine * const sched_engine = guc->sched_engine;

 if (__tasklet_is_enabled(&sched_engine->tasklet)) {
  GEM_BUG_ON(!guc->ct.enabled);
  __tasklet_disable_sync_once(&sched_engine->tasklet);
  sched_engine->tasklet.callback = NULL;
 }
}

static void enable_submission(struct intel_guc *guc)
{
 struct i915_sched_engine * const sched_engine = guc->sched_engine;
 unsigned long flags;

 spin_lock_irqsave(&guc->sched_engine->lock, flags);
 sched_engine->tasklet.callback = guc_submission_tasklet;
 wmb(); /* Make sure callback visible */
 if (!__tasklet_is_enabled(&sched_engine->tasklet) &&
     __tasklet_enable(&sched_engine->tasklet)) {
  GEM_BUG_ON(!guc->ct.enabled);

  /* And kick in case we missed a new request submission. */
  tasklet_hi_schedule(&sched_engine->tasklet);
 }
 spin_unlock_irqrestore(&guc->sched_engine->lock, flags);
}

static void guc_flush_submissions(struct intel_guc *guc)
{
 struct i915_sched_engine * const sched_engine = guc->sched_engine;
 unsigned long flags;

 spin_lock_irqsave(&sched_engine->lock, flags);
 spin_unlock_irqrestore(&sched_engine->lock, flags);
}

void intel_guc_submission_flush_work(struct intel_guc *guc)
{
 flush_work(&guc->submission_state.destroyed_worker);
}

static void guc_flush_destroyed_contexts(struct intel_guc *guc);

void intel_guc_submission_reset_prepare(struct intel_guc *guc)
{
 if (unlikely(!guc_submission_initialized(guc))) {
  /* Reset called during driver load? GuC not yet initialised! */
  return;
 }

 intel_gt_park_heartbeats(guc_to_gt(guc));
 disable_submission(guc);
 guc->interrupts.disable(guc);
 __reset_guc_busyness_stats(guc);

 /* Flush IRQ handler */
 spin_lock_irq(guc_to_gt(guc)->irq_lock);
 spin_unlock_irq(guc_to_gt(guc)->irq_lock);

 /* Flush tasklet */
 tasklet_disable(&guc->ct.receive_tasklet);
 tasklet_enable(&guc->ct.receive_tasklet);

 guc_flush_submissions(guc);
 guc_flush_destroyed_contexts(guc);
 flush_work(&guc->ct.requests.worker);

 scrub_guc_desc_for_outstanding_g2h(guc);
}

static struct intel_engine_cs *
guc_virtual_get_sibling(struct intel_engine_cs *ve, unsigned int sibling)
{
 struct intel_engine_cs *engine;
 intel_engine_mask_t tmp, mask = ve->mask;
 unsigned int num_siblings = 0;

 for_each_engine_masked(engine, ve->gt, mask, tmp)
  if (num_siblings++ == sibling)
   return engine;

 return NULL;
}

static inline struct intel_engine_cs *
__context_to_physical_engine(struct intel_context *ce)
{
 struct intel_engine_cs *engine = ce->engine;

 if (intel_engine_is_virtual(engine))
  engine = guc_virtual_get_sibling(engine, 0);

 return engine;
}

static void guc_reset_state(struct intel_context *ce, u32 head, bool scrub)
{
 struct intel_engine_cs *engine = __context_to_physical_engine(ce);

 if (!intel_context_is_schedulable(ce))
  return;

 GEM_BUG_ON(!intel_context_is_pinned(ce));

 /*
 * We want a simple context + ring to execute the breadcrumb update.
 * We cannot rely on the context being intact across the GPU hang,
 * so clear it and rebuild just what we need for the breadcrumb.
 * All pending requests for this context will be zapped, and any
 * future request will be after userspace has had the opportunity
 * to recreate its own state.
 */

 if (scrub)
  lrc_init_regs(ce, engine, true);

 /* Rerun the request; its payload has been neutered (if guilty). */
 lrc_update_regs(ce, engine, head);
}

static void guc_engine_reset_prepare(struct intel_engine_cs *engine)
{
 /*
 * Wa_22011802037: In addition to stopping the cs, we need
 * to wait for any pending mi force wakeups
 */

 if (intel_engine_reset_needs_wa_22011802037(engine->gt)) {
  intel_engine_stop_cs(engine);
  intel_engine_wait_for_pending_mi_fw(engine);
 }
}

static void guc_reset_nop(struct intel_engine_cs *engine)
{
}

static void guc_rewind_nop(struct intel_engine_cs *engine, bool stalled)
{
}

static void
__unwind_incomplete_requests(struct intel_context *ce)
{
 struct i915_request *rq, *rn;
 struct list_head *pl;
 int prio = I915_PRIORITY_INVALID;
 struct i915_sched_engine * const sched_engine =
  ce->engine->sched_engine;
 unsigned long flags;

 spin_lock_irqsave(&sched_engine->lock, flags);
 spin_lock(&ce->guc_state.lock);
 list_for_each_entry_safe_reverse(rq, rn,
      &ce->guc_state.requests,
      sched.link) {
  if (i915_request_completed(rq))
   continue;

  list_del_init(&rq->sched.link);
  __i915_request_unsubmit(rq);

  /* Push the request back into the queue for later resubmission. */
  GEM_BUG_ON(rq_prio(rq) == I915_PRIORITY_INVALID);
  if (rq_prio(rq) != prio) {
   prio = rq_prio(rq);
   pl = i915_sched_lookup_priolist(sched_engine, prio);
  }
  GEM_BUG_ON(i915_sched_engine_is_empty(sched_engine));

  list_add(&rq->sched.link, pl);
  set_bit(I915_FENCE_FLAG_PQUEUE, &rq->fence.flags);
 }
 spin_unlock(&ce->guc_state.lock);
 spin_unlock_irqrestore(&sched_engine->lock, flags);
}

static void __guc_reset_context(struct intel_context *ce, intel_engine_mask_t stalled)
{
 bool guilty;
 struct i915_request *rq;
 unsigned long flags;
 u32 head;
 int i, number_children = ce->parallel.number_children;
 struct intel_context *parent = ce;

 GEM_BUG_ON(intel_context_is_child(ce));

 intel_context_get(ce);

 /*
 * GuC will implicitly mark the context as non-schedulable when it sends
 * the reset notification. Make sure our state reflects this change. The
 * context will be marked enabled on resubmission.
 */

 spin_lock_irqsave(&ce->guc_state.lock, flags);
 clr_context_enabled(ce);
 spin_unlock_irqrestore(&ce->guc_state.lock, flags);

 /*
 * For each context in the relationship find the hanging request
 * resetting each context / request as needed
 */

 for (i = 0; i < number_children + 1; ++i) {
  if (!intel_context_is_pinned(ce))
   goto next_context;

  guilty = false;
  rq = intel_context_get_active_request(ce);
  if (!rq) {
   head = ce->ring->tail;
   goto out_replay;
  }

  if (i915_request_started(rq))
   guilty = stalled & ce->engine->mask;

  GEM_BUG_ON(i915_active_is_idle(&ce->active));
  head = intel_ring_wrap(ce->ring, rq->head);

  __i915_request_reset(rq, guilty);
  i915_request_put(rq);
out_replay:
  guc_reset_state(ce, head, guilty);
next_context:
  if (i != number_children)
   ce = list_next_entry(ce, parallel.child_link);
 }

 __unwind_incomplete_requests(parent);
 intel_context_put(parent);
}

void wake_up_all_tlb_invalidate(struct intel_guc *guc)
{
 struct intel_guc_tlb_wait *wait;
 unsigned long i;

 if (!intel_guc_tlb_invalidation_is_available(guc))
  return;

 xa_lock_irq(&guc->tlb_lookup);
 xa_for_each(&guc->tlb_lookup, i, wait)
  wake_up(&wait->wq);
 xa_unlock_irq(&guc->tlb_lookup);
}

void intel_guc_submission_reset(struct intel_guc *guc, intel_engine_mask_t stalled)
{
 struct intel_context *ce;
 unsigned long index;
 unsigned long flags;

 if (unlikely(!guc_submission_initialized(guc))) {
  /* Reset called during driver load? GuC not yet initialised! */
  return;
 }

 xa_lock_irqsave(&guc->context_lookup, flags);
 xa_for_each(&guc->context_lookup, index, ce) {
  if (!kref_get_unless_zero(&ce->ref))
   continue;

  xa_unlock(&guc->context_lookup);

  if (intel_context_is_pinned(ce) &&
      !intel_context_is_child(ce))
   __guc_reset_context(ce, stalled);

  intel_context_put(ce);

  xa_lock(&guc->context_lookup);
 }
 xa_unlock_irqrestore(&guc->context_lookup, flags);

 /* GuC is blown away, drop all references to contexts */
 xa_destroy(&guc->context_lookup);
}

static void guc_cancel_context_requests(struct intel_context *ce)
{
 struct i915_sched_engine *sched_engine = ce_to_guc(ce)->sched_engine;
 struct i915_request *rq;
 unsigned long flags;

 /* Mark all executing requests as skipped. */
 spin_lock_irqsave(&sched_engine->lock, flags);
 spin_lock(&ce->guc_state.lock);
 list_for_each_entry(rq, &ce->guc_state.requests, sched.link)
  i915_request_put(i915_request_mark_eio(rq));
 spin_unlock(&ce->guc_state.lock);
 spin_unlock_irqrestore(&sched_engine->lock, flags);
}

static void
guc_cancel_sched_engine_requests(struct i915_sched_engine *sched_engine)
{
 struct i915_request *rq, *rn;
 struct rb_node *rb;
 unsigned long flags;

 /* Can be called during boot if GuC fails to load */
 if (!sched_engine)
  return;

 /*
 * Before we call engine->cancel_requests(), we should have exclusive
 * access to the submission state. This is arranged for us by the
 * caller disabling the interrupt generation, the tasklet and other
 * threads that may then access the same state, giving us a free hand
 * to reset state. However, we still need to let lockdep be aware that
 * we know this state may be accessed in hardirq context, so we
 * disable the irq around this manipulation and we want to keep
 * the spinlock focused on its duties and not accidentally conflate
 * coverage to the submission's irq state. (Similarly, although we
 * shouldn't need to disable irq around the manipulation of the
 * submission's irq state, we also wish to remind ourselves that
 * it is irq state.)
 */

 spin_lock_irqsave(&sched_engine->lock, flags);

 /* Flush the queued requests to the timeline list (for retiring). */
 while ((rb = rb_first_cached(&sched_engine->queue))) {
  struct i915_priolist *p = to_priolist(rb);

  priolist_for_each_request_consume(rq, rn, p) {
   list_del_init(&rq->sched.link);

   __i915_request_submit(rq);

   i915_request_put(i915_request_mark_eio(rq));
  }

  rb_erase_cached(&p->node, &sched_engine->queue);
  i915_priolist_free(p);
 }

 /* Remaining _unready_ requests will be nop'ed when submitted */

 sched_engine->queue_priority_hint = INT_MIN;
 sched_engine->queue = RB_ROOT_CACHED;

 spin_unlock_irqrestore(&sched_engine->lock, flags);
}

void intel_guc_submission_cancel_requests(struct intel_guc *guc)
{
 struct intel_context *ce;
 unsigned long index;
 unsigned long flags;

 xa_lock_irqsave(&guc->context_lookup, flags);
 xa_for_each(&guc->context_lookup, index, ce) {
  if (!kref_get_unless_zero(&ce->ref))
   continue;

  xa_unlock(&guc->context_lookup);

  if (intel_context_is_pinned(ce) &&
      !intel_context_is_child(ce))
   guc_cancel_context_requests(ce);

  intel_context_put(ce);

  xa_lock(&guc->context_lookup);
 }
 xa_unlock_irqrestore(&guc->context_lookup, flags);

 guc_cancel_sched_engine_requests(guc->sched_engine);

 /* GuC is blown away, drop all references to contexts */
 xa_destroy(&guc->context_lookup);

 /*
 * Wedged GT won't respond to any TLB invalidation request. Simply
 * release all the blocked waiters.
 */

 wake_up_all_tlb_invalidate(guc);
}

void intel_guc_submission_reset_finish(struct intel_guc *guc)
{
 int outstanding;

 /* Reset called during driver load or during wedge? */
 if (unlikely(!guc_submission_initialized(guc) ||
       !intel_guc_is_fw_running(guc) ||
       intel_gt_is_wedged(guc_to_gt(guc)))) {
  return;
 }

 /*
 * Technically possible for either of these values to be non-zero here,
 * but very unlikely + harmless. Regardless let's add an error so we can
 * see in CI if this happens frequently / a precursor to taking down the
 * machine.
 */

 outstanding = atomic_read(&guc->outstanding_submission_g2h);
 if (outstanding)
  guc_err(guc, "Unexpected outstanding GuC to Host response(s) in reset finish: %d\n",
   outstanding);
 atomic_set(&guc->outstanding_submission_g2h, 0);

 intel_guc_global_policies_update(guc);
 enable_submission(guc);
 intel_gt_unpark_heartbeats(guc_to_gt(guc));

 /*
 * The full GT reset will have cleared the TLB caches and flushed the
 * G2H message queue; we can release all the blocked waiters.
 */

 wake_up_all_tlb_invalidate(guc);
}

static void destroyed_worker_func(struct work_struct *w);
static void reset_fail_worker_func(struct work_struct *w);

bool intel_guc_tlb_invalidation_is_available(struct intel_guc *guc)
{
 return HAS_GUC_TLB_INVALIDATION(guc_to_gt(guc)->i915) &&
  intel_guc_is_ready(guc);
}

static int init_tlb_lookup(struct intel_guc *guc)
{
 struct intel_guc_tlb_wait *wait;
 int err;

 if (!HAS_GUC_TLB_INVALIDATION(guc_to_gt(guc)->i915))
  return 0;

 xa_init_flags(&guc->tlb_lookup, XA_FLAGS_ALLOC);

 wait = kzalloc(sizeof(*wait), GFP_KERNEL);
 if (!wait)
  return -ENOMEM;

 init_waitqueue_head(&wait->wq);

 /* Preallocate a shared id for use under memory pressure. */
 err = xa_alloc_cyclic_irq(&guc->tlb_lookup, &guc->serial_slot, wait,
      xa_limit_32b, &guc->next_seqno, GFP_KERNEL);
 if (err < 0) {
  kfree(wait);
  return err;
 }

 return 0;
}

static void fini_tlb_lookup(struct intel_guc *guc)
{
 struct intel_guc_tlb_wait *wait;

 if (!HAS_GUC_TLB_INVALIDATION(guc_to_gt(guc)->i915))
  return;

 wait = xa_load(&guc->tlb_lookup, guc->serial_slot);
 if (wait && wait->busy)
  guc_err(guc, "Unexpected busy item in tlb_lookup on fini\n");
 kfree(wait);

 xa_destroy(&guc->tlb_lookup);
}

/*
 * Set up the memory resources to be shared with the GuC (via the GGTT)
 * at firmware loading time.
 */

int intel_guc_submission_init(struct intel_guc *guc)
{
 struct intel_gt *gt = guc_to_gt(guc);
 int ret;

 if (guc->submission_initialized)
  return 0;

 if (GUC_SUBMIT_VER(guc) < MAKE_GUC_VER(1, 0, 0)) {
  ret = guc_lrc_desc_pool_create_v69(guc);
  if (ret)
   return ret;
 }

 ret = init_tlb_lookup(guc);
 if (ret)
  goto destroy_pool;

 guc->submission_state.guc_ids_bitmap =
  bitmap_zalloc(NUMBER_MULTI_LRC_GUC_ID(guc), GFP_KERNEL);
 if (!guc->submission_state.guc_ids_bitmap) {
  ret = -ENOMEM;
  goto destroy_tlb;
 }

 guc->timestamp.ping_delay = (POLL_TIME_CLKS / gt->clock_frequency + 1) * HZ;
 guc->timestamp.shift = gpm_timestamp_shift(gt);
 guc->submission_initialized = true;

 return 0;

destroy_tlb:
 fini_tlb_lookup(guc);
destroy_pool:
 guc_lrc_desc_pool_destroy_v69(guc);
 return ret;
}

void intel_guc_submission_fini(struct intel_guc *guc)
{
 if (!guc->submission_initialized)
  return;

 guc_fini_engine_stats(guc);
 guc_flush_destroyed_contexts(guc);
 guc_lrc_desc_pool_destroy_v69(guc);
 i915_sched_engine_put(guc->sched_engine);
 bitmap_free(guc->submission_state.guc_ids_bitmap);
 fini_tlb_lookup(guc);
 guc->submission_initialized = false;
}

static inline void queue_request(struct i915_sched_engine *sched_engine,
     struct i915_request *rq,
     int prio)
{
 GEM_BUG_ON(!list_empty(&rq->sched.link));
 list_add_tail(&rq->sched.link,
        i915_sched_lookup_priolist(sched_engine, prio));
 set_bit(I915_FENCE_FLAG_PQUEUE, &rq->fence.flags);
 tasklet_hi_schedule(&sched_engine->tasklet);
}

static int guc_bypass_tasklet_submit(struct intel_guc *guc,
         struct i915_request *rq)
{
 int ret = 0;

 __i915_request_submit(rq);

 trace_i915_request_in(rq, 0);

 if (is_multi_lrc_rq(rq)) {
  if (multi_lrc_submit(rq)) {
   ret = guc_wq_item_append(guc, rq);
   if (!ret)
    ret = guc_add_request(guc, rq);
  }
 } else {
  guc_set_lrc_tail(rq);
  ret = guc_add_request(guc, rq);
 }

 if (unlikely(ret == -EPIPE))
  disable_submission(guc);

 return ret;
}

static bool need_tasklet(struct intel_guc *guc, struct i915_request *rq)
{
 struct i915_sched_engine *sched_engine = rq->engine->sched_engine;
 struct intel_context *ce = request_to_scheduling_context(rq);

--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=97 H=95 G=95

¤ Dauer der Verarbeitung: 0.59 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.