[PATCH 1/2] tests/intel/xe_sriov_ggtt: Add test checking GGTT accesses

Lukasz Laguna <[email protected]> Tue, 4 Aug 2026 09:24:06 +0200
Newsgroups org.freedesktop.lists.igt-dev
Message-ID <[email protected]>
From: Piotr Piórkowski <[email protected]>

Add a test validating PTE ownership, accessibility, and modifiability
by PF and VFs.

The following scenarios are covered:
- pf-check-vfs-ids: verify VF ID assignment for each VF GGTT range,
- pf-check-vfs-pte: verify PF modifiability of VF PTEs,
- vfs-check-own-pte: verify VF modifiability of its own PTEs,
- vfs-check-other-vfs-pte: verify VF can't access other VFs PTEs,
- vfs-check-pf-pte: verify VF can't access PF PTEs.

Signed-off-by: Piotr Piórkowski <[email protected]>
Co-developed-by: Marcin Bernatowicz <[email protected]>
Signed-off-by: Marcin Bernatowicz <[email protected]>
Co-developed-by: Lukasz Laguna <[email protected]>
Signed-off-by: Lukasz Laguna <[email protected]>
---
 lib/xe/xe_ggtt.h            |   7 +
 tests/intel/xe_sriov_ggtt.c | 757 ++++++++++++++++++++++++++++++++++++
 tests/meson.build           |   1 +
 3 files changed, 765 insertions(+)
 create mode 100644 tests/intel/xe_sriov_ggtt.c

diff --git a/lib/xe/xe_ggtt.h b/lib/xe/xe_ggtt.h
index ada7ddbb0..8e0202cd7 100644
--- a/lib/xe/xe_ggtt.h
+++ b/lib/xe/xe_ggtt.h
@@ -8,6 +8,11 @@
 
 #include <stdint.h>
 
+#define GGTT_PAGE_SIZE			SZ_4K
+#define GGTT_TOP			0xfee00000
+
+#define XELPG_GGTT_PTE_PAT_MASK		GENMASK_ULL(53, 52)
+
 #define GGTT_PTE_ADDR_MASK		GENMASK_ULL(45, 12)
 #define TGL_GGTT_PTE_ADDR_MASK		GENMASK_ULL(38, 12)
 #define   GGTT_PTE_ADDR_SHIFT		12
@@ -16,10 +21,12 @@
 #define TGL_GGTT_PTE_VFID_MASK		GENMASK_ULL(4, 2)
 #define   GGTT_PTE_VFID_SHIFT		2
 
+#define GGTT_PAGE_LM			GENMASK_ULL(1, 1)
 #define GGTT_PAGE_PRESENT		GENMASK_ULL(0, 0)
 
 typedef uint64_t xe_ggtt_pte_t;
 typedef uint64_t xe_ggtt_pte_mask_t;
+typedef uint64_t xe_ggtt_addr_t;
 
 xe_ggtt_pte_mask_t xe_ggtt_get_vfid_mask(int pf_fd);
 xe_ggtt_pte_mask_t xe_ggtt_get_gpa_mask(int pf_fd);
