Intel-XE Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: S Sebinraj <s.sebinraj@intel.com>
To: intel-xe@lists.freedesktop.org
Cc: S Sebinraj <s.sebinraj@intel.corp-partner.google.com>,
	Carlos Santa <carlos.santa@intel.com>,
	Erin Park <erin.park@intel.com>, Ryan Neph <ryanneph@google.com>,
	Renato Pereyra <renatopereyra@google.com>,
	S Sebinraj <s.sebinraj@intel.com>
Subject: [PATCH 4/4] RFC: ANDROID: drm/xe: Gate mem_monitor wake ups on order 9 external fragmentation
Date: Mon,  7 Sep 2026 11:37:37 +0530	[thread overview]
Message-ID: <20260907060737.3636824-5-s.sebinraj@intel.com> (raw)
In-Reply-To: <20260907060737.3636824-1-s.sebinraj@intel.com>

From: S Sebinraj <s.sebinraj@intel.corp-partner.google.com>

xe_mem_monitor_notify_activity() previously woke the polling worker
based only on a raw byte-level delta (ACTIVITY_MIN_DELTA_BYTES), which
doesn't capture whether order 9 (2M) TTM allocations are actually
under any pressure.

Add xe_mem_monitor_zone_order9_free_bytes(), which reads the amount of
order 9 or larger free memory directly from struct zone's free_area
counts.

Add xe_mem_monitor_order9_supply_low(), which walks the populated zones
and returns true if any zone's order 9 free supply has dropped below
an adaptive floor. The floor is mon->max_delta_bytes, the largest
growth sample xe_mem_monitor_work() has ever observed on this device
clamped between the mem_monitor_order9_supply_min_mb and
mem_monitor_order9_supply_max_mb module parameters (in MiB).

Adapting the floor to the largest growth sample actually observed
avoids false positives from a single large but otherwise harmless
allocation burst when overall order 9 supply is still healthy.
The upper clamp (mem_monitor_order9_supply_max_mb) stops a single
anomalously large allocation from permanently pinning the floor too high.

Use this as an additional guard in xe_mem_monitor_notify_activity
both the existing byte-delta check and this free-supply check must
now indicate pressure before the worker is woken.

Test: Tested by running WLEU on a fragmented system and confirmed the
      worker is only woken when some populated zone's order-9 free
      supply drops below ORDER9_SUPPLY_MIN_PCT of total RAM.

Cc: Carlos Santa <carlos.santa@intel.com>
Cc: Erin Park <erin.park@intel.com>
Cc: Ryan Neph <ryanneph@google.com>
Cc: Renato Pereyra <renatopereyra@google.com>
Signed-off-by: S Sebinraj <s.sebinraj@intel.com>
---
 drivers/gpu/drm/xe/xe_mem_monitor.c | 115 +++++++++++++++++++++++++++-
 drivers/gpu/drm/xe/xe_mem_monitor.h |   2 +
 drivers/gpu/drm/xe/xe_module.c      |  22 ++++++
 drivers/gpu/drm/xe/xe_module.h      |   2 +
 4 files changed, 137 insertions(+), 4 deletions(-)

diff --git a/drivers/gpu/drm/xe/xe_mem_monitor.c b/drivers/gpu/drm/xe/xe_mem_monitor.c
index a34cafe56976..24458af2700c 100644
--- a/drivers/gpu/drm/xe/xe_mem_monitor.c
+++ b/drivers/gpu/drm/xe/xe_mem_monitor.c
@@ -59,9 +59,11 @@
 #include "xe_mem_monitor.h"
 
 #include <linux/atomic.h>
+#include <linux/compiler.h>
 #include <linux/debugfs.h>
 #include <linux/jiffies.h>
 #include <linux/minmax.h>
+#include <linux/mmzone.h>
 #include <linux/seq_file.h>
 #include <linux/slab.h>
 #include <linux/string.h>
@@ -129,6 +131,13 @@
  * @last_poll_jiffies:     jiffies value of the most recent real sample taken
  *                         by xe_mem_monitor_work(), used to detect and decay
  *                         the EMA across skipped (slept-through) intervals
+ * @max_delta_bytes:       largest clamped_delta (growth-only, per real
+ *                         sample) ever observed by xe_mem_monitor_work(),
+ *                         used by xe_mem_monitor_order9_supply_low() to
+ *                         adapt its floor upward to this workload's
+ *                         demonstrated behaviour (clamped between
+ *                         mem_monitor_order9_supply_min_mb and
+ *                         mem_monitor_order9_supply_max_mb there).
  * @suspended:             set while the device is suspended (system or
  *                         runtime PM); xe_mem_monitor_notify_activity() is a
  *                         no-op while set, so that BO eviction/restore
