Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: "Bai, Zongyao" <zongyao.bai@intel.com>
To: Rodrigo Vivi <rodrigo.vivi@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 17:00:19 -0800	[thread overview]
Message-ID: <7053c11b-e67e-4955-a07f-aa9bea190b8f@intel.com> (raw)
In-Reply-To: <avIzOG78pyACjrCl@intel.com>

Thank you Vivi for the reviewing!

On 11/9/2026 12:55 PM, Rodrigo Vivi wrote:
> 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()

Got it.
will change to: XE_FORCE_WAKE_HOLD_MS

>
>> +
>>   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?

Get it.  Will change to:

  __xe_force_wake_put(fw, ref, unsigned int hold_ms)
hold_ms = 0 /* sleep immediately */
hold_ms = XE_FORCE_WAKE_HOLD_MS   /* delayed release */

>
>> +{
>> +	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...

Yeah, it's not worth to save a parameter here.

Got it, will change to:

+        if (hold_ms && fw->delayed_enabled) {
+            unsigned long hold = msecs_to_jiffies(hold_ms);
+
+            domain->hold_until = jiffies + hold;
              fw->delayed_domains |= BIT(domain->id);
+            queue_delayed_work(system_percpu_wq, 
&fw->delayed_release_work, hold);

And init it as true, and
in  xe_force_wake_stop()  ->  fw->delayed_enabled = false;
in xe_force_wake_disable_work() ->  fw->delayed_enabled = false;

>
>> +				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?

Agree! It's much better, will change to:

+   if (!xe_pm_runtime_get_if_active(xe))
+       return;

>
>> +
>> +	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-10  1:00 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
2026-11-10  1:00     ` Bai, Zongyao [this message]
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=7053c11b-e67e-4955-a07f-aa9bea190b8f@intel.com \
    --to=zongyao.bai@intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=rodrigo.vivi@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