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
prev 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