@@ -155,6 +164,7 @@ struct xe_mem_monitor_data {
 	unsigned long last_activity_jiffies;
 	unsigned long last_poll_jiffies;
 	bool is_high_throughput;
+	u64 max_delta_bytes;
 	atomic_t suspended;
 	bool was_pending_before_suspend;
 
@@ -186,6 +196,26 @@ static void xe_mem_monitor_set_latency_mode(struct xe_mem_monitor_data *mon)
 	trace_xe_gpu_mem_mode_switch(XE_GPU_MEM_MODE_LATENCY, mon->ema_growth_bytes);
 }
 
+/*
+ * Returns the total amount of memory (bytes) available as order-9 (2M) or
+ * larger contiguous blocks in the given zone. Computed directly from
+ * struct zone's free_area[] counts (see <linux/mmzone.h>)
+ */
+static u64 xe_mem_monitor_zone_order9_free_bytes(struct zone *zone)
+{
+	unsigned long free_blocks_suitable = 0;
+	unsigned int order;
+
+	for (order = 9; order < NR_PAGE_ORDERS; order++) {
+		/* nr_free is lockless/diagnostic-only */
+		unsigned long blocks = data_race(zone->free_area[order].nr_free);
+
+		free_blocks_suitable += blocks << (order - 9);
+	}
+
+	return (u64)free_blocks_suitable << (9 + PAGE_SHIFT);
+}
+
 static void xe_mem_monitor_work(struct work_struct *work)
 {
 	struct xe_mem_monitor_data *mon =
@@ -255,6 +285,8 @@ static void xe_mem_monitor_work(struct work_struct *work)
 				 ALPHA_RETAINED * mon->ema_growth_bytes)
 				>> ALPHA_SHIFT;
 
+	mon->max_delta_bytes = max_t(u64, mon->max_delta_bytes, clamped_delta);
+
 	atomic64_set(&mon->info.current_bytes, current_bytes);
 	atomic64_set(&mon->info.delta_signed, delta_signed);
 
@@ -348,6 +380,58 @@ void xe_mem_monitor_fini(struct xe_device *xe)
 	drm_info(&xe->drm, "xe_mem_monitor: stopped");
 }
 
+/*
+ * Companion check to ACTIVITY_MIN_DELTA_BYTES for
+ * xe_mem_monitor_notify_activity(): a large byte-level delta doesn't
+ * necessarily mean order-9 (2M) allocations are under any real pressure, so
+ * this looks instead at whether any single populated zone's absolute
+ * order-9-and-larger free supply has dropped below the adaptive floor
+ * clamp_t(mon->max_delta_bytes, mem_monitor_order9_supply_min_mb,
+ * mem_monitor_order9_supply_max_mb) independent of the size of whatever
+ * delta triggered this call, since a single large delta is not itself
+ * evidence that supply is actually running low. Taking mon->max_delta_bytes
+ * (the largest growth sample ever observed on this device, see
+ * xe_mem_monitor_work()) as the basis, clamped between the two module
+ * parameters, means the floor adapts upward to this workload's own
+ * demonstrated behaviour without being able to grow unbounded from a
+ * single anomalous allocation.
+ */
+#if IS_ENABLED(CONFIG_COMPACTION)
+static bool xe_mem_monitor_order9_supply_low(struct xe_mem_monitor_data *mon)
+{
+	struct xe_device *xe = mon->xe;
+	struct pglist_data *pgdat = NODE_DATA(0);
+	u64 min_free_bytes = clamp_t(u64, mon->max_delta_bytes,
+				     (u64)xe_modparam.mem_monitor_order9_supply_min_mb << 20,
+				     (u64)xe_modparam.mem_monitor_order9_supply_max_mb << 20);
+	unsigned int z;
+
+	for (z = 0; z < MAX_NR_ZONES; z++) {
+		struct zone *zone = &pgdat->node_zones[z];
+
+		if (!populated_zone(zone))
+			continue;
+
+		if (xe_mem_monitor_zone_order9_free_bytes(zone) < min_free_bytes) {
+			drm_dbg(&xe->drm,
+				"xe_mem_monitor: zone=%s order9_free=%llu MB < min=%llu MB\n",
+				zone->name,
+				xe_mem_monitor_zone_order9_free_bytes(zone) >> 20,
+				min_free_bytes >> 20);
+			return true;
+		}
+	}
+
+	return false;
+}
+#else
+static inline bool xe_mem_monitor_order9_supply_low(struct xe_mem_monitor_data *mon)
+{
+	/* No zone data available - don't gate on it. */
+	return true;
+}
+#endif
+
 /**
  * xe_mem_monitor_notify_activity - notify the monitor of GPU memory changes
  * @xe: the Xe device
@@ -367,6 +451,12 @@ void xe_mem_monitor_fini(struct xe_device *xe)
  * cross. If the worker is already scheduled (delayed_work_pending()), it
  * will read the live counter fresh when it runs and pick up whatever
  * accumulated, so there's no need to re-schedule again here.
+ *
+ * As a further guard, even a significant byte-level change is ignored unless
+ * xe_mem_monitor_order9_supply_low() also reports that some populated
+ * zone's order-9-and-larger free supply has dropped below the adaptive
+ * floor described there. A byte-level delta alone doesn't imply 2M
+ * allocations are under any real pressure.
  */
 void xe_mem_monitor_notify_activity(struct xe_device *xe)
 {
@@ -387,11 +477,14 @@ void xe_mem_monitor_notify_activity(struct xe_device *xe)
 	if (abs_delta < ACTIVITY_MIN_DELTA_BYTES)
 		return;
 
-	mon->last_activity_jiffies = jiffies;
-
 	if (delayed_work_pending(&mon->work))
 		return;
 
+	if (!xe_mem_monitor_order9_supply_low(mon))
+		return;
+
+	mon->last_activity_jiffies = jiffies;
+
 	schedule_delayed_work(&mon->work, 0);
 }
 
