diff --git a/drivers/gpu/drm/i915/Kconfig.debug b/drivers/gpu/drm/i915/Kconfig.debug
index 1140525da75a..06953c1d6fd4 100644
--- a/drivers/gpu/drm/i915/Kconfig.debug
+++ b/drivers/gpu/drm/i915/Kconfig.debug
@@ -223,3 +223,17 @@ config DRM_I915_DEBUG_RUNTIME_PM
 	  driver loading, suspend and resume operations.
 
 	  If in doubt, say "N"
+
+config DRM_I915_DUMP_PPGTT
+        bool "Enable PPGTT Page Table dump support"
+        depends on DRM_I915
+        default n
+        help
+	  Choose this option to enable PPGTT page table dump support.
+	  The page table snapshot helps developers to debug page table
+	  related issues. This will affect performance and dumps a lot of
+	  information, so only recommended for developer debug.
+
+          Recommended for driver developers only.
+
+	  If in doubt, say "N".
diff --git a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c
index cbcfbf86ea61..c771659d72e2 100644
--- a/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c
+++ b/drivers/gpu/drm/i915/gem/i915_gem_execbuffer.c
@@ -2707,6 +2707,7 @@ i915_gem_do_execbuffer(struct drm_device *dev,
 		intel_engine_pool_mark_active(eb.batch->private, eb.request);
 
 	trace_i915_request_queue(eb.request, eb.batch_flags);
+	ppgtt_dump(eb.context->vm, 0, eb.context->vm->total);
 	err = eb_submit(&eb);
 err_request:
 	add_to_client(eb.request, file);
diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.c b/drivers/gpu/drm/i915/i915_gem_gtt.c
index 08cbbb4d91cb..6a7348af5717 100644
--- a/drivers/gpu/drm/i915/i915_gem_gtt.c
+++ b/drivers/gpu/drm/i915/i915_gem_gtt.c
@@ -1215,6 +1215,97 @@ static int gen8_ppgtt_alloc(struct i915_address_space *vm,
 	return err;
 }
 
+#ifdef CONFIG_DRM_I915_DUMP_PPGTT
+static void __gen8_ppgtt_dump(struct i915_address_space * const vm,
+			      struct i915_page_directory * const pd,
+			      u64 start, u64 end, int lvl)
+{
+	char *prefix[4] = { "\t\t\t\t", "\t\t\t", "\t\t", "\t"};
+	char *format = "%s [0x%03x] 0x%llx: 0x%llx\n";
+	unsigned int idx, len;
+	gen8_pte_t *vaddr;
+	unsigned int pdpe;
+	bool is_large;
+
+	GEM_BUG_ON(end > vm->total >> GEN8_PTE_SHIFT);
+
+	len = gen8_pd_range(start, end, lvl--, &idx);
+	GEM_BUG_ON(!len || (idx + len - 1) >> gen8_pd_shift(1));
+
+	spin_lock(&pd->lock);
+	GEM_BUG_ON(!atomic_read(px_used(pd))); /* Must be pinned! */
+	do {
+		struct i915_page_table *pt = pd->entry[idx];
+
+		if (!pt) {
+			start += (1 << gen8_pd_shift(lvl + 1));
+			continue;
+		}
+
+		vaddr = kmap_atomic_px(&pd->pt);
+		pdpe = gen8_pd_index(start, lvl + 1);
+		DRM_DEBUG_DRIVER(format, prefix[lvl + 1], pdpe,
+				 start, vaddr[pdpe]);
+		is_large = (vaddr[pdpe] & GEN8_PDE_PS_2M);
+		kunmap_atomic(vaddr);
+		if (is_large) {
+			start += (1 << gen8_pd_shift(lvl + 1));
+			continue;
+		}
+
+		if (lvl) {
+			atomic_inc(&pt->used);
+			spin_unlock(&pd->lock);
+
+			__gen8_ppgtt_dump(vm, as_pd(pt),
+					  start, end, lvl);
+
+			start += (1 << gen8_pd_shift(lvl + 1));
+			spin_lock(&pd->lock);
+			atomic_dec(&pt->used);
+			GEM_BUG_ON(!atomic_read(&pt->used));
+		} else {
+			unsigned int count = gen8_pt_count(start, end);
+
+			pdpe = gen8_pd_index(start, lvl);
+			vaddr = kmap_atomic_px(pt);
+			while (count) {
+				if (vaddr[pdpe] != vm->scratch[lvl].encode)
+					DRM_DEBUG_DRIVER(format, prefix[lvl],
+							 pdpe, start,
+							 vaddr[pdpe]);
+				start++;
+				count--;
+				pdpe++;
+			}
+
+			kunmap_atomic(vaddr);
+			GEM_BUG_ON(atomic_read(&pt->used) > I915_PDES);
+		}
+	} while (idx++, --len);
+	spin_unlock(&pd->lock);
+}
+
+static void gen8_ppgtt_dump(struct i915_address_space *vm,
+			    u64 start, u64 length)
+{
+	GEM_BUG_ON(!IS_ALIGNED(start, BIT_ULL(GEN8_PTE_SHIFT)));
+	GEM_BUG_ON(!IS_ALIGNED(length, BIT_ULL(GEN8_PTE_SHIFT)));
+	GEM_BUG_ON(range_overflows(start, length, vm->total));
+
+	start >>= GEN8_PTE_SHIFT;
+	length >>= GEN8_PTE_SHIFT;
+	GEM_BUG_ON(length == 0);
+
+	DRM_DEBUG_DRIVER("PPGTT dump: start 0x%llx length 0x%llx\n",
+			 start, length);
+	__gen8_ppgtt_dump(vm, i915_vm_to_ppgtt(vm)->pd,
+			  start, start + length, vm->top);
+}
+#else
+#define gen8_ppgtt_dump   NULL
+#endif
+
 static inline struct sgt_dma {
 	struct scatterlist *sg;
 	dma_addr_t dma, max;
@@ -1584,6 +1675,7 @@ static struct i915_ppgtt *gen8_ppgtt_create(struct drm_i915_private *i915)
 	ppgtt->vm.insert_entries = gen8_ppgtt_insert;
 	ppgtt->vm.allocate_va_range = gen8_ppgtt_alloc;
 	ppgtt->vm.clear_range = gen8_ppgtt_clear;
+	ppgtt->vm.dump_va_range = gen8_ppgtt_dump;
 
 	if (intel_vgpu_active(i915))
 		gen8_ppgtt_notify_vgt(ppgtt, true);
diff --git a/drivers/gpu/drm/i915/i915_gem_gtt.h b/drivers/gpu/drm/i915/i915_gem_gtt.h
index 722b1e7ce291..a4358fb1c711 100644
--- a/drivers/gpu/drm/i915/i915_gem_gtt.h
+++ b/drivers/gpu/drm/i915/i915_gem_gtt.h
@@ -367,6 +367,8 @@ struct i915_address_space {
 				 u64 start, u64 length);
 	void (*clear_range)(struct i915_address_space *vm,
 			    u64 start, u64 length);
+	void (*dump_va_range)(struct i915_address_space *vm,
+			      u64 start, u64 length);
 	void (*insert_page)(struct i915_address_space *vm,
 			    dma_addr_t addr,
 			    u64 offset,
@@ -683,6 +685,18 @@ int i915_gem_gtt_insert(struct i915_address_space *vm,
 
 #define PIN_OFFSET_MASK		(-I915_GTT_PAGE_SIZE)
 
+#ifdef CONFIG_DRM_I915_DUMP_PPGTT
+static inline void ppgtt_dump(struct i915_address_space *vm,
+			      u64 start, u64 length)
+{
+	if (vm->dump_va_range)
+		vm->dump_va_range(vm, start, length);
+}
+#else
+static inline void ppgtt_dump(struct i915_address_space *vm,
+			      u64 start, u64 length) { }
+#endif
+
 /* SVM API */
 #define I915_GTT_SVM_READONLY  BIT(0)
 
