Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Rodrigo Vivi <rodrigo.vivi@intel.com>
To: Zongyao Bai <zongyao.bai@intel.com>
Cc: <intel-xe@lists.freedesktop.org>
Subject: Re: [PATCH 1/3] drm/xe/forcewake: add delayed release with hysteresis
Date: Mon, 9 Nov 2026 15:55:20 -0500	[thread overview]
Message-ID: <avIzOG78pyACjrCl@intel.com> (raw)
In-Reply-To: <20261017031856.2723986-2-zongyao.bai@intel.com>

On Sat, Oct 17, 2026 at 03:18:54AM +0000, Zongyao Bai wrote:
> Add xe_force_wake_put_delay(), which keeps a domain awake for a short hold
> time after its last reference is dropped instead of putting it to sleep
> immediately. A xe_force_wake_get() within that time reuses the awake domain
> without a wake request and ACK wait, which makes repeated short accesses
> cheaper.
> 
> The domain is put to sleep from a delayed work, in process context, so
> the sleep ACK can be waited for in the same way as a regular put. The work
> sleeps only the domains whose hold time expired and which still have no
> reference, and requeues itself for the rest. The hold time is a couple of
> jiffies.
> 
> Signed-off-by: Zongyao Bai <zongyao.bai@intel.com>
> ---
>  drivers/gpu/drm/xe/xe_force_wake.c       | 184 ++++++++++++++++++++---
>  drivers/gpu/drm/xe/xe_force_wake.h       |  21 ++-
>  drivers/gpu/drm/xe/xe_force_wake_types.h |  14 ++
>  drivers/gpu/drm/xe/xe_gt.c               |   5 +-
>  4 files changed, 197 insertions(+), 27 deletions(-)
> 
> diff --git a/drivers/gpu/drm/xe/xe_force_wake.c b/drivers/gpu/drm/xe/xe_force_wake.c
> index 197e2197bd0a..71517bc4ffe6 100644
> --- a/drivers/gpu/drm/xe/xe_force_wake.c
> +++ b/drivers/gpu/drm/xe/xe_force_wake.c
> @@ -6,16 +6,22 @@
>  #include "xe_force_wake.h"
>  
>  #include <drm/drm_util.h>
> +#include <linux/device.h>
> +#include <linux/workqueue.h>
>  
>  #include "regs/xe_gt_regs.h"
>  #include "regs/xe_reg_defs.h"
>  #include "xe_gt.h"
>  #include "xe_gt_printk.h"
>  #include "xe_mmio.h"
> +#include "xe_pm.h"
>  #include "xe_sriov.h"
>  
>  #define XE_FORCE_WAKE_ACK_TIMEOUT_MS	50
>  
> +/* Minimum hold of a delayed release, in jiffies. */
> +#define XE_FORCE_WAKE_HOLD_JIFFIES	2

please define in msec and use msec_to_jiffies()