@@ -525,15 +618,29 @@ static int info_show(struct seq_file *s, void *data)
 {
 	struct xe_device *xe = s->private;
 	struct xe_mem_monitor_data *mon = xe->mem_monitor;
+	int mode = atomic_read(&xe->gpu_mem_mode);
+	struct pglist_data *pgdat = NODE_DATA(0);
+	unsigned long long order9_free_bytes = 0;
 
 	if (!mon)
 		return 1;
 
-	seq_printf(s, "current=%llu MB, delta=%lld MB, EMA=%llu MB mode=%d\n",
+	for (int z = 0; z < MAX_NR_ZONES; z++) {
+		struct zone *zone = &pgdat->node_zones[z];
+
+		if (!populated_zone(zone))
+			continue;
+		order9_free_bytes += xe_mem_monitor_zone_order9_free_bytes(zone);
+	}
+
+	seq_printf(s, "current=%llu MB, delta=%lld MB, EMA=%llu MB mode=%s, alpha=0.%03u, "
+		   "order9_free_total=%llu MB\n",
 		   atomic64_read(&mon->info.current_bytes) >> 20,
 		   atomic64_read(&mon->info.delta_signed) >> 20,
 		   mon->ema_growth_bytes >> 20,
-		   atomic_read(&xe->gpu_mem_mode));
+		   mode == XE_GPU_MEM_MODE_LATENCY ? "latency" : "throughput",
+		   (ALPHA_NUM * 1000U + (1U << (ALPHA_SHIFT - 1))) >> ALPHA_SHIFT,
+		   order9_free_bytes >> 20);
 	return 0;
 }
 DEFINE_SHOW_ATTRIBUTE(info);
diff --git a/drivers/gpu/drm/xe/xe_mem_monitor.h b/drivers/gpu/drm/xe/xe_mem_monitor.h
index c617c10c1a12..a30fabd5a8ea 100644
--- a/drivers/gpu/drm/xe/xe_mem_monitor.h
+++ b/drivers/gpu/drm/xe/xe_mem_monitor.h
@@ -28,6 +28,8 @@ enum xe_gpu_mem_mode {
 #define XE_MEM_MONITOR_DEFAULT_POLL_MS				2000
 #define XE_MEM_MONITOR_DEFAULT_DEBOUNCE_COUNT			5
 #define XE_MEM_MONITOR_DEFAULT_IDLE_TIMEOUT_MS			10000
+#define XE_MEM_MONITOR_DEFAULT_ORDER9_SUPPLY_MIN_MB		512
+#define XE_MEM_MONITOR_DEFAULT_ORDER9_SUPPLY_MAX_MB		1024
 
 /*
  * Minimum enforced value for mem_monitor_poll_ms (no maximum is
diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
index a38776734544..69825baf100c 100644
--- a/drivers/gpu/drm/xe/xe_module.c
+++ b/drivers/gpu/drm/xe/xe_module.c
@@ -54,6 +54,8 @@ struct xe_modparam xe_modparam = {
 	.mem_monitor_poll_ms = XE_MEM_MONITOR_DEFAULT_POLL_MS,
 	.mem_monitor_debounce_count = XE_MEM_MONITOR_DEFAULT_DEBOUNCE_COUNT,
 	.mem_monitor_idle_timeout_ms = XE_MEM_MONITOR_DEFAULT_IDLE_TIMEOUT_MS,
+	.mem_monitor_order9_supply_min_mb = XE_MEM_MONITOR_DEFAULT_ORDER9_SUPPLY_MIN_MB,
+	.mem_monitor_order9_supply_max_mb = XE_MEM_MONITOR_DEFAULT_ORDER9_SUPPLY_MAX_MB,
 #endif
 	/* the rest are 0 by default */
 };
@@ -162,6 +164,26 @@ MODULE_PARM_DESC(mem_monitor_idle_timeout_ms,
 		 "mode after which the monitor's polling worker goes to sleep "
 		 "(woken again on the next allocation) [default="
 		 __stringify(XE_MEM_MONITOR_DEFAULT_IDLE_TIMEOUT_MS) "])");
+
+module_param_named(mem_monitor_order9_supply_min_mb,
+		   xe_modparam.mem_monitor_order9_supply_min_mb, uint, 0644);
+MODULE_PARM_DESC(mem_monitor_order9_supply_min_mb,
+		 "Minimum order-9-and-larger free supply, in MiB, required per "
+		 "populated zone before a significant GPU memory change wakes the "
+		 "monitor's polling worker. The effective floor actually used is "
+		 "the largest single growth sample ever observed by the worker, "
+		 "clamped between this value and mem_monitor_order9_supply_max_mb, "
+		 "so it adapts to this workload's own demonstrated behaviour without "
+		 "growing unbounded from a single anomalous allocation "
+		 "[default=" __stringify(XE_MEM_MONITOR_DEFAULT_ORDER9_SUPPLY_MIN_MB) "])");
+
+module_param_named(mem_monitor_order9_supply_max_mb,
+		   xe_modparam.mem_monitor_order9_supply_max_mb, uint, 0644);
+MODULE_PARM_DESC(mem_monitor_order9_supply_max_mb,
+		 "Upper bound, in MiB, on the adaptive order-9 free supply floor "
+		 "described under mem_monitor_order9_supply_min_mb - caps how high "
+		 "a single unusually large growth sample can permanently push that "
+		 "floor [default=" __stringify(XE_MEM_MONITOR_DEFAULT_ORDER9_SUPPLY_MAX_MB) "])");
 #endif
 
 static int xe_check_nomodeset(void)
diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h
index 24e0b52e48ca..19d0de96e078 100644
--- a/drivers/gpu/drm/xe/xe_module.h
+++ b/drivers/gpu/drm/xe/xe_module.h
@@ -38,6 +38,8 @@ struct xe_modparam {
 	u32 mem_monitor_poll_ms;
 	u32 mem_monitor_debounce_count;
 	u32 mem_monitor_idle_timeout_ms;
+	u32 mem_monitor_order9_supply_min_mb;
+	u32 mem_monitor_order9_supply_max_mb;
 #endif
 };
 
-- 
2.34.1


      parent reply	other threads:[~2026-09-07  6:47 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-07  6:07 [PATCH 0/4] RFC: drm/xe: Dynamic Xe Memory Monitor S Sebinraj
2026-09-07  6:07 ` [PATCH 1/4] RFC: ANDROID: drm/xe: add adaptive GPU memory growth monitor xe_mem_monitor S Sebinraj
2026-09-07  6:07 ` [PATCH 2/4] RFC: ANDROID: drm/xe: register ttm page alloc vendor hooks internally S Sebinraj
2026-09-07  6:07 ` [PATCH 3/4] RFC: ANDROID: drm/xe: provide safe mem_monitor enable toggle via debugfs S Sebinraj
2026-09-07  6:07 ` S Sebinraj [this message]

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=20260907060737.3636824-5-s.sebinraj@intel.com \
    --to=s.sebinraj@intel.com \
    --cc=carlos.santa@intel.com \
    --cc=erin.park@intel.com \
    --cc=intel-xe@lists.freedesktop.org \
    --cc=renatopereyra@google.com \
    --cc=ryanneph@google.com \
    --cc=s.sebinraj@intel.corp-partner.google.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