diff --git a/tests/intel/xe_sriov_ggtt.c b/tests/intel/xe_sriov_ggtt.c
new file mode 100644
index 000000000..2c2184930
--- /dev/null
+++ b/tests/intel/xe_sriov_ggtt.c
@@ -0,0 +1,757 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright(c) 2026 Intel Corporation. All rights reserved.
+ */
+
+#include <linux/bitops.h>
+
+#include "drmtest.h"
+#include "igt_sriov_device.h"
+#include "intel_chipset.h"
+#include "xe/xe_ggtt.h"
+#include "xe/xe_mmio.h"
+#include "xe/xe_sriov_debugfs.h"
+#include "xe/xe_sriov_provisioning.h"
+#include "xe/xe_query.h"
+
+/**
+ * TEST: xe_sriov_ggtt
+ * Category: Core
+ * Mega feature: SR-IOV
+ * Sub-category: GGTT
+ * Functionality: GGTT isolation
+ * Description: Checks GGTT bitfields and VFs GGTT isolation
+ *
+ * SUBTEST: pf-check-vfs-ids
+ * Description:
+ *   Verify that the VF ID in the GGTT PTE has been correctly assigned
+ *
+ * SUBTEST: pf-check-vfs-pte
+ * Description:
+ *   Verify on PF the modifiability of GGTT PTE assigned to VFs
+ *
+ * SUBTEST: vfs-check-own-pte
+ * Description:
+ *   Verify of modifiability of GGTT PTE owned by VF
+ *
+ * SUBTEST: vfs-check-other-vfs-pte
+ * Description:
+ *   Verify the non-availability of VF to GGTT PTEs owned by other VFs
+ *
+ * SUBTEST: vfs-check-pf-pte
+ * Description:
+ *   Verify the non-availability of VF to GGTT PTEs owned by PF
+ */
+
+IGT_TEST_DESCRIPTION("Xe tests for SR-IOV GGTT");
+
+#define SRIOV_GGTT_TC(__name)	.test = __name, .name = #__name
+
+struct ggtt_test_data {
+	int pf_fd;
+	struct xe_mmio mmio;
+	struct xe_mmio pf_mmio;
+	bool verify_on_pf;
+	bool bus_master_enabled_in_test;
+	void (*set_pte)(struct xe_mmio *mmio, uint8_t tile, xe_ggtt_addr_t addr,
+			xe_ggtt_pte_t pte);
+	xe_ggtt_pte_t (*get_pte)(struct xe_mmio *mmio, uint8_t tile, xe_ggtt_addr_t addr);
+};
+
+struct pte_testcase {
+	const char *name;
+	bool (*test)(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+		     xe_ggtt_pte_t *out);
+	bool (*requires)(int pf_fd);
+};
+
+static uint64_t xe_ggtt_addr_to_pte_offset(xe_ggtt_addr_t addr)
+{
+	igt_assert_f(IS_ALIGNED(addr, GGTT_PAGE_SIZE), "GGTT address %#lx is not aligned to %#x\n",
+		     addr, GGTT_PAGE_SIZE);
+
+	return (addr / GGTT_PAGE_SIZE) * sizeof(xe_ggtt_pte_t);
+}
+
+static void intel_set_pte(struct xe_mmio *mmio, uint8_t tile, xe_ggtt_addr_t addr,
+			  xe_ggtt_pte_t pte)
+{
+	xe_mmio_ggtt_write(mmio, tile, xe_ggtt_addr_to_pte_offset(addr), pte);
+}
+
+#define GEN12_VF_CAP_REG	0x1901f8
+
+static void intel_set_pte_and_sync(struct xe_mmio *mmio, uint8_t tile, xe_ggtt_addr_t addr,
+				   xe_ggtt_pte_t pte)
+{
+	intel_set_pte(mmio, tile, addr, pte);
+
+	/* Adding a fence by reading MMIO register to ensure memory ordering */
+	xe_mmio_tile_read32(mmio, tile, GEN12_VF_CAP_REG);
+}
+
+static xe_ggtt_pte_t intel_get_pte(struct xe_mmio *mmio, uint8_t tile, xe_ggtt_addr_t addr)
+{
+	return xe_mmio_ggtt_read(mmio, tile, xe_ggtt_addr_to_pte_offset(addr));
+}
+
+static bool has_lmem(int pf_fd)
+{
+	return xe_has_vram(pf_fd);
+}
+
+static bool has_pat(int pf_fd)
+{
+	uint16_t dev_id = intel_get_drm_devid(pf_fd);
+
+	return intel_graphics_ver(dev_id) >= IP_VER(12, 70);
+}
+
+static bool check_pte_vfid(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+			   unsigned int vf_id, xe_ggtt_pte_t *out)
+{
+	unsigned int val;
+
+	*out = data->get_pte(&data->mmio, tile, addr);
+	val = xe_ggtt_pte_get_vfid(data->pf_fd, *out);
+
+	return (val == vf_id);
+}
+
+static bool pte_is_value_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				    xe_ggtt_pte_mask_t mask, xe_ggtt_pte_t *out)
+{
+	xe_ggtt_pte_t original_pte;
+	xe_ggtt_pte_t write_pte;
+	xe_ggtt_pte_t read_pte;
+	bool ret = true;
+
+	original_pte = data->get_pte(&data->mmio, tile, addr);
+
+	write_pte = original_pte ^ mask;
+	data->set_pte(&data->mmio, tile, addr, write_pte);
+	read_pte = data->get_pte(&data->mmio, tile, addr);
+
+	*out = read_pte;
+
+	if ((read_pte & mask) != (write_pte & mask))
+		ret = false;
+
+	if (data->verify_on_pf) {
+		xe_ggtt_pte_t pf_read_pte = data->get_pte(&data->pf_mmio, tile, addr);
+
+		/* Double check, this time compare with value read on PF */
+		if ((pf_read_pte & mask) != (write_pte & mask))
+			ret = false;
+	}
+
+	data->set_pte(&data->mmio, tile, addr, original_pte);
+
+	return ret;
+}
+
+static bool pte_is_value_not_readable(struct ggtt_test_data *data, uint8_t tile,
+				      xe_ggtt_addr_t addr, xe_ggtt_pte_mask_t mask,
+				      xe_ggtt_pte_t *out)
+{
+	*out = data->get_pte(&data->mmio, tile, addr);
+
+	return (*out & mask) == 0;
+}
+
+static bool pte_not_accessible(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+			       xe_ggtt_pte_t *out)
+{
+	uint16_t dev_id = intel_get_drm_devid(data->pf_fd);
+	xe_ggtt_pte_mask_t mask;
+	xe_ggtt_pte_t expected;
+
+	if (intel_graphics_ver(dev_id) < IP_VER(12, 10)) {
+		expected = ~0;
+		mask = ~0;
+	} else if (intel_graphics_ver(dev_id) < IP_VER(12, 70)) {
+		expected = 0;
+		mask = ~0;
+	} else {
+		expected = 0;
+		mask = GGTT_PTE_ADDR_MASK | GGTT_PTE_VFID_MASK;
+	}
+
+	*out = data->get_pte(&data->mmio, tile, addr);
+	return (*out & mask) == expected;
+}
+
+static bool pte_pat_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+			       xe_ggtt_pte_t *out)
+{
+	return pte_is_value_modifiable(data, tile, addr, XELPG_GGTT_PTE_PAT_MASK, out);
+}
+
+static bool pte_pat_not_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				   xe_ggtt_pte_t *out)
+{
+	return !pte_pat_modifiable(data, tile, addr, out);
+}
+
+static bool pte_gpa_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+			       xe_ggtt_pte_t *out)
+{
+	return pte_is_value_modifiable(data, tile, addr, xe_ggtt_get_gpa_mask(data->pf_fd), out);
+}
+
+static bool pte_gpa_not_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				   xe_ggtt_pte_t *out)
+{
+	return !pte_gpa_modifiable(data, tile, addr, out);
+}
+
+static bool pte_vfid_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				xe_ggtt_pte_t *out)
+{
+	return pte_is_value_modifiable(data, tile, addr, xe_ggtt_get_vfid_mask(data->pf_fd), out);
+}
+
+static bool pte_vfid_not_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				    xe_ggtt_pte_t *out)
+{
+	return !pte_vfid_modifiable(data, tile, addr, out);
+}
+
+static bool pte_vfid_not_readable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				  xe_ggtt_pte_t *out)
+{
+	return pte_is_value_not_readable(data, tile, addr,
+					 xe_ggtt_get_vfid_mask(data->pf_fd), out);
+}
+
+static bool pte_lmem_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				xe_ggtt_pte_t *out)
+{
+	return pte_is_value_modifiable(data, tile, addr, GGTT_PAGE_LM, out);
+}
+
+static bool pte_lmem_not_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				    xe_ggtt_pte_t *out)
+{
+	return !pte_lmem_modifiable(data, tile, addr, out);
+}
+
+static bool pte_valid_modifiable(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+				 xe_ggtt_pte_t *out)
+{
+	return pte_is_value_modifiable(data, tile, addr, GGTT_PAGE_PRESENT, out);
+}
+
+static bool pte_valid_not_modifiable(struct ggtt_test_data *data, uint8_t tile,
+				     xe_ggtt_addr_t addr, xe_ggtt_pte_t *out)
+{
+	return !pte_valid_modifiable(data, tile, addr, out);
+}
+
+static bool run_test_on_pte(struct ggtt_test_data *data, uint8_t tile, xe_ggtt_addr_t addr,
+			    const struct pte_testcase *tc, unsigned int vf_id)
+{
+	xe_ggtt_pte_t read_val;
+
+	if (!tc->test(data, tile, addr, &read_val)) {
+		igt_warn("%s failed at GGTT address %#lx on VF%d. PTE value: %#lx\n",
+			 tc->name, addr, vf_id, read_val);
+		return false;
+	}
+
+	return true;
+}
+
+static bool verify_each_pte_in_range;
+
+static xe_ggtt_addr_t next_addr(xe_ggtt_addr_t addr,
+				const struct xe_sriov_provisioned_range *range)
+{
+	xe_ggtt_addr_t step, next, last_page;
+
+	if (verify_each_pte_in_range)
+		return addr + GGTT_PAGE_SIZE;
+
+	if (addr < range->start + GGTT_PAGE_SIZE)
+		step = GGTT_PAGE_SIZE;
+	else
+		step = (addr - range->start) * 2;
+
+	next = addr + step;
+
+	last_page = ALIGN_DOWN(range->end, GGTT_PAGE_SIZE);
+	if (addr < last_page && next > last_page)
+		return last_page;
+
+	return next;
+}
+
+#define for_each_pte(ggtt_addr__, ggtt_range__) \
+	for ((ggtt_addr__) = (ggtt_range__)->start; \
+	     (ggtt_addr__) < (ggtt_range__)->end; \
+	     (ggtt_addr__) = next_addr((ggtt_addr__), (ggtt_range__)))
+
+static bool is_testcase_supported(const struct pte_testcase *tc, struct ggtt_test_data *data)
+{
+	return !(tc->requires && !tc->requires(data->pf_fd));
+}
+
+static bool run_test_on_ggtt_range(struct ggtt_test_data *data, uint8_t tile,
+				   struct xe_sriov_provisioned_range *range,
+				   const struct pte_testcase *tc, unsigned int vf_id)
+{
+	xe_ggtt_addr_t addr;
+
+	igt_assert_f(IS_ALIGNED(range->start, GGTT_PAGE_SIZE),
+		     "GGTT start range for VF%d (%#lx) is not aligned to %#x\n",
+		     vf_id, range->start, GGTT_PAGE_SIZE);
+
+	if (!is_testcase_supported(tc, data)) {
+		igt_debug("Skip test case %s on VF%u\n", tc->name, vf_id);
+		return true;
+	}
+
+	for_each_pte(addr, range)
+		if (!run_test_on_pte(data, tile, addr, tc, vf_id))
+			return false;
+	return true;
+}
+
+static bool run_test_outside_ggtt_range(struct ggtt_test_data *data, uint8_t tile,
+					struct xe_sriov_provisioned_range *range,
+					const struct pte_testcase *tc, unsigned int vf_id)
+{
+	struct xe_sriov_provisioned_range full_range = { .start = 0, .end = GGTT_TOP};
+	xe_ggtt_addr_t addr;
+
+	if (!is_testcase_supported(tc, data)) {
+		igt_debug("Skip test case %s on VF%u\n", tc->name, vf_id);
+		return true;
+	}
+
+	for_each_pte(addr, &full_range) {
+		if (addr >= range->start && addr <= range->end)
+			continue;
+		if (!run_test_on_pte(data, tile, addr, tc, vf_id))
+			return false;
+	}
+	return true;
+}
+
+static bool skip_vf_bus_master;
+
+#define PCI_COMMAND         0x04
+#define PCI_COMMAND_MASTER  REG_BIT(2)
+
+static void set_bus_master(struct ggtt_test_data *test_data, bool enable)
+{
+	struct pci_device *dev = test_data->mmio.intel_mmio.dev;
+	bool was_enabled;
+	char bdf[16];
+	uint16_t cmd;
+
+	igt_assert(dev);
+
+	if (skip_vf_bus_master)
+		return;
+
+	snprintf(bdf, sizeof(bdf), "%04x:%02x:%02x.%x",
+		 dev->domain, dev->bus, dev->dev, dev->func);
+
+	pci_device_cfg_read_u16(dev, &cmd, PCI_COMMAND);
+
+	was_enabled = cmd & PCI_COMMAND_MASTER;
+
+	if (enable) {
+		if (was_enabled)
+			return;
+
+		cmd |= PCI_COMMAND_MASTER;
+		pci_device_cfg_write_u16(dev, cmd, PCI_COMMAND);
+		test_data->bus_master_enabled_in_test = true;
+
+		igt_debug("%s: Enabling Bus Master (cmd=0x%04x)\n", bdf, cmd);
+	} else {
+		if (!test_data->bus_master_enabled_in_test || !was_enabled)
+			return;
+
+		cmd &= ~PCI_COMMAND_MASTER;
+		pci_device_cfg_write_u16(dev, cmd, PCI_COMMAND);
+
+		igt_debug("%s: Disabling Bus Master (cmd=0x%04x)\n", bdf, cmd);
+	}
+}
+
+static void enable_bus_master(struct ggtt_test_data *test_data)
+{
+	set_bus_master(test_data, true);
+}
+
+static void disable_bus_master(struct ggtt_test_data *test_data)
+{
+	set_bus_master(test_data, false);
+}
+
+static void init_subtest(int pf_fd, int vf_id, uint8_t tile,
+			 struct xe_sriov_provisioned_range **range,
+			 struct ggtt_test_data *test_data, unsigned int num_vfs)
+{
+	uint16_t dev_id = intel_get_drm_devid(pf_fd);
+	unsigned int nr_ranges, main_gt;
+
+	igt_sriov_disable_driver_autoprobe(pf_fd);
+	igt_sriov_enable_vfs(pf_fd, num_vfs);
+
+	/* refresh PCI state */
+	igt_pci_system_reinit();
+
+	main_gt = xe_tile_get_main_gt_id(pf_fd, tile);
+	xe_sriov_pf_debugfs_read_provisioned_ranges(pf_fd, XE_SRIOV_SHARED_RES_GGTT,
+						    main_gt, range, &nr_ranges);
+	igt_assert_eq(num_vfs, nr_ranges);
+
+	test_data->pf_fd = pf_fd;
+	test_data->verify_on_pf = (vf_id > 0);
+	test_data->get_pte = intel_get_pte;
+
+	if (intel_graphics_ver(dev_id) >= IP_VER(12, 7))
+		test_data->set_pte = intel_set_pte_and_sync;
+	else
+		test_data->set_pte = intel_set_pte;
+
+	xe_mmio_vf_access_init(pf_fd, vf_id, &test_data->mmio);
+	if (test_data->verify_on_pf)
+		xe_mmio_access_init(pf_fd, &test_data->pf_mmio);
+
+	enable_bus_master(test_data);
+}
+
+static void fini_subtest(struct xe_sriov_provisioned_range *range,
+			 struct ggtt_test_data *test_data)
+{
+	disable_bus_master(test_data);
+
+	free(range);
+
+	if (test_data->verify_on_pf)
+		xe_mmio_access_fini(&test_data->pf_mmio);
+	xe_mmio_access_fini(&test_data->mmio);
+
+	igt_sriov_disable_vfs(test_data->pf_fd);
+}
+
+#define for_each_pte_test(tc__, testcases__) \
+	for ((tc__) = (testcases__); (tc__)->test; (tc__)++)
+
+static void pf_check_vfs_ids(int pf_fd, uint8_t tile, unsigned int num_vfs)
+{
+	struct xe_sriov_provisioned_range *range;
+	struct ggtt_test_data test_data;
+	xe_ggtt_addr_t addr;
+	xe_ggtt_pte_t out;
+	unsigned int vf;
+	bool is_correct;
+
+	init_subtest(pf_fd, 0, tile, &range, &test_data, num_vfs);
+
+	for_each_sriov_enabled_vf(pf_fd, vf_id) {
+		igt_assert_eq(vf_id, range[vf_id - 1].vf_id);
+		igt_assert_f(IS_ALIGNED(range[vf_id - 1].start, GGTT_PAGE_SIZE),
+			     "GGTT start range for VF%d (%#lx) is not aligned to %#x\n",
+			     vf_id, range[vf_id - 1].start, GGTT_PAGE_SIZE);
+
+		igt_debug("Checking VF%u range [%#lx-%#lx]\n",
+			  vf_id, range[vf_id - 1].start, range[vf_id - 1].end);
+		for_each_pte(addr, &range[vf_id - 1]) {
+			is_correct = check_pte_vfid(&test_data, tile, addr, vf_id, &out);
+			if (!is_correct) {
+				vf = vf_id;
+				goto out;
+			}
+		}
+	}
+
+out:
+	fini_subtest(range, &test_data);
+
+	igt_fail_on_f(!is_correct,
+		      "Found PTE from VF%d range with wrong VF ID: Read PTE: %#lx on address: %#lx\n",
+		      vf, out, addr);
+}
+
+static void pf_check_vfs_pte(int pf_fd, uint8_t tile)
+{
+	static struct pte_testcase pte_testcases[] = {
+		{ SRIOV_GGTT_TC(pte_pat_modifiable), .requires = has_pat },
+		{ SRIOV_GGTT_TC(pte_gpa_modifiable) },
+		{ SRIOV_GGTT_TC(pte_vfid_modifiable) },
+		{ SRIOV_GGTT_TC(pte_lmem_modifiable), .requires = has_lmem },
+		{ SRIOV_GGTT_TC(pte_valid_modifiable) },
+		{ },
+	};
+	struct xe_sriov_provisioned_range *range;
+	struct ggtt_test_data test_data;
+	struct pte_testcase *tc;
+	int failed = 0;
+
+	init_subtest(pf_fd, 0, tile, &range, &test_data, igt_sriov_get_total_vfs(pf_fd));
+
+	for_each_sriov_vf(pf_fd, vf_id) {
+		igt_debug("Checking VF%u range [%#lx-%#lx]\n", vf_id, range[vf_id - 1].start,
+			  range[vf_id - 1].end);
+		for_each_pte_test(tc, pte_testcases) {
+			igt_debug("Run '%s' check\n", tc->name);
+			if (!run_test_on_ggtt_range(&test_data, tile, &range[vf_id - 1], tc,
+						    vf_id))
+				failed++;
+		}
+	}
+
+	fini_subtest(range, &test_data);
+
+	igt_fail_on_f(failed, "%s: Count of failed test cases: %d\n", __func__, failed);
+}
+
+static void vfs_check_own_pte(int pf_fd, int vf_id, uint8_t tile)
+{
+	static struct pte_testcase pte_testcases[] = {
+		{ SRIOV_GGTT_TC(pte_pat_modifiable), .requires = has_pat },
+		{ SRIOV_GGTT_TC(pte_gpa_modifiable) },
+		{ SRIOV_GGTT_TC(pte_vfid_not_readable) },
+		{ SRIOV_GGTT_TC(pte_vfid_not_modifiable) },
+		{ SRIOV_GGTT_TC(pte_lmem_modifiable), .requires = has_lmem },
+		{ SRIOV_GGTT_TC(pte_valid_not_modifiable) },
+		{ },
+	};
+	struct xe_sriov_provisioned_range *range;
+	struct ggtt_test_data test_data;
+	struct pte_testcase *tc;
+	int failed = 0;
+
+	init_subtest(pf_fd, vf_id, tile, &range, &test_data, igt_sriov_get_total_vfs(pf_fd));
+
+	igt_debug("Checking VF%u range [%#lx-%#lx]\n", vf_id, range[vf_id - 1].start,
+		  range[vf_id - 1].end);
+	for_each_pte_test(tc, pte_testcases) {
+		igt_debug("Run '%s' check\n", tc->name);
+		if (!run_test_on_ggtt_range(&test_data, tile, &range[vf_id - 1], tc, vf_id))
+			failed++;
+	}
+
+	fini_subtest(range, &test_data);
+
+	igt_fail_on_f(failed, "%s: Count of failed test cases: %d\n", __func__, failed);
+}
+
+static void vfs_check_other_vfs_pte(int pf_fd, int vf_id, uint8_t tile)
+{
+	static struct pte_testcase pte_testcases[] = {
+		{ SRIOV_GGTT_TC(pte_not_accessible) },
+		{ SRIOV_GGTT_TC(pte_pat_not_modifiable), .requires = has_pat },
+		{ SRIOV_GGTT_TC(pte_gpa_not_modifiable) },
+		{ SRIOV_GGTT_TC(pte_vfid_not_modifiable) },
+		{ SRIOV_GGTT_TC(pte_lmem_not_modifiable), .requires = has_lmem },
+		{ SRIOV_GGTT_TC(pte_valid_not_modifiable) },
+		{ },
+	};
+	struct xe_sriov_provisioned_range *range;
+	struct ggtt_test_data test_data;
+	struct pte_testcase *tc;
+	int failed = 0;
+
+	init_subtest(pf_fd, vf_id, tile, &range, &test_data, igt_sriov_get_total_vfs(pf_fd));
+
+	for_each_pte_test(tc, pte_testcases) {
+		bool ret;
+
+		igt_debug("Run '%s' check\n", tc->name);
+		for_each_sriov_vf(pf_fd, other_vf_id) {
+			if (vf_id == other_vf_id)
+				continue;
+			igt_debug("Checking VF%u range [%#lx-%#lx]\n", other_vf_id,
+				  range[other_vf_id - 1].start, range[other_vf_id - 1].end);
+			ret = run_test_on_ggtt_range(&test_data, tile, &range[other_vf_id - 1], tc,
+						     vf_id);
+			if (!ret)
+				break;
+		}
+		if (!ret)
+			failed++;
+	}
+
+	fini_subtest(range, &test_data);
+
+	igt_fail_on_f(failed, "%s: Count of failed test cases: %d\n", __func__, failed);
+}
+
+static void assign_to_pf_other_vfs_ggtt(int pf_fd, uint8_t tile, struct xe_mmio *pf_mmio,
+					unsigned int skip_vf_id,
+					struct xe_sriov_provisioned_range *range)
+{
+	for_each_sriov_vf(pf_fd, vf_id) {
+		xe_ggtt_pte_t pte = GGTT_PAGE_PRESENT;
+		xe_ggtt_addr_t addr;
+
+		if (skip_vf_id == vf_id)
+			continue;
+
+		igt_debug("Set PF as owner for VF%u range [%#lx-%#lx]\n", vf_id,
+			  range[vf_id - 1].start, range[vf_id - 1].end);
+		for_each_pte(addr, &range[vf_id - 1])
+			intel_set_pte(pf_mmio, tile, addr, pte);
+	}
+}
+
+static void restore_other_vfs_ggtt(int pf_fd, uint8_t tile, struct xe_mmio *pf_mmio,
+				   unsigned int skip_vf_id,
+				   struct xe_sriov_provisioned_range *range)
+{
+	for_each_sriov_vf(pf_fd, vf_id) {
+		xe_ggtt_pte_t pte = ((vf_id << GGTT_PTE_VFID_SHIFT) &
+				     xe_ggtt_get_vfid_mask(pf_fd)) | GGTT_PAGE_PRESENT;
+		xe_ggtt_addr_t addr;
+
+		if (skip_vf_id == vf_id)
+			continue;
+
+		igt_debug("Restore the original owner for VF%u range [%#lx-%#lx]\n",
+			  vf_id, range[vf_id - 1].start, range[vf_id - 1].end);
+		for_each_pte(addr, &range[vf_id - 1])
+			intel_set_pte(pf_mmio, tile, addr, pte);
+	}
+}
+
+static void vfs_check_pf_pte(int pf_fd, int vf_id, uint8_t tile)
+{
+	static struct pte_testcase pte_testcases[] = {
+		{ SRIOV_GGTT_TC(pte_not_accessible) },
+		{ SRIOV_GGTT_TC(pte_pat_not_modifiable), .requires = has_pat },
+		{ SRIOV_GGTT_TC(pte_gpa_not_modifiable) },
+		{ SRIOV_GGTT_TC(pte_vfid_not_modifiable) },
+		{ SRIOV_GGTT_TC(pte_lmem_not_modifiable), .requires = has_lmem },
+		{ SRIOV_GGTT_TC(pte_valid_not_modifiable) },
+		{ },
+	};
+	struct xe_sriov_provisioned_range *range;
+	struct ggtt_test_data test_data;
+	struct pte_testcase *tc;
+	int failed = 0;
+
+	init_subtest(pf_fd, vf_id, tile, &range, &test_data, igt_sriov_get_total_vfs(pf_fd));
+	assign_to_pf_other_vfs_ggtt(pf_fd, tile, &test_data.pf_mmio, vf_id, range);
+
+	igt_debug("Checking GGTT outside VF%u range [%#lx-%#lx]\n",
+		  vf_id, range[vf_id - 1].start, range[vf_id - 1].end);
+
+	for_each_pte_test(tc, pte_testcases) {
+		igt_debug("Run '%s' check\n", tc->name);
+		if (!run_test_outside_ggtt_range(&test_data, tile, &range[vf_id - 1], tc, vf_id))
+			failed++;
+	}
+
+	restore_other_vfs_ggtt(pf_fd, tile, &test_data.pf_mmio, vf_id, range);
+	fini_subtest(range, &test_data);
+
+	igt_fail_on_f(failed, "%s: Count of failed test cases: %d\n", __func__, failed);
+}
+
+static void skip_on_mtl_vf(int pf_fd)
+{
+	uint16_t dev_id = intel_get_drm_devid(pf_fd);
+
+	igt_skip_on_f(IS_METEORLAKE(dev_id),
+		      "On MTL VF there is no access to GGTT through MMIO, skip\n");
+}
+
+static int opts_handler(int opt, int opt_index, void *data)
+{
+	switch (opt) {
+	case 'b':
+		skip_vf_bus_master = true;
+		break;
+	case 'p':
+		verify_each_pte_in_range = true;
+		break;
+	default:
+		return IGT_OPT_HANDLER_ERROR;
+	}
+
+	return IGT_OPT_HANDLER_SUCCESS;
+}
+
+static const struct option long_opts[] = {
+	{ .name = "skip-vf-bme", .has_arg = false, .val = 'b', },
+	{ .name = "verify-each-pte", .has_arg = false, .val = 'p', },
+	{}
+};
+
+static const char help_str[] =
+	"  --skip-vf-bme\tSkip setting VF Bus Master Enable bit\n"
+	"  --verify-each-pte\tVerify each PTE in range\n";
+
+int igt_main_args("", long_opts, help_str, opts_handler, NULL)
+{
+	bool autoprobe;
+	uint8_t tile;
+	int pf_fd;
+
+	igt_fixture()
+	{
+		pf_fd = drm_open_driver(DRIVER_XE);
+		igt_require(igt_sriov_is_pf(pf_fd));
+		igt_require(igt_sriov_get_enabled_vfs(pf_fd) == 0);
+		autoprobe = igt_sriov_is_driver_autoprobe_enabled(pf_fd);
+	}
+
+	igt_describe("Verify that the VF ID in the GGTT PTE has been correctly assigned");
+	igt_subtest_with_dynamic("pf-check-vfs-ids")
+		for_each_sriov_num_vfs(pf_fd, num_vfs)
+			xe_for_each_tile(pf_fd, tile)
+				igt_dynamic_f("numvfs-%u-tile-%u", num_vfs, tile)
+					pf_check_vfs_ids(pf_fd, tile, num_vfs);
+
+	igt_describe("Verify on PF the modifiability of GGTT PTE assigned to VFs");
+	igt_subtest_with_dynamic("pf-check-vfs-pte")
+		xe_for_each_tile(pf_fd, tile)
+			igt_dynamic_f("tile-%u", tile)
+				pf_check_vfs_pte(pf_fd, tile);
+
+	igt_describe("Verify of modifiability of GGTT PTE owned by VF");
+	igt_subtest_with_dynamic("vfs-check-own-pte") {
+		skip_on_mtl_vf(pf_fd);
+		for_each_sriov_vf(pf_fd, vf_id)
+			xe_for_each_tile(pf_fd, tile)
+				igt_dynamic_f("vf%u-tile-%u", vf_id, tile)
+					vfs_check_own_pte(pf_fd, vf_id, tile);
+	}
+
+	igt_describe("Verify the non-availability of VF to GGTT PTEs owned by other VFs");
+	igt_subtest_with_dynamic("vfs-check-other-vfs-pte") {
+		igt_require(igt_sriov_get_total_vfs(pf_fd) > 1);
+		skip_on_mtl_vf(pf_fd);
+		for_each_sriov_vf(pf_fd, vf_id)
+			xe_for_each_tile(pf_fd, tile)
+				igt_dynamic_f("vf%u-tile-%u", vf_id, tile)
+					vfs_check_other_vfs_pte(pf_fd, vf_id, tile);
+	}
+
+	igt_describe("Verify the non-availability of VF to GGTT PTEs owned by PF");
+	igt_subtest_with_dynamic("vfs-check-pf-pte") {
+		skip_on_mtl_vf(pf_fd);
+		for_each_sriov_vf(pf_fd, vf_id)
+			xe_for_each_tile(pf_fd, tile)
+				igt_dynamic_f("vf%u-tile-%u", vf_id, tile)
+					vfs_check_pf_pte(pf_fd, vf_id, tile);
+	}
+
+	igt_fixture() {
+		igt_sriov_disable_vfs(pf_fd);
+		/* abort to avoid execution of next tests with enabled VFs */
+		igt_abort_on_f(igt_sriov_get_enabled_vfs(pf_fd) > 0, "Failed to disable VF(s)");
+		autoprobe ? igt_sriov_enable_driver_autoprobe(pf_fd) :
+			    igt_sriov_disable_driver_autoprobe(pf_fd);
+		igt_abort_on_f(autoprobe != igt_sriov_is_driver_autoprobe_enabled(pf_fd),
+			       "Failed to restore sriov_drivers_autoprobe value\n");
+		drm_close_driver(pf_fd);
+	}
+}
diff --git a/tests/meson.build b/tests/meson.build
index a62f447df..1ac89bab7 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -341,6 +341,7 @@ intel_xe_progs = [
 	'xe_sriov_admin',
 	'xe_sriov_auto_provisioning',
 	'xe_sriov_flr',
+	'xe_sriov_ggtt',
 	'xe_sriov_vfio',
 	'xe_sriov_scheduling',
         'xe_survivability',
-- 
2.43.0