> +
>  static const char *str_wake_sleep(bool wake)
>  {
>  	return wake ? "wake" : "sleep";
> @@ -42,12 +48,22 @@ static void init_domain(struct xe_force_wake *fw,
>  	mark_domain_initialized(fw, id);
>  }
>  
> -void xe_force_wake_init_gt(struct xe_gt *gt, struct xe_force_wake *fw)
> +static void xe_force_wake_disable_work(void *arg)
> +{
> +	struct xe_force_wake *fw = arg;
> +
> +	disable_delayed_work_sync(&fw->delayed_release_work);
> +}
> +
> +static void xe_force_wake_delayed_release_work(struct work_struct *work);
> +
> +int xe_force_wake_init_gt(struct xe_gt *gt, struct xe_force_wake *fw)
>  {
>  	struct xe_device *xe = gt_to_xe(gt);
>  
>  	fw->gt = gt;
>  	spin_lock_init(&fw->lock);
> +	INIT_DELAYED_WORK(&fw->delayed_release_work, xe_force_wake_delayed_release_work);
>  
>  	if (xe->info.graphics_verx100 >= 1270) {
>  		init_domain(fw, XE_FW_DOMAIN_ID_GT,
> @@ -58,6 +74,8 @@ void xe_force_wake_init_gt(struct xe_gt *gt, struct xe_force_wake *fw)
>  			    FORCEWAKE_GT,
>  			    FORCEWAKE_ACK_GT);
>  	}
> +
> +	return devm_add_action_or_reset(xe->drm.dev, xe_force_wake_disable_work, fw);
>  }
>  
>  void xe_force_wake_init_engines(struct xe_gt *gt, struct xe_force_wake *fw)
> @@ -187,8 +205,13 @@ unsigned int __must_check xe_force_wake_get(struct xe_force_wake *fw,
>  	spin_lock_irqsave(&fw->lock, flags);
>  	for_each_fw_domain_masked(domain, ref_rqst, fw, tmp) {
>  		if (!domain->ref++) {
> -			awake_rqst |= BIT(domain->id);
> -			domain_wake(gt, domain);
> +			if (fw->delayed_domains & BIT(domain->id)) {
> +				/* Still awake from a delayed release: reuse it. */
> +				fw->delayed_domains &= ~BIT(domain->id);
> +			} else {
> +				awake_rqst |= BIT(domain->id);
> +				domain_wake(gt, domain);
> +			}
>  		}
>  		ref_incr |= BIT(domain->id);
>  	}
> @@ -212,23 +235,46 @@ unsigned int __must_check xe_force_wake_get(struct xe_force_wake *fw,
>  	return ref_incr;
>  }
>  
> -/**
> - * xe_force_wake_put - Decrement the refcount and put domain to sleep if refcount becomes 0
> - * @fw: Pointer to the force wake structure
> - * @fw_ref: return of xe_force_wake_get()
> - *
> - * This function reduces the reference counts for domains in fw_ref. If
> - * refcount for any of the specified domain reaches 0, it puts the domain to sleep
> - * and waits for acknowledgment for domain to sleep within 50 milisec timeout.
> - * Warns in case of timeout of ack from domain.
> +/*
> + * Put the @mask domains to sleep and wait for the acknowledgment. Must be
> + * called with fw->lock held. Returns the mask of domains that failed to
> + * acknowledge.
>   */
> -void xe_force_wake_put(struct xe_force_wake *fw, unsigned int fw_ref)
> +static unsigned int xe_force_wake_sleep_locked(struct xe_force_wake *fw,
> +					       unsigned int mask)
>  {
>  	struct xe_gt *gt = fw->gt;
>  	struct xe_force_wake_domain *domain;
> -	unsigned int tmp, sleep = 0;
> +	unsigned int tmp, ack_fail = 0;
> +
> +	lockdep_assert_held(&fw->lock);
> +
> +	for_each_fw_domain_masked(domain, mask, fw, tmp)
> +		domain_sleep(gt, domain);
> +
> +	for_each_fw_domain_masked(domain, mask, fw, tmp) {
> +		if (domain_sleep_wait(gt, domain) == 0)
> +			fw->awake_domains &= ~BIT(domain->id);
> +		else
> +			ack_fail |= BIT(domain->id);
> +	}
> +
> +	return ack_fail;
> +}
> +
> +static void xe_force_wake_warn_sleep_fail(struct xe_gt *gt, unsigned int ack_fail)
> +{
> +	xe_gt_WARN(gt, ack_fail, "Forcewake domain%s %#x failed to acknowledge sleep request\n",
> +		   str_plural(hweight_long(ack_fail)), ack_fail);
> +}
> +
> +static void __xe_force_wake_put(struct xe_force_wake *fw, unsigned int fw_ref,
> +				bool delayed)

perhaps instead of bool we pass the msec of delay instead?

> +{
> +	struct xe_gt *gt = fw->gt;
> +	struct xe_force_wake_domain *domain;
> +	unsigned int tmp, sleep = 0, ack_fail;
>  	unsigned long flags;
> -	int ack_fail = 0;
>  
>  	/*
>  	 * Avoid unnecessary lock and unlock when the function is called
> @@ -244,21 +290,111 @@ void xe_force_wake_put(struct xe_force_wake *fw, unsigned int fw_ref)
>  	for_each_fw_domain_masked(domain, fw_ref, fw, tmp) {
>  		xe_gt_assert(gt, domain->ref);
>  
> -		if (!--domain->ref) {
> +		if (--domain->ref)
> +			continue;
> +
> +		if (delayed) {
> +			domain->hold_until = jiffies + XE_FORCE_WAKE_HOLD_JIFFIES;
> +			fw->delayed_domains |= BIT(domain->id);
> +			/*
> +			 * queue_delayed_work() returns false both when the work
> +			 * is already pending and when flush/stop has disabled
> +			 * it. A pending worker releases the domain later, a
> +			 * disabled one never will, so release it right away.
> +			 *
> +			 * If the worker starts and clears PENDING between the
> +			 * two checks, it is misread as disabled and released
> +			 * early. That is safe: the delayed bit is cleared
> +			 * under fw->lock, so the worker does not see the
> +			 * domain and does not sleep it again. Only this one
> +			 * hold is lost.
> +			 */
> +			if (!queue_delayed_work(system_percpu_wq, &fw->delayed_release_work,
> +						XE_FORCE_WAKE_HOLD_JIFFIES) &&
> +			    !delayed_work_pending(&fw->delayed_release_work)) {

This is confusing... perhaps a fw->delayed_enabled bool that is set and cleared
under fw->lock by stop, is cleaner removing race and comment above...

> +				fw->delayed_domains &= ~BIT(domain->id);
> +				sleep |= BIT(domain->id);
> +			}
> +		} else {
>  			sleep |= BIT(domain->id);
> -			domain_sleep(gt, domain);
>  		}
>  	}
> -	for_each_fw_domain_masked(domain, sleep, fw, tmp) {
> -		if (domain_sleep_wait(gt, domain) == 0)
> -			fw->awake_domains &= ~BIT(domain->id);
> -		else
> -			ack_fail |= BIT(domain->id);
> +	ack_fail = xe_force_wake_sleep_locked(fw, sleep);
> +	spin_unlock_irqrestore(&fw->lock, flags);
> +
> +	xe_force_wake_warn_sleep_fail(gt, ack_fail);
> +}
> +
> +/**
> + * xe_force_wake_put - Decrement the refcount and put domain to sleep if refcount becomes 0
> + * @fw: Pointer to the force wake structure
> + * @fw_ref: return of xe_force_wake_get()
> + *
> + * This function reduces the reference counts for domains in fw_ref. If
> + * refcount for any of the specified domain reaches 0, it puts the domain to sleep
> + * and waits for acknowledgment for domain to sleep within 50 milisec timeout.
> + * Warns in case of timeout of ack from domain.
> + */
> +void xe_force_wake_put(struct xe_force_wake *fw, unsigned int fw_ref)
> +{
> +	__xe_force_wake_put(fw, fw_ref, false);
> +}
> +
> +/**
> + * xe_force_wake_put_delay - Decrement the refcount, delaying the sleep request
> + * @fw: Pointer to the force wake structure
> + * @fw_ref: return of xe_force_wake_get()
> + *
> + * Like xe_force_wake_put(), but a domain whose refcount reaches 0 is kept
> + * awake for a short hold time instead of being put to sleep right away. An
> + * xe_force_wake_get() during that time reuses the awake domain without a
> + * wake request, which makes repeated short accesses cheaper. The domain is
> + * put to sleep by a worker once the hold time expires without a new user.
> + *
> + * Meant for opt-in hot paths that are expected to be repeated shortly after.
> + */
> +void xe_force_wake_put_delay(struct xe_force_wake *fw, unsigned int fw_ref)
> +{
> +	__xe_force_wake_put(fw, fw_ref, true);
> +}
> +
> +/*
> + * Put the delayed domains whose hold time expired to sleep, and requeue for
> + * the remaining ones.
> + */
> +static void xe_force_wake_delayed_release_work(struct work_struct *work)
> +{
> +	struct xe_force_wake *fw = container_of(to_delayed_work(work),
> +						struct xe_force_wake, delayed_release_work);
> +	struct xe_gt *gt = fw->gt;
> +	struct xe_force_wake_domain *domain;
> +	unsigned int tmp, expired = 0, ack_fail;
> +	unsigned long flags, next = 0;
> +	bool remaining = false;
> +
> +	xe_gt_assert(gt, !xe_pm_runtime_suspended(gt_to_xe(gt)));

what about a get_if_active variant or something like that instead this
fragile assert?

> +
> +	spin_lock_irqsave(&fw->lock, flags);
> +	for_each_fw_domain_masked(domain, fw->delayed_domains, fw, tmp) {
> +		if (time_before(jiffies, domain->hold_until)) {
> +			if (!remaining || time_before(domain->hold_until, next))
> +				next = domain->hold_until;
> +			remaining = true;
> +			continue;
> +		}
> +
> +		expired |= BIT(domain->id);
> +		fw->delayed_domains &= ~BIT(domain->id);
>  	}
> +
> +	ack_fail = xe_force_wake_sleep_locked(fw, expired);
> +
> +	if (remaining)
> +		queue_delayed_work(system_percpu_wq, &fw->delayed_release_work,
> +				   next - jiffies);
>  	spin_unlock_irqrestore(&fw->lock, flags);
>  
> -	xe_gt_WARN(gt, ack_fail, "Forcewake domain%s %#x failed to acknowledge sleep request\n",
> -		   str_plural(hweight_long(ack_fail)), ack_fail);
> +	xe_force_wake_warn_sleep_fail(gt, ack_fail);
>  }
>  
>  const char *xe_force_wake_domain_to_str(enum xe_force_wake_domain_id id)
> diff --git a/drivers/gpu/drm/xe/xe_force_wake.h b/drivers/gpu/drm/xe/xe_force_wake.h
> index e2721f205d6c..35a2252c762a 100644
> --- a/drivers/gpu/drm/xe/xe_force_wake.h
> +++ b/drivers/gpu/drm/xe/xe_force_wake.h
> @@ -11,13 +11,14 @@
>  
>  struct xe_gt;
>  
> -void xe_force_wake_init_gt(struct xe_gt *gt,
> -			   struct xe_force_wake *fw);
> +int xe_force_wake_init_gt(struct xe_gt *gt,
> +			  struct xe_force_wake *fw);
>  void xe_force_wake_init_engines(struct xe_gt *gt,
>  				struct xe_force_wake *fw);
>  unsigned int __must_check xe_force_wake_get(struct xe_force_wake *fw,
>  					    enum xe_force_wake_domains domains);
>  void xe_force_wake_put(struct xe_force_wake *fw, unsigned int fw_ref);
> +void xe_force_wake_put_delay(struct xe_force_wake *fw, unsigned int fw_ref);
>  
>  const char *xe_force_wake_domain_to_str(enum xe_force_wake_domain_id id);
>  
> @@ -103,6 +104,22 @@ DEFINE_CLASS(xe_force_wake, struct xe_force_wake_ref,
>  #define xe_with_force_wake(ref, fw, domains) \
>  	__xe_with_force_wake(ref, fw, domains, __UNIQUE_ID(done))
>  
> +/*
> + * Same as the xe_force_wake class, but the domains are released with
> + * xe_force_wake_put_delay().
> + */
> +DEFINE_CLASS(xe_force_wake_delay, struct xe_force_wake_ref,
> +	     xe_force_wake_put_delay(_T.fw, _T.domains),
> +	     xe_force_wake_constructor(fw, domains),
> +	     struct xe_force_wake *fw, unsigned int domains);
> +
> +#define __xe_with_force_wake_delay(ref, fw, domains, done) \
> +	for (CLASS(xe_force_wake_delay, ref)(fw, domains), *(done) = NULL; \
> +	     !(done); (done) = (void *)1)
> +
> +#define xe_with_force_wake_delay(ref, fw, domains) \
> +	__xe_with_force_wake_delay(ref, fw, domains, __UNIQUE_ID(done))
> +
>  /*
>   * Used when xe_force_wake_constructor() has already been called by another
>   * function and the current function is responsible for releasing the forcewake
> diff --git a/drivers/gpu/drm/xe/xe_force_wake_types.h b/drivers/gpu/drm/xe/xe_force_wake_types.h
> index 14b7b86e801b..5c16511c6f36 100644
> --- a/drivers/gpu/drm/xe/xe_force_wake_types.h
> +++ b/drivers/gpu/drm/xe/xe_force_wake_types.h
> @@ -8,6 +8,7 @@
>  
>  #include <linux/mutex.h>
>  #include <linux/types.h>
> +#include <linux/workqueue.h>
>  
>  #include "regs/xe_reg_defs.h"
>  
> @@ -82,6 +83,11 @@ struct xe_force_wake_domain {
>  	u32 mask;
>  	/** @ref: domain reference */
>  	u32 ref;
> +	/**
> +	 * @hold_until: jiffies value until which a delayed release keeps the
> +	 * domain awake. Protected by &xe_force_wake.lock.
> +	 */
> +	unsigned long hold_until;
>  };
>  
>  /**
> @@ -101,6 +107,14 @@ struct xe_force_wake {
>  	spinlock_t lock;
>  	/** @awake_domains: mask of all domains awake */
>  	unsigned int awake_domains;
> +	/**
> +	 * @delayed_domains: mask of unreferenced domains that are kept awake
> +	 * until @delayed_release_work puts them to sleep. A subset of
> +	 * @awake_domains.
> +	 */
> +	unsigned int delayed_domains;
> +	/** @delayed_release_work: puts expired @delayed_domains to sleep */
> +	struct delayed_work delayed_release_work;
>  	/** @initialized_domains: mask of all initialized domains */
>  	unsigned int initialized_domains;
>  	/** @domains: force wake domains */
> diff --git a/drivers/gpu/drm/xe/xe_gt.c b/drivers/gpu/drm/xe/xe_gt.c
> index 775c826b68b4..5ed96807fe34 100644
> --- a/drivers/gpu/drm/xe/xe_gt.c
> +++ b/drivers/gpu/drm/xe/xe_gt.c
> @@ -515,7 +515,10 @@ int xe_gt_init_early(struct xe_gt *gt)
>  
>  	xe_wa_process_gt_oob(gt);
>  
> -	xe_force_wake_init_gt(gt, gt_to_fw(gt));
> +	err = xe_force_wake_init_gt(gt, gt_to_fw(gt));
> +	if (err)
> +		return err;
> +
>  	spin_lock_init(&gt->global_invl_lock);
>  
>  	err = xe_gt_tlb_inval_init_early(gt);
> -- 
> 2.43.0
> 

  reply	other threads:[~2026-11-09 20:55 UTC|newest]

Thread overview: 17+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-17  3:18 [PATCH 0/3] drm/xe/forcewake: add delayed release for engine-cycle queries Zongyao Bai
2026-10-17  3:18 ` [PATCH 1/3] drm/xe/forcewake: add delayed release with hysteresis Zongyao Bai
2026-11-09 20:55   ` Rodrigo Vivi [this message]
2026-11-10  1:00     ` Bai, Zongyao
2026-10-17  3:18 ` [PATCH 2/3] drm/xe/forcewake: flush delayed release at power and teardown boundaries Zongyao Bai
2026-10-17  3:18 ` [PATCH 3/3] drm/xe/query: use delayed forcewake release for engine-cycle queries Zongyao Bai
2026-10-17  3:25 ` ✗ CI.checkpatch: warning for drm/xe/forcewake: add delayed " Patchwork
2026-10-17  3:27 ` ✓ CI.KUnit: success " Patchwork
2026-10-17  3:27 ` [PATCH 0/3] " Simon Richter
2026-10-17  4:07 ` ✓ Xe.CI.BAT: success for " Patchwork
2026-10-17  5:48 ` ✓ Xe.CI.FULL: " Patchwork
2026-11-10 22:05 ` [PATCH v2 0/3] " Zongyao Bai
2026-11-10 22:05   ` [PATCH v2 1/3] drm/xe/forcewake: add delayed release with hysteresis Zongyao Bai
2026-11-10 22:34     ` sashiko-bot
2026-11-10 22:05   ` [PATCH v2 2/3] drm/xe/forcewake: flush delayed release at power and teardown boundaries Zongyao Bai
2026-11-10 22:26     ` sashiko-bot
2026-11-10 22:05   ` [PATCH v2 3/3] drm/xe/query: use delayed forcewake release for engine-cycle queries Zongyao Bai

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=avIzOG78pyACjrCl@intel.com \
    --to=rodrigo.vivi@intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=zongyao.bai@intel.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox