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(>->global_invl_lock);
>
> err = xe_gt_tlb_inval_init_early(gt);
> --
> 2.43.0
>
next prev parent 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