* [RFC PATCH] drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms
@ 2026-10-28 4:50 Daniel Colascione
2026-10-28 13:23 ` sashiko-bot
2026-10-29 15:16 ` Matthew Brost
0 siblings, 2 replies; 4+ messages in thread
From: Daniel Colascione @ 2026-10-28 4:50 UTC (permalink / raw)
To: intel-xe; +Cc: Matthew Brost, Thomas Hellstrom, Rodrigo Vivi, dri-devel
We initialize the GGTT GPU page tables on startup. On my LNL, doing
this one PTE at a time through the BAR takes about 900 ms and involves
a million small writes.
Use the GPU's copy engine to fill the PTEs in batches. We build
MI_UPDATE_GTT commands in normal memory, then send them to the GPU for
execution. This way, we avoid the per-PTE MMIO write and do the PTE fill
in 36.77 ms, ~24x faster.
The new path is enabled by default. xe.ggtt_init_gpu=0 uses the old
CPU-driven path.
LNL-only because I can test only LNL. Might it work elsewhere?
Signed-off-by: Daniel Colascione <dancol@dancol.org>
---
drivers/gpu/drm/xe/instructions/xe_mi_commands.h | 2 +
drivers/gpu/drm/xe/xe_ggtt.c | 128 +++++++++++++++++++++--
drivers/gpu/drm/xe/xe_ggtt.h | 1 +
drivers/gpu/drm/xe/xe_gt.c | 3 +-
drivers/gpu/drm/xe/xe_module.c | 5 +
drivers/gpu/drm/xe/xe_module.h | 1 +
6 files changed, 130 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/xe/instructions/xe_mi_commands.h b/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
index 835ef75bbeb1..04bd4225adef 100644
--- a/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
+++ b/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
@@ -62,6 +62,8 @@
#define MI_LRI_FORCE_POSTED REG_BIT(12)
#define MI_LRI_LEN(x) (((x) & 0xff) + 1)
+#define MI_UPDATE_GTT __MI_INSTR(0x23)
+
#define MI_STORE_REGISTER_MEM (__MI_INSTR(0x24) | XE_INSTR_NUM_DW(4))
#define MI_SRM_USE_GGTT REG_BIT(22)
#define MI_SRM_ADD_CS_OFFSET REG_BIT(19)
diff --git a/drivers/gpu/drm/xe/xe_ggtt.c b/drivers/gpu/drm/xe/xe_ggtt.c
index 773777afc68a..3737d085ae68 100644
--- a/drivers/gpu/drm/xe/xe_ggtt.c
+++ b/drivers/gpu/drm/xe/xe_ggtt.c
@@ -15,20 +15,25 @@
#include <drm/intel/i915_drm.h>
#include <generated/xe_wa_oob.h>
+#include "instructions/xe_mi_commands.h"
#include "regs/xe_gt_regs.h"
#include "regs/xe_gtt_defs.h"
#include "regs/xe_regs.h"
#include "xe_assert.h"
+#include "xe_bb.h"
#include "xe_bo.h"
+#include "xe_exec_queue.h"
#include "xe_gt.h"
#include "xe_gt_printk.h"
#include "xe_gt_types.h"
#include "xe_log.h"
#include "xe_map.h"
#include "xe_mmio.h"
+#include "xe_module.h"
#include "xe_pat.h"
#include "xe_pm.h"
#include "xe_res_cursor.h"
+#include "xe_sched_job.h"
#include "xe_sriov.h"
#include "xe_tile_printk.h"
#include "xe_tile_sriov_vf.h"
@@ -384,9 +389,9 @@ static void dev_fini_ggtt(void *arg)
* @ggtt: the &xe_ggtt to be initialized
*
* It allows to create new mappings usable by the GuC.
- * Mappings are not usable by the HW engines, as it doesn't have scratch nor
- * initial clear done to it yet. That will happen in the regular, non-early
- * GGTT initialization.
+ * HW engines may only access explicitly mapped bootstrap buffers until the
+ * remaining holes have been cleared. Normally xe_ggtt_init() does this;
+ * the GPU clear finishes this work in xe_ggtt_init_late().
*
* Return: 0 on success or a negative error code on failure.
*/
@@ -456,18 +461,119 @@ ALLOW_ERROR_INJECTION(xe_ggtt_init_early, ERRNO); /* See xe_pci_probe() */
static void xe_ggtt_invalidate(struct xe_ggtt *ggtt);
static void xe_ggtt_invalidate_engine(struct xe_ggtt *ggtt);
-static void xe_ggtt_initial_clear(struct xe_ggtt *ggtt)
+static bool ggtt_use_gpu(struct xe_device *xe)
{
+ return xe_modparam.ggtt_init_gpu && xe->info.platform == XE_LUNARLAKE;
+}
+
+/*
+ * MI_UPDATE_GTT takes at most 511 inline PTEs (ACM PRM, Vol. 2a,
+ * pp. 1096-1097). Limit batches to 512K to fit the kernel batch pool.
+ */
+#define GGTT_INIT_PTES_PER_PACKET 511
+#define GGTT_INIT_BATCH_DWORDS (128 * (2 + 2 * GGTT_INIT_PTES_PER_PACKET))
+
+static int ggtt_clear_gpu(struct xe_exec_queue *q, struct xe_bb *bb,
+ u64 pte, u64 start, u64 end)
+{
+ while (start < end) {
+ struct xe_sched_job *job;
+ int err;
+
+ bb->len = 0;
+ while (start < end && bb->len < GGTT_INIT_BATCH_DWORDS) {
+ u32 count = min_t(u64, (end - start) >> XE_PTE_SHIFT,
+ GGTT_INIT_PTES_PER_PACKET);
+
+ bb->cs[bb->len++] = MI_UPDATE_GTT | (2 * count);
+ bb->cs[bb->len++] = start;
+ memset64((u64 *)&bb->cs[bb->len], pte, count);
+ bb->len += 2 * count;
+ start += (u64)count << XE_PTE_SHIFT;
+ }
+
+ job = xe_bb_create_job(q, bb);
+ if (IS_ERR(job))
+ return PTR_ERR(job);
+
+ xe_sched_job_arm(job);
+ xe_sched_job_get(job);
+ xe_sched_job_push(job);
+
+ dma_fence_wait(&job->drm.s_fence->finished, false);
+ err = dma_fence_get_status(&job->drm.s_fence->finished);
+ xe_sched_job_put(job);
+ if (err < 0)
+ return err;
+ }
+
+ return 0;
+}
+
+static int xe_ggtt_initial_clear(struct xe_ggtt *ggtt, struct xe_hw_engine *hwe)
+{
+ struct xe_device *xe = tile_to_xe(ggtt->tile);
+ struct xe_exec_queue *q = NULL;
+ struct xe_bb *bb = NULL;
struct drm_mm_node *hole;
- u64 start, end;
+ u16 pat_index = xe_cache_pat_idx(xe, XE_CACHE_WB);
+ u64 start, end, pte = 0;
+ int err = 0;
+
+ if (hwe) {
+ q = xe_exec_queue_create(xe, NULL, BIT(hwe->logical_instance), 1,
+ hwe, EXEC_QUEUE_FLAG_KERNEL, 0);
+ if (IS_ERR(q))
+ return PTR_ERR(q);
+
+ bb = xe_bb_new(hwe->gt, GGTT_INIT_BATCH_DWORDS, false);
+ if (IS_ERR(bb)) {
+ err = PTR_ERR(bb);
+ goto put_queue;
+ }
+ }
- /* Display may have allocated inside ggtt, so be careful with clearing here */
+ if (ggtt->scratch)
+ pte = xe_bo_addr(ggtt->scratch, 0, XE_PAGE_SIZE) |
+ ggtt->pt_ops->pte_encode_flags(ggtt->scratch, pat_index);
+
+ /* Preserve boot mappings and exclude allocator changes until completion. */
mutex_lock(&ggtt->lock);
- drm_mm_for_each_hole(hole, &ggtt->mm, start, end)
- xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
+ drm_mm_for_each_hole(hole, &ggtt->mm, start, end) {
+ if (q)
+ err = ggtt_clear_gpu(q, bb, pte, ggtt->start + start, ggtt->start + end);
+ else
+ xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
+ if (err)
+ goto unlock;
+ }
xe_ggtt_invalidate(ggtt);
+unlock:
mutex_unlock(&ggtt->lock);
+ xe_bb_free(bb, NULL);
+put_queue:
+ if (q)
+ xe_exec_queue_put(q);
+ return err;
+}
+
+/**
+ * xe_ggtt_init_late - Finish deferred GGTT initialization after GuC startup
+ * @ggtt: GGTT to initialize
+ *
+ * All default contexts must be recorded before waiting for a GPU clear with
+ * the GGTT mutex held, so reset recovery need not allocate context mappings.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+int xe_ggtt_init_late(struct xe_ggtt *ggtt)
+{
+ if (!ggtt_use_gpu(tile_to_xe(ggtt->tile)))
+ return 0;
+
+ return xe_ggtt_initial_clear(ggtt, xe_gt_hw_engine(ggtt->tile->primary_gt,
+ XE_ENGINE_CLASS_COPY, 0, false));
}
static void ggtt_node_fini(struct xe_ggtt_node *node)
@@ -536,6 +642,9 @@ void xe_ggtt_node_remove(struct xe_ggtt_node *node, bool invalidate)
* xe_ggtt_init - Regular non-early GGTT initialization
* @ggtt: the &xe_ggtt to be initialized
*
+ * Allocate the scratch page if needed and clear unused entries, or defer
+ * the clear to xe_ggtt_init_late() when GPU initialization is enabled.
+ *
* Return: 0 on success or a negative error code on failure.
*/
int xe_ggtt_init(struct xe_ggtt *ggtt)
@@ -572,7 +681,8 @@ int xe_ggtt_init(struct xe_ggtt *ggtt)
xe_bo_size(ggtt->scratch));
}
- xe_ggtt_initial_clear(ggtt);
+ if (!ggtt_use_gpu(xe))
+ xe_ggtt_initial_clear(ggtt, NULL);
return devm_add_action_or_reset(xe->drm.dev, ggtt_fini, ggtt);
err:
diff --git a/drivers/gpu/drm/xe/xe_ggtt.h b/drivers/gpu/drm/xe/xe_ggtt.h
index c864cc975a69..cae8600d7142 100644
--- a/drivers/gpu/drm/xe/xe_ggtt.h
+++ b/drivers/gpu/drm/xe/xe_ggtt.h
@@ -17,6 +17,7 @@ struct xe_ggtt *xe_ggtt_alloc(struct xe_tile *tile);
int xe_ggtt_init_early(struct xe_ggtt *ggtt);
int xe_ggtt_init_kunit(struct xe_ggtt *ggtt, u32 reserved, u32 size);
int xe_ggtt_init(struct xe_ggtt *ggtt);
+int xe_ggtt_init_late(struct xe_ggtt *ggtt);
void xe_ggtt_shift_nodes(struct xe_ggtt *ggtt, u64 new_base);
u64 xe_ggtt_start(struct xe_ggtt *ggtt);
diff --git a/drivers/gpu/drm/xe/xe_gt.c b/drivers/gpu/drm/xe/xe_gt.c
index 45fb074812a2..d1c14a02de66 100644
--- a/drivers/gpu/drm/xe/xe_gt.c
+++ b/drivers/gpu/drm/xe/xe_gt.c
@@ -686,7 +686,8 @@ static int gt_init_with_all_forcewake(struct xe_gt *gt)
if (err)
return err;
- return 0;
+ return xe_gt_is_main_type(gt) ?
+ xe_ggtt_init_late(gt_to_tile(gt)->mem.ggtt) : 0;
}
static void xe_gt_fini(void *arg)
diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
index 897724cb5cfb..44cc0107cf53 100644
--- a/drivers/gpu/drm/xe/xe_module.c
+++ b/drivers/gpu/drm/xe/xe_module.c
@@ -22,6 +22,7 @@
#include "xe_sched_job.h"
struct xe_modparam xe_modparam = {
+ .ggtt_init_gpu = true,
.probe_display = XE_DEFAULT_PROBE_DISPLAY,
.guc_log_level = XE_DEFAULT_GUC_LOG_LEVEL,
.force_probe = XE_DEFAULT_FORCE_PROBE,
@@ -34,6 +35,10 @@ struct xe_modparam xe_modparam = {
/* the rest are 0 by default */
};
+module_param_named(ggtt_init_gpu, xe_modparam.ggtt_init_gpu, bool, 0444);
+MODULE_PARM_DESC(ggtt_init_gpu,
+ "GPU GGTT initialization on Lunar Lake (default: true)");
+
module_param_named(svm_notifier_size, xe_modparam.svm_notifier_size, uint, 0600);
MODULE_PARM_DESC(svm_notifier_size, "Set the svm notifier size in MiB, must be power of 2 "
"[default=" __stringify(XE_DEFAULT_SVM_NOTIFIER_SIZE) "]");
diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h
index 6272d9e41207..8aa91822c35c 100644
--- a/drivers/gpu/drm/xe/xe_module.h
+++ b/drivers/gpu/drm/xe/xe_module.h
@@ -12,6 +12,7 @@ struct work_struct;
/* Module modprobe variables */
struct xe_modparam {
+ bool ggtt_init_gpu;
bool probe_display;
int force_vram_bar_size;
int guc_log_level;
^ permalink raw reply related [flat|nested] 4+ messages in thread
* Re: [RFC PATCH] drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms
2026-10-28 4:50 [RFC PATCH] drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms Daniel Colascione
@ 2026-10-28 13:23 ` sashiko-bot
2026-10-29 15:16 ` Matthew Brost
1 sibling, 0 replies; 4+ messages in thread
From: sashiko-bot @ 2026-10-28 13:23 UTC (permalink / raw)
To: Daniel Colascione, intel-xe; +Cc: dri-devel
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [High] drm/xe/ggtt: fs_reclaim lock inversion in ggtt_clear_gpu() under ggtt->lock
- [Low] drm/xe: missing kernel-parameters.txt entry for xe.ggtt_init_gpu
--
commit ed4c9843bc7f8d6478d283f57c556b54c7d8a262
Author: Daniel Colascione <dancol@dancol.org>
drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms
This patch uses the GPU copy engine to initialize GGTT page tables in batches
via MI_UPDATE_GTT commands on Lunar Lake, deferring the initial GGTT hole
clear to xe_ggtt_init_late() after GuC startup. It also adds the
xe.ggtt_init_gpu module parameter to toggle this behavior.
> diff --git a/drivers/gpu/drm/xe/xe_ggtt.c b/drivers/gpu/drm/xe/xe_ggtt.c
> index 9ef2ce743770f..47d12e5e95458 100644
> --- a/drivers/gpu/drm/xe/xe_ggtt.c
> +++ b/drivers/gpu/drm/xe/xe_ggtt.c
[ ... ]
> @@ -456,18 +461,119 @@ ALLOW_ERROR_INJECTION(xe_ggtt_init_early, ERRNO); /* See xe_pci_probe() */
[ ... ]
> +static int ggtt_clear_gpu(struct xe_exec_queue *q, struct xe_bb *bb,
> + u64 pte, u64 start, u64 end)
> +{
> + while (start < end) {
> + struct xe_sched_job *job;
> + int err;
> +
> + bb->len = 0;
> + while (start < end && bb->len < GGTT_INIT_BATCH_DWORDS) {
> + u32 count = min_t(u64, (end - start) >> XE_PTE_SHIFT,
> + GGTT_INIT_PTES_PER_PACKET);
> +
> + bb->cs[bb->len++] = MI_UPDATE_GTT | (2 * count);
> + bb->cs[bb->len++] = start;
> + memset64((u64 *)&bb->cs[bb->len], pte, count);
> + bb->len += 2 * count;
> + start += (u64)count << XE_PTE_SHIFT;
> + }
> +
> + job = xe_bb_create_job(q, bb);
> + if (IS_ERR(job))
> + return PTR_ERR(job);
[ ... ]
> +static int xe_ggtt_initial_clear(struct xe_ggtt *ggtt, struct xe_hw_engine *hwe)
> +{
[ ... ]
> + /* Preserve boot mappings and exclude allocator changes until completion. */
> mutex_lock(&ggtt->lock);
> - drm_mm_for_each_hole(hole, &ggtt->mm, start, end)
> - xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
> + drm_mm_for_each_hole(hole, &ggtt->mm, start, end) {
> + if (q)
> + err = ggtt_clear_gpu(q, bb, pte, ggtt->start + start, ggtt->start + end);
> + else
> + xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
> + if (err)
> + goto unlock;
> + }
[Severity: High]
Does calling ggtt_clear_gpu() while holding ggtt->lock cause an fs_reclaim
lock inversion?
During allocation in xe_ggtt_alloc(), primelockdep() in
drivers/gpu/drm/xe/xe_ggtt.c establishes the ordering fs_reclaim ->
ggtt->lock because BO destruction during memory reclaim acquires ggtt->lock:
static void primelockdep(struct xe_ggtt *ggtt)
{
if (!IS_ENABLED(CONFIG_LOCKDEP))
return;
fs_reclaim_acquire(GFP_KERNEL);
might_lock(&ggtt->lock);
fs_reclaim_release(GFP_KERNEL);
}
However, on Lunar Lake with xe.ggtt_init_gpu enabled,
gt_init_with_all_forcewake() -> xe_ggtt_init_late() -> xe_ggtt_initial_clear()
acquires ggtt->lock and calls ggtt_clear_gpu(), which invokes
xe_bb_create_job() -> __xe_bb_create_job() -> xe_sched_job_create().
Inside xe_sched_job_create(), several GFP_KERNEL allocations occur under
ggtt->lock, including job_alloc() in drivers/gpu/drm/xe/xe_sched_job.c:
static struct xe_sched_job *job_alloc(bool parallel)
{
return kmem_cache_zalloc(parallel ? xe_sched_job_parallel_slab :
xe_sched_job_slab, GFP_KERNEL);
}
as well as drm_sched_job_init() and xe_lrc_alloc_seqno_fence() ->
xe_hw_fence_alloc() -> fence_alloc() in drivers/gpu/drm/xe/xe_hw_fence.c:
return kmem_cache_zalloc(xe_hw_fence_slab, GFP_KERNEL);
Wouldn't this violate the fs_reclaim -> ggtt->lock hierarchy, triggering a
lockdep circular locking warning under CONFIG_PROVE_LOCKING during probe and
risking a reclaim deadlock if direct reclaim attempts to free a GGTT-mapped
BO?
> diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
> index 897724cb5cfb8..44cc0107cf53a 100644
> --- a/drivers/gpu/drm/xe/xe_module.c
> +++ b/drivers/gpu/drm/xe/xe_module.c
> @@ -22,6 +22,7 @@
> #include "xe_sched_job.h"
>
> struct xe_modparam xe_modparam = {
> + .ggtt_init_gpu = true,
> .probe_display = XE_DEFAULT_PROBE_DISPLAY,
> .guc_log_level = XE_DEFAULT_GUC_LOG_LEVEL,
> .force_probe = XE_DEFAULT_FORCE_PROBE,
> @@ -34,6 +35,10 @@ struct xe_modparam xe_modparam = {
> /* the rest are 0 by default */
> };
>
> +module_param_named(ggtt_init_gpu, xe_modparam.ggtt_init_gpu, bool, 0444);
> +MODULE_PARM_DESC(ggtt_init_gpu,
> + "GPU GGTT initialization on Lunar Lake (default: true)");
[Severity: Low]
Should this new xe.ggtt_init_gpu module parameter also be documented in
Documentation/admin-guide/kernel-parameters.txt?
--
Sashiko AI review · https://sashiko.dev/#/patchset/87v76m1l9k.fsf@dancol.org?part=1
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [RFC PATCH] drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms
2026-10-28 4:50 [RFC PATCH] drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms Daniel Colascione
2026-10-28 13:23 ` sashiko-bot
@ 2026-10-29 15:16 ` Matthew Brost
2026-10-29 15:31 ` Matthew Brost
1 sibling, 1 reply; 4+ messages in thread
From: Matthew Brost @ 2026-10-29 15:16 UTC (permalink / raw)
To: Daniel Colascione; +Cc: intel-xe, Thomas Hellstrom, Rodrigo Vivi, dri-devel
On Tue, Oct 27, 2026 at 09:50:31PM -0700, Daniel Colascione wrote:
Thanks for this - I have wondered why when I probe Xe it takes a
noticable amount of time - I suspect this is why.
> We initialize the GGTT GPU page tables on startup. On my LNL, doing
> this one PTE at a time through the BAR takes about 900 ms and involves
> a million small writes.
>
> Use the GPU's copy engine to fill the PTEs in batches. We build
> MI_UPDATE_GTT commands in normal memory, then send them to the GPU for
> execution. This way, we avoid the per-PTE MMIO write and do the PTE fill
> in 36.77 ms, ~24x faster.
>
> The new path is enabled by default. xe.ggtt_init_gpu=0 uses the old
> CPU-driven path.
>
> LNL-only because I can test only LNL. Might it work elsewhere?
>
It should work anywhere.
> Signed-off-by: Daniel Colascione <dancol@dancol.org>
> ---
> drivers/gpu/drm/xe/instructions/xe_mi_commands.h | 2 +
> drivers/gpu/drm/xe/xe_ggtt.c | 128 +++++++++++++++++++++--
> drivers/gpu/drm/xe/xe_ggtt.h | 1 +
> drivers/gpu/drm/xe/xe_gt.c | 3 +-
> drivers/gpu/drm/xe/xe_module.c | 5 +
> drivers/gpu/drm/xe/xe_module.h | 1 +
> 6 files changed, 130 insertions(+), 10 deletions(-)
>
> diff --git a/drivers/gpu/drm/xe/instructions/xe_mi_commands.h b/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
> index 835ef75bbeb1..04bd4225adef 100644
> --- a/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
> +++ b/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
> @@ -62,6 +62,8 @@
> #define MI_LRI_FORCE_POSTED REG_BIT(12)
> #define MI_LRI_LEN(x) (((x) & 0xff) + 1)
>
> +#define MI_UPDATE_GTT __MI_INSTR(0x23)
> +
> #define MI_STORE_REGISTER_MEM (__MI_INSTR(0x24) | XE_INSTR_NUM_DW(4))
> #define MI_SRM_USE_GGTT REG_BIT(22)
> #define MI_SRM_ADD_CS_OFFSET REG_BIT(19)
> diff --git a/drivers/gpu/drm/xe/xe_ggtt.c b/drivers/gpu/drm/xe/xe_ggtt.c
> index 773777afc68a..3737d085ae68 100644
> --- a/drivers/gpu/drm/xe/xe_ggtt.c
> +++ b/drivers/gpu/drm/xe/xe_ggtt.c
> @@ -15,20 +15,25 @@
> #include <drm/intel/i915_drm.h>
> #include <generated/xe_wa_oob.h>
>
> +#include "instructions/xe_mi_commands.h"
> #include "regs/xe_gt_regs.h"
> #include "regs/xe_gtt_defs.h"
> #include "regs/xe_regs.h"
> #include "xe_assert.h"
> +#include "xe_bb.h"
> #include "xe_bo.h"
> +#include "xe_exec_queue.h"
> #include "xe_gt.h"
> #include "xe_gt_printk.h"
> #include "xe_gt_types.h"
> #include "xe_log.h"
> #include "xe_map.h"
> #include "xe_mmio.h"
> +#include "xe_module.h"
> #include "xe_pat.h"
> #include "xe_pm.h"
> #include "xe_res_cursor.h"
> +#include "xe_sched_job.h"
> #include "xe_sriov.h"
> #include "xe_tile_printk.h"
> #include "xe_tile_sriov_vf.h"
> @@ -384,9 +389,9 @@ static void dev_fini_ggtt(void *arg)
> * @ggtt: the &xe_ggtt to be initialized
> *
> * It allows to create new mappings usable by the GuC.
> - * Mappings are not usable by the HW engines, as it doesn't have scratch nor
> - * initial clear done to it yet. That will happen in the regular, non-early
> - * GGTT initialization.
> + * HW engines may only access explicitly mapped bootstrap buffers until the
> + * remaining holes have been cleared. Normally xe_ggtt_init() does this;
> + * the GPU clear finishes this work in xe_ggtt_init_late().
> *
> * Return: 0 on success or a negative error code on failure.
> */
> @@ -456,18 +461,119 @@ ALLOW_ERROR_INJECTION(xe_ggtt_init_early, ERRNO); /* See xe_pci_probe() */
> static void xe_ggtt_invalidate(struct xe_ggtt *ggtt);
> static void xe_ggtt_invalidate_engine(struct xe_ggtt *ggtt);
>
> -static void xe_ggtt_initial_clear(struct xe_ggtt *ggtt)
> +static bool ggtt_use_gpu(struct xe_device *xe)
> {
> + return xe_modparam.ggtt_init_gpu && xe->info.platform == XE_LUNARLAKE;
Per below (last reply) - let's just turn this everywhere in platform
config.
> +}
> +
> +/*
> + * MI_UPDATE_GTT takes at most 511 inline PTEs (ACM PRM, Vol. 2a,
> + * pp. 1096-1097). Limit batches to 512K to fit the kernel batch pool.
> + */
> +#define GGTT_INIT_PTES_PER_PACKET 511
> +#define GGTT_INIT_BATCH_DWORDS (128 * (2 + 2 * GGTT_INIT_PTES_PER_PACKET))
> +
> +static int ggtt_clear_gpu(struct xe_exec_queue *q, struct xe_bb *bb,
> + u64 pte, u64 start, u64 end)
s/ggtt_clear_gpu/xe_ggtt_clear_gpu
> +{
> + while (start < end) {
> + struct xe_sched_job *job;
> + int err;
> +
> + bb->len = 0;
> + while (start < end && bb->len < GGTT_INIT_BATCH_DWORDS) {
> + u32 count = min_t(u64, (end - start) >> XE_PTE_SHIFT,
> + GGTT_INIT_PTES_PER_PACKET);
> +
> + bb->cs[bb->len++] = MI_UPDATE_GTT | (2 * count);
> + bb->cs[bb->len++] = start;
> + memset64((u64 *)&bb->cs[bb->len], pte, count);
> + bb->len += 2 * count;
> + start += (u64)count << XE_PTE_SHIFT;
> + }
> +
> + job = xe_bb_create_job(q, bb);
> + if (IS_ERR(job))
> + return PTR_ERR(job);
> +
> + xe_sched_job_arm(job);
> + xe_sched_job_get(job);
> + xe_sched_job_push(job);
> +
> + dma_fence_wait(&job->drm.s_fence->finished, false);
You can pipeline this function.
i.e., generate fences in a loop and only wait on the last one.
This is fairly common pattern, xe_svm_copy is an example of function
with loop which issues a bunch of jobs and only waits on the very last
fence.
> + err = dma_fence_get_status(&job->drm.s_fence->finished);
> + xe_sched_job_put(job);
> + if (err < 0)
> + return err;
> + }
> +
> + return 0;
> +}
> +
> +static int xe_ggtt_initial_clear(struct xe_ggtt *ggtt, struct xe_hw_engine *hwe)
> +{
> + struct xe_device *xe = tile_to_xe(ggtt->tile);
> + struct xe_exec_queue *q = NULL;
> + struct xe_bb *bb = NULL;
> struct drm_mm_node *hole;
> - u64 start, end;
> + u16 pat_index = xe_cache_pat_idx(xe, XE_CACHE_WB);
> + u64 start, end, pte = 0;
> + int err = 0;
> +
> + if (hwe) {
> + q = xe_exec_queue_create(xe, NULL, BIT(hwe->logical_instance), 1,
> + hwe, EXEC_QUEUE_FLAG_KERNEL, 0);
Can we use migration queue (xe_migrate.c)? We'd probably need a bit of
layering rework to allow another layer like GGTT to safely use this
queue but that seems better to keep a single isolated kernel queue.
> + if (IS_ERR(q))
> + return PTR_ERR(q);
> +
> + bb = xe_bb_new(hwe->gt, GGTT_INIT_BATCH_DWORDS, false);
> + if (IS_ERR(bb)) {
> + err = PTR_ERR(bb);
> + goto put_queue;
> + }
> + }
>
> - /* Display may have allocated inside ggtt, so be careful with clearing here */
> + if (ggtt->scratch)
> + pte = xe_bo_addr(ggtt->scratch, 0, XE_PAGE_SIZE) |
> + ggtt->pt_ops->pte_encode_flags(ggtt->scratch, pat_index);
> +
> + /* Preserve boot mappings and exclude allocator changes until completion. */
> mutex_lock(&ggtt->lock);
CI and Sashiko correctly complain that 'ggtt->lock' is tainted with
reclaim and ggtt_clear_gpu will be a malloc for the GPU job.
I'm actually questioning if this taint is actually valid. afiak we never
shrink or evict anything GGTT mapped (i.e., that memory is always
pinned). So I think we can just drop the taint - I'd do this in a
standalone patch.
> - drm_mm_for_each_hole(hole, &ggtt->mm, start, end)
> - xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
> + drm_mm_for_each_hole(hole, &ggtt->mm, start, end) {
> + if (q)
> + err = ggtt_clear_gpu(q, bb, pte, ggtt->start + start, ggtt->start + end);
> + else
> + xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
> + if (err)
> + goto unlock;
> + }
>
> xe_ggtt_invalidate(ggtt);
> +unlock:
> mutex_unlock(&ggtt->lock);
> + xe_bb_free(bb, NULL);
> +put_queue:
> + if (q)
> + xe_exec_queue_put(q);
> + return err;
> +}
> +
> +/**
> + * xe_ggtt_init_late - Finish deferred GGTT initialization after GuC startup
> + * @ggtt: GGTT to initialize
> + *
> + * All default contexts must be recorded before waiting for a GPU clear with
> + * the GGTT mutex held, so reset recovery need not allocate context mappings.
> + *
> + * Return: 0 on success or a negative error code on failure.
> + */
> +int xe_ggtt_init_late(struct xe_ggtt *ggtt)
> +{
> + if (!ggtt_use_gpu(tile_to_xe(ggtt->tile)))
> + return 0;
> +
> + return xe_ggtt_initial_clear(ggtt, xe_gt_hw_engine(ggtt->tile->primary_gt,
> + XE_ENGINE_CLASS_COPY, 0, false));
> }
>
> static void ggtt_node_fini(struct xe_ggtt_node *node)
> @@ -536,6 +642,9 @@ void xe_ggtt_node_remove(struct xe_ggtt_node *node, bool invalidate)
> * xe_ggtt_init - Regular non-early GGTT initialization
> * @ggtt: the &xe_ggtt to be initialized
> *
> + * Allocate the scratch page if needed and clear unused entries, or defer
> + * the clear to xe_ggtt_init_late() when GPU initialization is enabled.
> + *
> * Return: 0 on success or a negative error code on failure.
> */
> int xe_ggtt_init(struct xe_ggtt *ggtt)
> @@ -572,7 +681,8 @@ int xe_ggtt_init(struct xe_ggtt *ggtt)
> xe_bo_size(ggtt->scratch));
> }
>
> - xe_ggtt_initial_clear(ggtt);
> + if (!ggtt_use_gpu(xe))
> + xe_ggtt_initial_clear(ggtt, NULL);
>
> return devm_add_action_or_reset(xe->drm.dev, ggtt_fini, ggtt);
> err:
> diff --git a/drivers/gpu/drm/xe/xe_ggtt.h b/drivers/gpu/drm/xe/xe_ggtt.h
> index c864cc975a69..cae8600d7142 100644
> --- a/drivers/gpu/drm/xe/xe_ggtt.h
> +++ b/drivers/gpu/drm/xe/xe_ggtt.h
> @@ -17,6 +17,7 @@ struct xe_ggtt *xe_ggtt_alloc(struct xe_tile *tile);
> int xe_ggtt_init_early(struct xe_ggtt *ggtt);
> int xe_ggtt_init_kunit(struct xe_ggtt *ggtt, u32 reserved, u32 size);
> int xe_ggtt_init(struct xe_ggtt *ggtt);
> +int xe_ggtt_init_late(struct xe_ggtt *ggtt);
>
> void xe_ggtt_shift_nodes(struct xe_ggtt *ggtt, u64 new_base);
> u64 xe_ggtt_start(struct xe_ggtt *ggtt);
> diff --git a/drivers/gpu/drm/xe/xe_gt.c b/drivers/gpu/drm/xe/xe_gt.c
> index 45fb074812a2..d1c14a02de66 100644
> --- a/drivers/gpu/drm/xe/xe_gt.c
> +++ b/drivers/gpu/drm/xe/xe_gt.c
> @@ -686,7 +686,8 @@ static int gt_init_with_all_forcewake(struct xe_gt *gt)
> if (err)
> return err;
>
> - return 0;
> + return xe_gt_is_main_type(gt) ?
> + xe_ggtt_init_late(gt_to_tile(gt)->mem.ggtt) : 0;
> }
>
> static void xe_gt_fini(void *arg)
> diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
> index 897724cb5cfb..44cc0107cf53 100644
> --- a/drivers/gpu/drm/xe/xe_module.c
> +++ b/drivers/gpu/drm/xe/xe_module.c
> @@ -22,6 +22,7 @@
> #include "xe_sched_job.h"
>
> struct xe_modparam xe_modparam = {
> + .ggtt_init_gpu = true,
I'd make this platform param in xe_pci.c and turn it on for every
platform which supports the required GPU instructions (likely every Xe
supported platform). We can let this soak in for a couple months in our
CI before getting into a kernel release to ensure everything is
functional and then perhaps rip out the non-GPU clear code.
Matt
> .probe_display = XE_DEFAULT_PROBE_DISPLAY,
> .guc_log_level = XE_DEFAULT_GUC_LOG_LEVEL,
> .force_probe = XE_DEFAULT_FORCE_PROBE,
> @@ -34,6 +35,10 @@ struct xe_modparam xe_modparam = {
> /* the rest are 0 by default */
> };
>
> +module_param_named(ggtt_init_gpu, xe_modparam.ggtt_init_gpu, bool, 0444);
> +MODULE_PARM_DESC(ggtt_init_gpu,
> + "GPU GGTT initialization on Lunar Lake (default: true)");
> +
> module_param_named(svm_notifier_size, xe_modparam.svm_notifier_size, uint, 0600);
> MODULE_PARM_DESC(svm_notifier_size, "Set the svm notifier size in MiB, must be power of 2 "
> "[default=" __stringify(XE_DEFAULT_SVM_NOTIFIER_SIZE) "]");
> diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h
> index 6272d9e41207..8aa91822c35c 100644
> --- a/drivers/gpu/drm/xe/xe_module.h
> +++ b/drivers/gpu/drm/xe/xe_module.h
> @@ -12,6 +12,7 @@ struct work_struct;
>
> /* Module modprobe variables */
> struct xe_modparam {
> + bool ggtt_init_gpu;
> bool probe_display;
> int force_vram_bar_size;
> int guc_log_level;
^ permalink raw reply [flat|nested] 4+ messages in thread
* Re: [RFC PATCH] drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms
2026-10-29 15:16 ` Matthew Brost
@ 2026-10-29 15:31 ` Matthew Brost
0 siblings, 0 replies; 4+ messages in thread
From: Matthew Brost @ 2026-10-29 15:31 UTC (permalink / raw)
To: Daniel Colascione; +Cc: intel-xe, Thomas Hellstrom, Rodrigo Vivi, dri-devel
On Thu, Oct 29, 2026 at 08:16:44AM -0700, Matthew Brost wrote:
Sorry double reply, just had another thought...
> On Tue, Oct 27, 2026 at 09:50:31PM -0700, Daniel Colascione wrote:
>
> Thanks for this - I have wondered why when I probe Xe it takes a
> noticable amount of time - I suspect this is why.
>
> > We initialize the GGTT GPU page tables on startup. On my LNL, doing
> > this one PTE at a time through the BAR takes about 900 ms and involves
> > a million small writes.
> >
> > Use the GPU's copy engine to fill the PTEs in batches. We build
> > MI_UPDATE_GTT commands in normal memory, then send them to the GPU for
> > execution. This way, we avoid the per-PTE MMIO write and do the PTE fill
> > in 36.77 ms, ~24x faster.
> >
> > The new path is enabled by default. xe.ggtt_init_gpu=0 uses the old
> > CPU-driven path.
> >
> > LNL-only because I can test only LNL. Might it work elsewhere?
> >
>
> It should work anywhere.
>
> > Signed-off-by: Daniel Colascione <dancol@dancol.org>
> > ---
> > drivers/gpu/drm/xe/instructions/xe_mi_commands.h | 2 +
> > drivers/gpu/drm/xe/xe_ggtt.c | 128 +++++++++++++++++++++--
> > drivers/gpu/drm/xe/xe_ggtt.h | 1 +
> > drivers/gpu/drm/xe/xe_gt.c | 3 +-
> > drivers/gpu/drm/xe/xe_module.c | 5 +
> > drivers/gpu/drm/xe/xe_module.h | 1 +
> > 6 files changed, 130 insertions(+), 10 deletions(-)
> >
> > diff --git a/drivers/gpu/drm/xe/instructions/xe_mi_commands.h b/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
> > index 835ef75bbeb1..04bd4225adef 100644
> > --- a/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
> > +++ b/drivers/gpu/drm/xe/instructions/xe_mi_commands.h
> > @@ -62,6 +62,8 @@
> > #define MI_LRI_FORCE_POSTED REG_BIT(12)
> > #define MI_LRI_LEN(x) (((x) & 0xff) + 1)
> >
> > +#define MI_UPDATE_GTT __MI_INSTR(0x23)
> > +
> > #define MI_STORE_REGISTER_MEM (__MI_INSTR(0x24) | XE_INSTR_NUM_DW(4))
> > #define MI_SRM_USE_GGTT REG_BIT(22)
> > #define MI_SRM_ADD_CS_OFFSET REG_BIT(19)
> > diff --git a/drivers/gpu/drm/xe/xe_ggtt.c b/drivers/gpu/drm/xe/xe_ggtt.c
> > index 773777afc68a..3737d085ae68 100644
> > --- a/drivers/gpu/drm/xe/xe_ggtt.c
> > +++ b/drivers/gpu/drm/xe/xe_ggtt.c
> > @@ -15,20 +15,25 @@
> > #include <drm/intel/i915_drm.h>
> > #include <generated/xe_wa_oob.h>
> >
> > +#include "instructions/xe_mi_commands.h"
> > #include "regs/xe_gt_regs.h"
> > #include "regs/xe_gtt_defs.h"
> > #include "regs/xe_regs.h"
> > #include "xe_assert.h"
> > +#include "xe_bb.h"
> > #include "xe_bo.h"
> > +#include "xe_exec_queue.h"
> > #include "xe_gt.h"
> > #include "xe_gt_printk.h"
> > #include "xe_gt_types.h"
> > #include "xe_log.h"
> > #include "xe_map.h"
> > #include "xe_mmio.h"
> > +#include "xe_module.h"
> > #include "xe_pat.h"
> > #include "xe_pm.h"
> > #include "xe_res_cursor.h"
> > +#include "xe_sched_job.h"
> > #include "xe_sriov.h"
> > #include "xe_tile_printk.h"
> > #include "xe_tile_sriov_vf.h"
> > @@ -384,9 +389,9 @@ static void dev_fini_ggtt(void *arg)
> > * @ggtt: the &xe_ggtt to be initialized
> > *
> > * It allows to create new mappings usable by the GuC.
> > - * Mappings are not usable by the HW engines, as it doesn't have scratch nor
> > - * initial clear done to it yet. That will happen in the regular, non-early
> > - * GGTT initialization.
> > + * HW engines may only access explicitly mapped bootstrap buffers until the
> > + * remaining holes have been cleared. Normally xe_ggtt_init() does this;
> > + * the GPU clear finishes this work in xe_ggtt_init_late().
> > *
> > * Return: 0 on success or a negative error code on failure.
> > */
> > @@ -456,18 +461,119 @@ ALLOW_ERROR_INJECTION(xe_ggtt_init_early, ERRNO); /* See xe_pci_probe() */
> > static void xe_ggtt_invalidate(struct xe_ggtt *ggtt);
> > static void xe_ggtt_invalidate_engine(struct xe_ggtt *ggtt);
> >
> > -static void xe_ggtt_initial_clear(struct xe_ggtt *ggtt)
> > +static bool ggtt_use_gpu(struct xe_device *xe)
> > {
> > + return xe_modparam.ggtt_init_gpu && xe->info.platform == XE_LUNARLAKE;
>
> Per below (last reply) - let's just turn this everywhere in platform
> config.
>
> > +}
> > +
> > +/*
> > + * MI_UPDATE_GTT takes at most 511 inline PTEs (ACM PRM, Vol. 2a,
> > + * pp. 1096-1097). Limit batches to 512K to fit the kernel batch pool.
> > + */
> > +#define GGTT_INIT_PTES_PER_PACKET 511
> > +#define GGTT_INIT_BATCH_DWORDS (128 * (2 + 2 * GGTT_INIT_PTES_PER_PACKET))
> > +
> > +static int ggtt_clear_gpu(struct xe_exec_queue *q, struct xe_bb *bb,
> > + u64 pte, u64 start, u64 end)
>
> s/ggtt_clear_gpu/xe_ggtt_clear_gpu
>
> > +{
> > + while (start < end) {
> > + struct xe_sched_job *job;
> > + int err;
> > +
> > + bb->len = 0;
> > + while (start < end && bb->len < GGTT_INIT_BATCH_DWORDS) {
> > + u32 count = min_t(u64, (end - start) >> XE_PTE_SHIFT,
> > + GGTT_INIT_PTES_PER_PACKET);
> > +
> > + bb->cs[bb->len++] = MI_UPDATE_GTT | (2 * count);
> > + bb->cs[bb->len++] = start;
> > + memset64((u64 *)&bb->cs[bb->len], pte, count);
> > + bb->len += 2 * count;
> > + start += (u64)count << XE_PTE_SHIFT;
> > + }
> > +
> > + job = xe_bb_create_job(q, bb);
> > + if (IS_ERR(job))
> > + return PTR_ERR(job);
> > +
> > + xe_sched_job_arm(job);
> > + xe_sched_job_get(job);
> > + xe_sched_job_push(job);
> > +
> > + dma_fence_wait(&job->drm.s_fence->finished, false);
>
> You can pipeline this function.
>
> i.e., generate fences in a loop and only wait on the last one.
>
> This is fairly common pattern, xe_svm_copy is an example of function
> with loop which issues a bunch of jobs and only waits on the very last
> fence.
>
> > + err = dma_fence_get_status(&job->drm.s_fence->finished);
> > + xe_sched_job_put(job);
> > + if (err < 0)
> > + return err;
> > + }
> > +
> > + return 0;
> > +}
> > +
> > +static int xe_ggtt_initial_clear(struct xe_ggtt *ggtt, struct xe_hw_engine *hwe)
> > +{
> > + struct xe_device *xe = tile_to_xe(ggtt->tile);
> > + struct xe_exec_queue *q = NULL;
> > + struct xe_bb *bb = NULL;
> > struct drm_mm_node *hole;
> > - u64 start, end;
> > + u16 pat_index = xe_cache_pat_idx(xe, XE_CACHE_WB);
> > + u64 start, end, pte = 0;
> > + int err = 0;
> > +
> > + if (hwe) {
> > + q = xe_exec_queue_create(xe, NULL, BIT(hwe->logical_instance), 1,
> > + hwe, EXEC_QUEUE_FLAG_KERNEL, 0);
>
> Can we use migration queue (xe_migrate.c)? We'd probably need a bit of
> layering rework to allow another layer like GGTT to safely use this
> queue but that seems better to keep a single isolated kernel queue.
>
> > + if (IS_ERR(q))
> > + return PTR_ERR(q);
> > +
> > + bb = xe_bb_new(hwe->gt, GGTT_INIT_BATCH_DWORDS, false);
> > + if (IS_ERR(bb)) {
> > + err = PTR_ERR(bb);
> > + goto put_queue;
> > + }
> > + }
> >
> > - /* Display may have allocated inside ggtt, so be careful with clearing here */
> > + if (ggtt->scratch)
> > + pte = xe_bo_addr(ggtt->scratch, 0, XE_PAGE_SIZE) |
> > + ggtt->pt_ops->pte_encode_flags(ggtt->scratch, pat_index);
> > +
> > + /* Preserve boot mappings and exclude allocator changes until completion. */
> > mutex_lock(&ggtt->lock);
>
> CI and Sashiko correctly complain that 'ggtt->lock' is tainted with
> reclaim and ggtt_clear_gpu will be a malloc for the GPU job.
>
> I'm actually questioning if this taint is actually valid. afiak we never
> shrink or evict anything GGTT mapped (i.e., that memory is always
> pinned). So I think we can just drop the taint - I'd do this in a
> standalone patch.
>
> > - drm_mm_for_each_hole(hole, &ggtt->mm, start, end)
> > - xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
> > + drm_mm_for_each_hole(hole, &ggtt->mm, start, end) {
> > + if (q)
> > + err = ggtt_clear_gpu(q, bb, pte, ggtt->start + start, ggtt->start + end);
> > + else
> > + xe_ggtt_clear(ggtt, ggtt->start + start, end - start);
> > + if (err)
> > + goto unlock;
> > + }
> >
> > xe_ggtt_invalidate(ggtt);
> > +unlock:
> > mutex_unlock(&ggtt->lock);
> > + xe_bb_free(bb, NULL);
> > +put_queue:
> > + if (q)
> > + xe_exec_queue_put(q);
> > + return err;
> > +}
> > +
> > +/**
> > + * xe_ggtt_init_late - Finish deferred GGTT initialization after GuC startup
> > + * @ggtt: GGTT to initialize
> > + *
> > + * All default contexts must be recorded before waiting for a GPU clear with
> > + * the GGTT mutex held, so reset recovery need not allocate context mappings.
> > + *
> > + * Return: 0 on success or a negative error code on failure.
> > + */
> > +int xe_ggtt_init_late(struct xe_ggtt *ggtt)
> > +{
> > + if (!ggtt_use_gpu(tile_to_xe(ggtt->tile)))
> > + return 0;
> > +
> > + return xe_ggtt_initial_clear(ggtt, xe_gt_hw_engine(ggtt->tile->primary_gt,
> > + XE_ENGINE_CLASS_COPY, 0, false));
> > }
> >
> > static void ggtt_node_fini(struct xe_ggtt_node *node)
> > @@ -536,6 +642,9 @@ void xe_ggtt_node_remove(struct xe_ggtt_node *node, bool invalidate)
> > * xe_ggtt_init - Regular non-early GGTT initialization
> > * @ggtt: the &xe_ggtt to be initialized
> > *
> > + * Allocate the scratch page if needed and clear unused entries, or defer
> > + * the clear to xe_ggtt_init_late() when GPU initialization is enabled.
> > + *
> > * Return: 0 on success or a negative error code on failure.
> > */
> > int xe_ggtt_init(struct xe_ggtt *ggtt)
> > @@ -572,7 +681,8 @@ int xe_ggtt_init(struct xe_ggtt *ggtt)
> > xe_bo_size(ggtt->scratch));
> > }
> >
> > - xe_ggtt_initial_clear(ggtt);
> > + if (!ggtt_use_gpu(xe))
> > + xe_ggtt_initial_clear(ggtt, NULL);
Hmm, so this defers GGTT clears to after we use GGTT memory for queues /
jobs.
The fact this works points to GGTT scratch most likely not actually
being needed but that is pretty hard to reason 100% correctly.
I wonder if a better solution is completely drop the GGTT clear and BO
which sets XE_BO_FLAG_GGTT just allocates guard page at the end (again
with a config to enable this which is on by default until we can reason
this isn't needed).
A lot to take in here. Let us know if you want to continue driving this,
or I can try to find an engineer at Intel to own it. A reduction from
900 ms to 37 ms is a compelling result, and this clearly needs to be
reworked.
Matt
> >
> > return devm_add_action_or_reset(xe->drm.dev, ggtt_fini, ggtt);
> > err:
> > diff --git a/drivers/gpu/drm/xe/xe_ggtt.h b/drivers/gpu/drm/xe/xe_ggtt.h
> > index c864cc975a69..cae8600d7142 100644
> > --- a/drivers/gpu/drm/xe/xe_ggtt.h
> > +++ b/drivers/gpu/drm/xe/xe_ggtt.h
> > @@ -17,6 +17,7 @@ struct xe_ggtt *xe_ggtt_alloc(struct xe_tile *tile);
> > int xe_ggtt_init_early(struct xe_ggtt *ggtt);
> > int xe_ggtt_init_kunit(struct xe_ggtt *ggtt, u32 reserved, u32 size);
> > int xe_ggtt_init(struct xe_ggtt *ggtt);
> > +int xe_ggtt_init_late(struct xe_ggtt *ggtt);
> >
> > void xe_ggtt_shift_nodes(struct xe_ggtt *ggtt, u64 new_base);
> > u64 xe_ggtt_start(struct xe_ggtt *ggtt);
> > diff --git a/drivers/gpu/drm/xe/xe_gt.c b/drivers/gpu/drm/xe/xe_gt.c
> > index 45fb074812a2..d1c14a02de66 100644
> > --- a/drivers/gpu/drm/xe/xe_gt.c
> > +++ b/drivers/gpu/drm/xe/xe_gt.c
> > @@ -686,7 +686,8 @@ static int gt_init_with_all_forcewake(struct xe_gt *gt)
> > if (err)
> > return err;
> >
> > - return 0;
> > + return xe_gt_is_main_type(gt) ?
> > + xe_ggtt_init_late(gt_to_tile(gt)->mem.ggtt) : 0;
> > }
> >
> > static void xe_gt_fini(void *arg)
> > diff --git a/drivers/gpu/drm/xe/xe_module.c b/drivers/gpu/drm/xe/xe_module.c
> > index 897724cb5cfb..44cc0107cf53 100644
> > --- a/drivers/gpu/drm/xe/xe_module.c
> > +++ b/drivers/gpu/drm/xe/xe_module.c
> > @@ -22,6 +22,7 @@
> > #include "xe_sched_job.h"
> >
> > struct xe_modparam xe_modparam = {
> > + .ggtt_init_gpu = true,
>
> I'd make this platform param in xe_pci.c and turn it on for every
> platform which supports the required GPU instructions (likely every Xe
> supported platform). We can let this soak in for a couple months in our
> CI before getting into a kernel release to ensure everything is
> functional and then perhaps rip out the non-GPU clear code.
>
> Matt
>
> > .probe_display = XE_DEFAULT_PROBE_DISPLAY,
> > .guc_log_level = XE_DEFAULT_GUC_LOG_LEVEL,
> > .force_probe = XE_DEFAULT_FORCE_PROBE,
> > @@ -34,6 +35,10 @@ struct xe_modparam xe_modparam = {
> > /* the rest are 0 by default */
> > };
> >
> > +module_param_named(ggtt_init_gpu, xe_modparam.ggtt_init_gpu, bool, 0444);
> > +MODULE_PARM_DESC(ggtt_init_gpu,
> > + "GPU GGTT initialization on Lunar Lake (default: true)");
> > +
> > module_param_named(svm_notifier_size, xe_modparam.svm_notifier_size, uint, 0600);
> > MODULE_PARM_DESC(svm_notifier_size, "Set the svm notifier size in MiB, must be power of 2 "
> > "[default=" __stringify(XE_DEFAULT_SVM_NOTIFIER_SIZE) "]");
> > diff --git a/drivers/gpu/drm/xe/xe_module.h b/drivers/gpu/drm/xe/xe_module.h
> > index 6272d9e41207..8aa91822c35c 100644
> > --- a/drivers/gpu/drm/xe/xe_module.h
> > +++ b/drivers/gpu/drm/xe/xe_module.h
> > @@ -12,6 +12,7 @@ struct work_struct;
> >
> > /* Module modprobe variables */
> > struct xe_modparam {
> > + bool ggtt_init_gpu;
> > bool probe_display;
> > int force_vram_bar_size;
> > int guc_log_level;
^ permalink raw reply [flat|nested] 4+ messages in thread
end of thread, other threads:[~2026-10-29 15:32 UTC | newest]
Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-28 4:50 [RFC PATCH] drm/xe/ggtt: Cut GGTT initialization from 900 ms to 37 ms Daniel Colascione
2026-10-28 13:23 ` sashiko-bot
2026-10-29 15:16 ` Matthew Brost
2026-10-29 15:31 ` Matthew Brost
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox