intel-gfx.lists.freedesktop.org archive mirror
 help / color / mirror / Atom feed
* [PATCH i-g-t v3 0/3] Add initial Panthor tests
@ 2025-11-04 20:28 Daniel Almeida
  2025-11-04 20:28 ` [PATCH i-g-t v3 1/3] lib: add support for opening Panthor devices Daniel Almeida
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Daniel Almeida @ 2025-11-04 20:28 UTC (permalink / raw)
  To: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, boris.brezillon,
	liviu.dudau, steven.price, aliceryhl, jeffv
  Cc: Daniel Almeida, intel-gfx, igt-dev, dri-devel

This series adds basic Panthor tests. In particular, these are being
used to test both Panthor[0] and Tyr[1], i.e.: the new Rust GPU driver
that implements Panthor's uAPI (i.e.: panthor_drm.h). Most of the
initial tests were chosen in order to have something to test Tyr with,
but this series lays the groundwork so that more interesting tests can
be added to test more of Panthor itself.

This work is being tested on a RockPi 5, featuring an rk3588 SoC and
Mali-G610 Valhall.

Note that there's a few (less than five?) remaining checkpatch.pl
comments about long lines. IMHO there's no way to format them better so
I hope we can live with this.

[0]: https://patchwork.freedesktop.org/patch/msgid/20240229162230.2634044-12-boris.brezillon@collabora.com
[1]: https://lore.kernel.org/dri-devel/aMLB0Vs0dJ_AkU4z@google.com/

Changes from v2:

Thanks, Boris {
  - New helpers for command stream manipulation (see "struct cs_instr")
  - Reworked bad indentation and formatting
  - Removed BE support (UMD doesn't care about this either)
}

Thanks, Daniel Stone {
  - Introduced igt_panthor_get_first_core(), which correctly computes
    the first available core using ffs()
  - Stopped hardcoding the page size and switched to getpagesize()
    instead,
  - Switched to more specific versions of igt_assert as appropriate (like
    igt_assert_neq(), etc)
  - Simplified panthor_group.c considerably by relying on the helpers in
    igt_panthor.[ch]
  - Switched to the syncobj_create() and syncobj_destroy() helpers.
}

- Added group creation helpers with sensible defaults (see
  igt_panthor_group_create_simple(), for example)
- Picked up tags

- Link to v2: https://lore.kernel.org/dri-devel/20250912181931.3738444-1-daniel.almeida@collabora.com/

Changes from v1:
- Rebased on top of the latest master
- Squashed patch 3 from v1 into patch 2.
- Switched to /* */ comments in headers
- Initialized padding fields to 0 as applicable in group_destroy and
  vm_destroy
- Removed wrong assert(gpu_rev != 0)
- Changed indentation to use tabs
- Rework igt_panthor_mmap_bo to take an offset (so we don't call the
  mmap_offset ioctl twice)
- Added igt_describe and docs to the functions igt_panthor.c
- Linked to the driver in the cover letter and patch 1.
- Improved the commit message for patch 1.
Link to v1: https://lore.kernel.org/dri-devel/20250828130402.2549948-1-daniel.almeida@collabora.com/



Daniel Almeida (3):
  lib: add support for opening Panthor devices
  panthor: add initial infrastructure
  tests/panthor: add panthor tests

 lib/drmtest.c                 |   1 +
 lib/drmtest.h                 |   1 +
 lib/igt_panthor.c             | 372 ++++++++++++++++++++++++++++++++++
 lib/igt_panthor.h             | 221 ++++++++++++++++++++
 lib/meson.build               |   1 +
 meson.build                   |   8 +
 tests/meson.build             |   2 +
 tests/panthor/meson.build     |  15 ++
 tests/panthor/panthor_gem.c   |  72 +++++++
 tests/panthor/panthor_group.c | 123 +++++++++++
 tests/panthor/panthor_query.c |  29 +++
 tests/panthor/panthor_vm.c    |  84 ++++++++
 12 files changed, 929 insertions(+)
 create mode 100644 lib/igt_panthor.c
 create mode 100644 lib/igt_panthor.h
 create mode 100644 tests/panthor/meson.build
 create mode 100644 tests/panthor/panthor_gem.c
 create mode 100644 tests/panthor/panthor_group.c
 create mode 100644 tests/panthor/panthor_query.c
 create mode 100644 tests/panthor/panthor_vm.c

-- 
2.51.0


^ permalink raw reply	[flat|nested] 12+ messages in thread

* [PATCH i-g-t v3 1/3] lib: add support for opening Panthor devices
  2025-11-04 20:28 [PATCH i-g-t v3 0/3] Add initial Panthor tests Daniel Almeida
@ 2025-11-04 20:28 ` Daniel Almeida
  2025-11-06  9:24   ` Boris Brezillon
  2025-11-04 20:28 ` [PATCH i-g-t v3 2/3] panthor: add initial infrastructure Daniel Almeida
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 12+ messages in thread
From: Daniel Almeida @ 2025-11-04 20:28 UTC (permalink / raw)
  To: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, boris.brezillon,
	liviu.dudau, steven.price, aliceryhl, jeffv
  Cc: Daniel Almeida, intel-gfx, igt-dev, dri-devel

We will be adding tests for Panthor in a subsequent patch, so first add
the ability to open the device.

Reviewed-by: Kamil Konieczny <kamil.konieczny@linux.intel.com>
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 lib/drmtest.c | 1 +
 lib/drmtest.h | 1 +
 2 files changed, 2 insertions(+)

diff --git a/lib/drmtest.c b/lib/drmtest.c
index 590c4671a..3d8c8e79e 100644
--- a/lib/drmtest.c
+++ b/lib/drmtest.c
@@ -220,6 +220,7 @@ static const struct module {
 	{ DRIVER_INTEL, "i915", modprobe_i915 },
 	{ DRIVER_MSM, "msm" },
 	{ DRIVER_PANFROST, "panfrost" },
+	{ DRIVER_PANTHOR, "panthor" },
 	{ DRIVER_V3D, "v3d" },
 	{ DRIVER_VC4, "vc4" },
 	{ DRIVER_VGEM, "vgem" },
diff --git a/lib/drmtest.h b/lib/drmtest.h
index 8416aa491..4b0a71102 100644
--- a/lib/drmtest.h
+++ b/lib/drmtest.h
@@ -57,6 +57,7 @@ int __get_drm_device_name(int fd, char *name, int name_size);
 #define DRIVER_VMWGFX   (1 << 8)
 #define DRIVER_VKMS	(1 << 9)
 #define DRIVER_VIRTIO	(1 << 10)
+#define DRIVER_PANTHOR	(1 << 11)
 
 /*
  * Exclude DRIVER_VGEM and DRIVER_VIRTIO from DRIVER_ANY since if you run
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH i-g-t v3 2/3] panthor: add initial infrastructure
  2025-11-04 20:28 [PATCH i-g-t v3 0/3] Add initial Panthor tests Daniel Almeida
  2025-11-04 20:28 ` [PATCH i-g-t v3 1/3] lib: add support for opening Panthor devices Daniel Almeida
@ 2025-11-04 20:28 ` Daniel Almeida
  2025-11-06  9:25   ` Boris Brezillon
  2025-11-04 20:28 ` [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests Daniel Almeida
  2025-11-06  9:29 ` [PATCH i-g-t v3 0/3] Add initial Panthor tests Boris Brezillon
  3 siblings, 1 reply; 12+ messages in thread
From: Daniel Almeida @ 2025-11-04 20:28 UTC (permalink / raw)
  To: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, boris.brezillon,
	liviu.dudau, steven.price, aliceryhl, jeffv
  Cc: Daniel Almeida, intel-gfx, igt-dev, dri-devel

Add the necessary code needed to compile panthor tests as well as the
basic infrastructure that will be used by the Panthor tests themselves.

To make sure everything is in order, add a basic test in
panthor_query.c.

Acked-by: Kamil Konieczny <kamil.konieczny@linux.intel.com>
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 lib/igt_panthor.c             |  41 ++++++++
 lib/igt_panthor.h             | 193 ++++++++++++++++++++++++++++++++++
 lib/meson.build               |   1 +
 meson.build                   |   8 ++
 tests/meson.build             |   2 +
 tests/panthor/meson.build     |  12 +++
 tests/panthor/panthor_query.c |  29 +++++
 7 files changed, 286 insertions(+)
 create mode 100644 lib/igt_panthor.c
 create mode 100644 lib/igt_panthor.h
 create mode 100644 tests/panthor/meson.build
 create mode 100644 tests/panthor/panthor_query.c

diff --git a/lib/igt_panthor.c b/lib/igt_panthor.c
new file mode 100644
index 000000000..0b690f796
--- /dev/null
+++ b/lib/igt_panthor.c
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
+
+#include "drmtest.h"
+#include "igt_panthor.h"
+#include "ioctl_wrappers.h"
+#include "panthor_drm.h"
+
+/**
+ * SECTION:igt_panthor
+ * @short_description: Panthor support library
+ * @title: Panthor
+ * @include: igt.h
+ *
+ * This library provides various auxiliary helper functions for writing Panthor
+ * tests.
+ */
+
+/**
+ * igt_panthor_query:
+ * @fd: device file descriptor
+ * @type: query type (e.g., DRM_PANTHOR_DEV_QUERY_GPU_INFO)
+ * @data: pointer to a struct to store the query result
+ * @size: size of the result struct
+ * @err: expected error code, or 0 for success
+ *
+ * Query GPU information.
+ */
+void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err)
+{
+	struct drm_panthor_dev_query query = {
+		.type = type,
+		.pointer = (uintptr_t)data,
+		.size = size,
+	};
+
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_DEV_QUERY, &query, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_DEV_QUERY, &query);
+}
diff --git a/lib/igt_panthor.h b/lib/igt_panthor.h
new file mode 100644
index 000000000..6f94b8f79
--- /dev/null
+++ b/lib/igt_panthor.h
@@ -0,0 +1,193 @@
+/* SPDX-License-Identifier: MIT */
+/* SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd. */
+
+#ifndef IGT_PANTHOR_H
+#define IGT_PANTHOR_H
+
+#include <stddef.h>
+#include <stdint.h>
+#include <stdbool.h>
+
+void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err);
+
+enum cs_opcode {
+	CS_OPCODE_NOP = 0,
+	CS_OPCODE_MOVE48 = 1,
+	CS_OPCODE_MOVE32 = 2,
+	CS_OPCODE_WAIT = 3,
+	CS_OPCODE_STM = 21,
+	CS_OPCODE_FLUSH_CACHE = 36,
+};
+
+enum cs_flush_mode {
+	CS_FLUSH_MODE_NONE = 0,
+	CS_FLUSH_MODE_CLEAN = 1,
+	CS_FLUSH_MODE_INVALIDATE = 2,
+	CS_FLUSH_MODE_CLEAN_AND_INVALIDATE = 3,
+};
+
+/* There's no plan to support big endian in the UMD, so keep
+ * things simple and don't bother supporting it here either.
+ */
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#error "big endian not supported"
+#endif
+
+struct cs_instr {
+	union {
+		struct {
+			uint64_t data: 56;
+			uint64_t opcode: 8;
+		} any;
+		struct {
+			uint64_t unused: 56;
+			uint64_t opcode: 8;
+		} nop;
+		struct {
+			uint64_t immediate: 48;
+			uint64_t dest: 8;
+			uint64_t opcode: 8;
+		} move48;
+		struct {
+			uint64_t immediate: 32;
+			uint64_t unused: 16;
+			uint64_t dest: 8;
+			uint64_t opcode: 8;
+		} move32;
+		struct {
+			uint64_t unused0: 16;
+			uint64_t wait_mask: 16;
+			uint64_t progress_inc: 1;
+			uint64_t unused1: 23;
+			uint64_t opcode: 8;
+		} wait;
+		struct {
+			uint64_t offset: 16;
+			uint64_t mask: 16;
+			uint64_t unused: 8;
+			uint64_t address: 8;
+			uint64_t src: 8;
+			uint64_t opcode: 8;
+		} stm;
+		struct {
+			uint64_t l2_mode: 4;
+			uint64_t lsc_mode: 4;
+			uint64_t other_mode: 4;
+			uint64_t unused0: 4;
+			uint64_t wait_mask: 16;
+			uint64_t unused1: 8;
+			uint64_t flush_id: 8;
+			uint64_t signal_slot: 4;
+			uint64_t unused2: 4;
+			uint64_t opcode: 8;
+		} flush;
+		uint64_t raw;
+	};
+};
+
+static inline uint64_t
+cs_nop(void)
+{
+	struct cs_instr instr = {
+		.nop = {
+			.opcode = CS_OPCODE_NOP,
+		},
+	};
+
+	return instr.raw;
+}
+
+static inline uint64_t
+cs_mov48(uint8_t dst, uint64_t imm)
+{
+	struct cs_instr instr = {
+		.move48 = {
+			.opcode = CS_OPCODE_MOVE48,
+			.dest = dst,
+			.immediate = (uint64_t)imm & 0xffffffffffffull,
+		},
+	};
+
+	return instr.raw;
+}
+
+static inline uint64_t
+cs_mov32(uint8_t dst, uint32_t imm)
+{
+	struct cs_instr instr = {
+		.move32 = {
+			.opcode = CS_OPCODE_MOVE32,
+			.dest = dst,
+			.immediate = (uint32_t)imm,
+		},
+	};
+
+	return instr.raw;
+}
+
+static inline uint64_t
+cs_wait(uint16_t wait_mask, bool progress_inc)
+{
+	struct cs_instr instr = {
+		.wait = {
+			.opcode = CS_OPCODE_WAIT,
+			.wait_mask = wait_mask,
+			.progress_inc = progress_inc,
+		},
+	};
+
+	return instr.raw;
+}
+
+static inline uint64_t
+cs_stm(uint8_t address, uint8_t src, uint16_t mask, int16_t offset)
+{
+	struct cs_instr instr = {
+		.stm = {
+			.opcode = CS_OPCODE_STM,
+			.offset = (uint16_t)offset,
+			.mask = mask,
+			.src = src,
+			.address = address,
+		},
+	};
+
+	return instr.raw;
+}
+
+static inline uint64_t
+cs_stm32(uint8_t address, uint8_t src, int16_t offset)
+{
+	return cs_stm(address, src, 0x1, offset);
+}
+
+static inline uint64_t
+cs_stm64(uint8_t address, uint8_t src, int16_t offset)
+{
+	return cs_stm(address, src, 0x3, offset);
+}
+
+static inline uint64_t
+cs_flush(enum cs_flush_mode l2_mode,
+	 enum cs_flush_mode lsc_mode,
+	 enum cs_flush_mode other_mode,
+	 uint16_t wait_mask,
+	 uint8_t flush_id,
+	 uint8_t signal_slot)
+{
+	struct cs_instr instr = {
+		.flush = {
+			.l2_mode = l2_mode,
+			.lsc_mode = lsc_mode,
+			.other_mode = other_mode,
+			.wait_mask = wait_mask,
+			.flush_id = flush_id,
+			.signal_slot = signal_slot,
+			.opcode = CS_OPCODE_FLUSH_CACHE,
+		},
+	};
+
+	return instr.raw;
+}
+
+#endif /* IGT_PANTHOR_H */
diff --git a/lib/meson.build b/lib/meson.build
index 725a46e98..707ce6ff9 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -106,6 +106,7 @@ lib_sources = [
 	'igt_kmod.c',
 	'igt_ktap.c',
 	'igt_panfrost.c',
+	'igt_panthor.c',
 	'igt_v3d.c',
 	'igt_vc4.c',
 	'igt_vmwgfx.c',
diff --git a/meson.build b/meson.build
index db6e09a94..bfcffbb9a 100644
--- a/meson.build
+++ b/meson.build
@@ -288,6 +288,7 @@ libexecdir = join_paths(get_option('libexecdir'), 'igt-gpu-tools')
 amdgpudir = join_paths(libexecdir, 'amdgpu')
 msmdir = join_paths(libexecdir, 'msm')
 panfrostdir = join_paths(libexecdir, 'panfrost')
+panthordir = join_paths(libexecdir, 'panthor')
 v3ddir = join_paths(libexecdir, 'v3d')
 vc4dir = join_paths(libexecdir, 'vc4')
 vkmsdir = join_paths(libexecdir, 'vkms')
@@ -341,6 +342,12 @@ if get_option('use_rpath')
 	endforeach
 	panfrost_rpathdir = join_paths(panfrost_rpathdir, libdir)
 
+	panthor_rpathdir = '$ORIGIN'
+	foreach p : panthordir.split('/')
+		panthor_rpathdir = join_paths(panthor_rpathdir, '..')
+	endforeach
+	panthor_rpathdir = join_paths(panthor_rpathdir, libdir)
+
 	v3d_rpathdir = '$ORIGIN'
 	foreach p : v3ddir.split('/')
 		v3d_rpathdir = join_paths(v3d_rpathdir, '..')
@@ -370,6 +377,7 @@ else
 	amdgpudir_rpathdir = ''
 	msm_rpathdir = ''
 	panfrost_rpathdir = ''
+	panthor_rpathdir = ''
 	v3d_rpathdir = ''
 	vc4_rpathdir = ''
 	vkms_rpathdir = ''
diff --git a/tests/meson.build b/tests/meson.build
index f2267bc09..e1fdac2cf 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -490,6 +490,8 @@ subdir('msm')
 
 subdir('panfrost')
 
+subdir('panthor')
+
 subdir('v3d')
 
 subdir('vc4')
diff --git a/tests/panthor/meson.build b/tests/panthor/meson.build
new file mode 100644
index 000000000..ce13aebaa
--- /dev/null
+++ b/tests/panthor/meson.build
@@ -0,0 +1,12 @@
+panthor_progs = [
+	'panthor_query'
+]
+
+foreach prog : panthor_progs
+	test_executables += executable(prog, prog + '.c',
+				       dependencies : test_deps,
+				       install_dir : panthordir,
+				       install_rpath : panthor_rpathdir,
+				       install : true)
+	test_list += join_paths('panthor', prog)
+endforeach
diff --git a/tests/panthor/panthor_query.c b/tests/panthor/panthor_query.c
new file mode 100644
index 000000000..81c2ebd23
--- /dev/null
+++ b/tests/panthor/panthor_query.c
@@ -0,0 +1,29 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
+
+#include "igt.h"
+#include "igt_core.h"
+#include "igt_panthor.h"
+#include "panthor_drm.h"
+#include <stdint.h>
+
+igt_main {
+	int fd;
+
+	igt_fixture {
+		fd = drm_open_driver(DRIVER_PANTHOR);
+	}
+
+	igt_describe("Query GPU information from ROM.");
+	igt_subtest("query") {
+		struct drm_panthor_gpu_info gpu = {};
+
+		igt_panthor_query(fd, DRM_PANTHOR_DEV_QUERY_GPU_INFO, &gpu, sizeof(gpu), 0);
+
+		igt_assert_neq(gpu.gpu_id, 0);
+	}
+
+	igt_fixture {
+		drm_close_driver(fd);
+	}
+}
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests
  2025-11-04 20:28 [PATCH i-g-t v3 0/3] Add initial Panthor tests Daniel Almeida
  2025-11-04 20:28 ` [PATCH i-g-t v3 1/3] lib: add support for opening Panthor devices Daniel Almeida
  2025-11-04 20:28 ` [PATCH i-g-t v3 2/3] panthor: add initial infrastructure Daniel Almeida
@ 2025-11-04 20:28 ` Daniel Almeida
  2025-11-04 21:04   ` Boris Brezillon
  2025-11-06  9:26   ` Boris Brezillon
  2025-11-06  9:29 ` [PATCH i-g-t v3 0/3] Add initial Panthor tests Boris Brezillon
  3 siblings, 2 replies; 12+ messages in thread
From: Daniel Almeida @ 2025-11-04 20:28 UTC (permalink / raw)
  To: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, boris.brezillon,
	liviu.dudau, steven.price, aliceryhl, jeffv
  Cc: Daniel Almeida, intel-gfx, igt-dev, dri-devel

Add an initial test suit covering query device properties, allocating
memory, binding and unbinding VA ranges through VM_BIND and submitting a
simple piece of work through GROUP_SUBMIT.

Acked-by: Kamil Konieczny <kamil.konieczny@linux.intel.com>
Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
---
 lib/igt_panthor.c             | 331 ++++++++++++++++++++++++++++++++++
 lib/igt_panthor.h             |  28 +++
 tests/panthor/meson.build     |   5 +-
 tests/panthor/panthor_gem.c   |  72 ++++++++
 tests/panthor/panthor_group.c | 123 +++++++++++++
 tests/panthor/panthor_vm.c    |  84 +++++++++
 6 files changed, 642 insertions(+), 1 deletion(-)
 create mode 100644 tests/panthor/panthor_gem.c
 create mode 100644 tests/panthor/panthor_group.c
 create mode 100644 tests/panthor/panthor_vm.c

diff --git a/lib/igt_panthor.c b/lib/igt_panthor.c
index 0b690f796..73ada9c59 100644
--- a/lib/igt_panthor.c
+++ b/lib/igt_panthor.c
@@ -6,6 +6,148 @@
 #include "ioctl_wrappers.h"
 #include "panthor_drm.h"
 
+/**
+ * igt_panthor_group_create:
+ * @fd: device file descriptor
+ * @group_create: pointer to group creation structure
+ * @err: expected error code, or 0 for success
+ *
+ * Create a group.
+ */
+void igt_panthor_group_create(int fd, struct drm_panthor_group_create *group_create, int err)
+{
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_CREATE, group_create, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_CREATE, group_create);
+}
+
+/**
+ * igt_panthor_group_destroy:
+ * @fd: device file descriptor
+ * @group_handle: group handle to destroy
+ * @err: expected error code, or 0 for success
+ *
+ * Destroy a group.
+ */
+void igt_panthor_group_destroy(int fd, uint32_t group_handle, int err)
+{
+	struct drm_panthor_group_destroy group_destroy = {
+		.group_handle = group_handle,
+	};
+
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_DESTROY, &group_destroy, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_DESTROY, &group_destroy);
+}
+
+/**
+ * igt_panthor_group_submit:
+ * @fd: device file descriptor
+ * @group_submit: pointer to group submission structure
+ * @err: expected error code, or 0 for success
+ *
+ * Submit work to a group.
+ */
+void igt_panthor_group_submit(int fd, struct drm_panthor_group_submit *group_submit, int err)
+{
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_SUBMIT, group_submit, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_SUBMIT, group_submit);
+}
+
+/**
+ * igt_panthor_get_first_core:
+ * @cores_present: bitmask of available cores
+ *
+ * Get a mask with only the first available core bit set.
+ *
+ * Returns: core mask with first available core, or 0 if no cores available
+ */
+uint64_t igt_panthor_get_first_core(uint64_t cores_present)
+{
+	if (cores_present == 0)
+		return 0;
+
+	return 1ULL << (ffs(cores_present) - 1);
+}
+
+/**
+ * igt_panthor_group_create_simple:
+ * @fd: device file descriptor
+ * @vm_id: VM ID to associate with the group
+ * @err: expected error code, or 0 for success
+ *
+ * Create a group with a single queue and reasonable defaults.
+ *
+ * Returns: group handle on success
+ */
+uint32_t igt_panthor_group_create_simple(int fd, uint32_t vm_id, int err)
+{
+	struct drm_panthor_gpu_info gpu_info = {};
+	struct drm_panthor_group_create group_create = {};
+	struct drm_panthor_queue_create queue = {};
+	struct drm_panthor_obj_array queues = {};
+
+	igt_panthor_query(fd, DRM_PANTHOR_DEV_QUERY_GPU_INFO, &gpu_info, sizeof(gpu_info), 0);
+
+	queue.priority = 0;
+	queue.ringbuf_size = 4096;
+	queues = (struct drm_panthor_obj_array)DRM_PANTHOR_OBJ_ARRAY(1, &queue);
+
+	group_create.queues = queues;
+	group_create.max_compute_cores = 1;
+	group_create.max_fragment_cores = 1;
+	group_create.max_tiler_cores = 1;
+	group_create.priority = PANTHOR_GROUP_PRIORITY_MEDIUM;
+	group_create.compute_core_mask = igt_panthor_get_first_core(gpu_info.shader_present);
+	group_create.fragment_core_mask = igt_panthor_get_first_core(gpu_info.shader_present);
+	group_create.tiler_core_mask = igt_panthor_get_first_core(gpu_info.tiler_present);
+	group_create.vm_id = vm_id;
+
+	igt_panthor_group_create(fd, &group_create, err);
+	return group_create.group_handle;
+}
+
+/**
+ * igt_panthor_group_submit_simple:
+ * @fd: device file descriptor
+ * @group_handle: group handle to submit to
+ * @queue_index: queue index within the group
+ * @stream_addr: GPU address of the command stream
+ * @stream_size: size of the command stream
+ * @syncobj_handle: sync object handle for completion signaling
+ * @err: expected error code, or 0 for success
+ *
+ * Submit work to a group queue with a simple interface.
+ */
+void igt_panthor_group_submit_simple(int fd, uint32_t group_handle,
+				     uint32_t queue_index, uint64_t stream_addr,
+				     uint32_t stream_size, uint32_t syncobj_handle,
+				     int err)
+{
+	struct drm_panthor_group_submit group_submit = {};
+	struct drm_panthor_queue_submit queue_submit = {};
+	struct drm_panthor_sync_op sync_op = {};
+
+	sync_op.handle = syncobj_handle;
+	sync_op.flags = DRM_PANTHOR_SYNC_OP_SIGNAL;
+
+	queue_submit.syncs = (struct drm_panthor_obj_array)DRM_PANTHOR_OBJ_ARRAY(1, &sync_op);
+	queue_submit.queue_index = queue_index;
+	queue_submit.stream_size = stream_size;
+	queue_submit.stream_addr = stream_addr;
+	queue_submit.latest_flush = 0;
+
+	group_submit.group_handle = group_handle;
+	group_submit.queue_submits = (struct drm_panthor_obj_array)
+		DRM_PANTHOR_OBJ_ARRAY(1, &queue_submit);
+
+	igt_panthor_group_submit(fd, &group_submit, err);
+}
+
 /**
  * SECTION:igt_panthor
  * @short_description: Panthor support library
@@ -39,3 +181,192 @@ void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err)
 	else
 		do_ioctl(fd, DRM_IOCTL_PANTHOR_DEV_QUERY, &query);
 }
+
+/**
+ * igt_panthor_vm_create:
+ * @fd: device file descriptor
+ * @vm_id: pointer to store the created VM ID
+ * @err: expected error code, or 0 for success
+ *
+ * Creates a VM.
+ */
+void igt_panthor_vm_create(int fd, uint32_t *vm_id, int err)
+{
+	struct drm_panthor_vm_create vm_create = {};
+
+	if (err) {
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create, err);
+	} else {
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
+		*vm_id = vm_create.id;
+	}
+}
+
+/**
+ * igt_panthor_vm_destroy:
+ * @fd: device file descriptor
+ * @vm_id: VM ID to destroy
+ * @err: expected error code, or 0 for success
+ *
+ * Destroys a VM.
+ */
+void igt_panthor_vm_destroy(int fd, uint32_t vm_id, int err)
+{
+	struct drm_panthor_vm_destroy vm_destroy = {
+		.id = vm_id,
+	};
+
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
+}
+
+/**
+ * igt_panthor_vm_bind:
+ * @fd: device file descriptor
+ * @vm_id: VM ID to bind the buffer to
+ * @bo_handle: buffer object handle to bind
+ * @va: virtual address to bind at
+ * @size: size of the binding
+ * @flags: binding flags
+ * @err: expected error code, or 0 for success
+ *
+ * Bind a buffer object to a virtual address in the specified VM.
+ */
+void igt_panthor_vm_bind(int fd, uint32_t vm_id, uint32_t bo_handle,
+			 uint64_t va, uint64_t size, uint32_t flags, int err)
+{
+	struct drm_panthor_vm_bind_op bind_op = {
+		.flags = flags,
+		.bo_handle = bo_handle,
+		.va = va,
+		.size = size,
+	};
+
+	struct drm_panthor_vm_bind vm_bind = {
+		.vm_id = vm_id,
+		.flags = 0,
+		.ops = DRM_PANTHOR_OBJ_ARRAY(1, &bind_op),
+	};
+
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_BIND, &vm_bind, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_BIND, &vm_bind);
+}
+
+/**
+ * igt_panthor_bo_create:
+ * @fd: device file descriptor
+ * @bo: pointer to panthor_bo structure to initialize
+ * @size: requested buffer size in bytes
+ * @flags: buffer object creation flags
+ * @err: expected error code, or 0 for success
+ *
+ * Creates a new buffer object
+ */
+void igt_panthor_bo_create(int fd, struct panthor_bo *bo,
+			   uint64_t size, uint32_t flags, int err)
+{
+	struct drm_panthor_bo_create bo_create = {
+		.size = size,
+		.flags = flags,
+	};
+
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_BO_CREATE, &bo_create, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_BO_CREATE, &bo_create);
+
+	bo->handle = bo_create.handle;
+	bo->size = bo_create.size;
+	bo->offset = 0;
+	bo->map = NULL;
+}
+
+/**
+ * igt_panthor_bo_mmap_offset:
+ * @fd: device file descriptor
+ * @handle: buffer object handle
+ * @err: expected error code, or 0 for success
+ *
+ * Get the mmap offset for a buffer object.
+ *
+ * Returns: the mmap offset for the buffer object
+ */
+uint64_t igt_panthor_bo_mmap_offset(int fd, uint32_t handle, int err)
+{
+	struct drm_panthor_bo_mmap_offset bo_mmap_offset = {
+		.handle = handle,
+	};
+
+	if (err)
+		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_BO_MMAP_OFFSET, &bo_mmap_offset, err);
+	else
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_BO_MMAP_OFFSET, &bo_mmap_offset);
+
+	return bo_mmap_offset.offset;
+}
+
+/**
+ * igt_panthor_mmap_bo:
+ * @fd: device file descriptor
+ * @handle: buffer object handle
+ * @size: size of the buffer to map
+ * @prot: memory protection flags (e.g., PROT_READ | PROT_WRITE)
+ * @offset: mmap offset for the buffer object
+ *
+ * Map a buffer object into the process address space.
+ *
+ * Returns: pointer to the mapped memory, or NULL on failure
+ */
+void *igt_panthor_mmap_bo(int fd, uint32_t handle, uint64_t size,
+			  unsigned int prot, uint64_t offset)
+{
+	void *ptr;
+
+	ptr = mmap(0, size, prot, MAP_SHARED, fd, offset);
+	if (ptr == MAP_FAILED)
+		return NULL;
+	else
+		return ptr;
+}
+
+/**
+ * igt_panthor_bo_create_mapped:
+ * @fd: device file descriptor
+ * @bo: pointer to panthor_bo structure to initialize
+ * @size: requested buffer size in bytes
+ * @flags: buffer object creation flags
+ * @err: expected error code, or 0 for success
+ *
+ * Create a new buffer object on the panthor device and map it into
+ * the process address space.
+ */
+void igt_panthor_bo_create_mapped(int fd, struct panthor_bo *bo, uint64_t size,
+				  uint32_t flags, int err)
+{
+	igt_panthor_bo_create(fd, bo, size, flags, err);
+	bo->offset = igt_panthor_bo_mmap_offset(fd, bo->handle, err);
+	bo->map = igt_panthor_mmap_bo(fd, bo->handle, bo->size,
+				      PROT_READ | PROT_WRITE, bo->offset);
+}
+
+/**
+ * igt_panthor_free_bo:
+ * @fd: panthor device file descriptor
+ * @bo: pointer to panthor_bo structure to free
+ *
+ * Free a buffer object and unmap it if it was mapped.
+ */
+void igt_panthor_free_bo(int fd, struct panthor_bo *bo)
+{
+	if (!bo)
+		return;
+
+	if (bo->map)
+		munmap(bo->map, bo->size);
+
+	gem_close(fd, bo->handle);
+}
diff --git a/lib/igt_panthor.h b/lib/igt_panthor.h
index 6f94b8f79..dc90033c0 100644
--- a/lib/igt_panthor.h
+++ b/lib/igt_panthor.h
@@ -7,8 +7,36 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <stdbool.h>
+#include "panthor_drm.h"
+
+struct panthor_bo {
+	int handle;
+	uint64_t offset;
+	uint64_t size;
+	void *map;
+};
 
 void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err);
+void igt_panthor_vm_create(int fd, uint32_t *vm_id, int err);
+void igt_panthor_vm_destroy(int fd, uint32_t vm_id, int err);
+void igt_panthor_vm_bind(int fd, uint32_t vm_id, uint32_t bo_handle, uint64_t va,
+			 uint64_t size, uint32_t flags, int err);
+void igt_panthor_bo_create(int fd, struct panthor_bo *bo, uint64_t size, uint32_t flags, int err);
+uint64_t igt_panthor_bo_mmap_offset(int fd, uint32_t handle, int err);
+void igt_panthor_free_bo(int fd, struct panthor_bo *bo);
+void igt_panthor_bo_create_mapped(int fd, struct panthor_bo *bo, uint64_t size,
+				  uint32_t flags, int err);
+void *igt_panthor_mmap_bo(int fd, uint32_t handle, uint64_t size,
+			  unsigned int prot, uint64_t offset);
+void igt_panthor_group_create(int fd, struct drm_panthor_group_create *group_create, int err);
+void igt_panthor_group_destroy(int fd, uint32_t group_handle, int err);
+void igt_panthor_group_submit(int fd, struct drm_panthor_group_submit *group_submit, int err);
+uint32_t igt_panthor_group_create_simple(int fd, uint32_t vm_id, int err);
+void igt_panthor_group_submit_simple(int fd, uint32_t group_handle,
+				     uint32_t queue_index, uint64_t stream_addr,
+				     uint32_t stream_size, uint32_t syncobj_handle,
+				     int err);
+uint64_t igt_panthor_get_first_core(uint64_t cores_present);
 
 enum cs_opcode {
 	CS_OPCODE_NOP = 0,
diff --git a/tests/panthor/meson.build b/tests/panthor/meson.build
index ce13aebaa..42a46e993 100644
--- a/tests/panthor/meson.build
+++ b/tests/panthor/meson.build
@@ -1,5 +1,8 @@
 panthor_progs = [
-	'panthor_query'
+	'panthor_gem',
+	'panthor_group',
+	'panthor_query',
+	'panthor_vm',
 ]
 
 foreach prog : panthor_progs
diff --git a/tests/panthor/panthor_gem.c b/tests/panthor/panthor_gem.c
new file mode 100644
index 000000000..57cd97e80
--- /dev/null
+++ b/tests/panthor/panthor_gem.c
@@ -0,0 +1,72 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
+
+#include <unistd.h>
+
+#include "igt.h"
+#include "igt_core.h"
+#include "igt_panthor.h"
+
+igt_main {
+	int fd;
+
+	igt_fixture {
+		fd = drm_open_driver(DRIVER_PANTHOR);
+	}
+
+	igt_describe("Create a buffer object");
+	igt_subtest("bo_create") {
+		struct panthor_bo bo;
+
+		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
+		igt_assert_neq(bo.handle, 0);
+
+		igt_panthor_free_bo(fd, &bo);
+	}
+
+	igt_describe("Create a fake mmap offset for a buffer object");
+	igt_subtest("bo_mmap_offset") {
+		struct panthor_bo bo;
+		uint64_t mmap_offset;
+
+		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
+		igt_assert_neq(bo.handle, 0);
+
+		mmap_offset = igt_panthor_bo_mmap_offset(fd, bo.handle, 0);
+		igt_assert_neq(mmap_offset, 0);
+
+		igt_panthor_free_bo(fd, &bo);
+	}
+
+	igt_describe("Same as bo_mmap_offset but with an invalid handle");
+	igt_subtest("bo_mmap_offset_invalid_handle") {
+		struct panthor_bo bo;
+		uint64_t mmap_offset;
+
+		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
+		igt_assert_neq(bo.handle, 0);
+
+		mmap_offset = igt_panthor_bo_mmap_offset(fd, 0xdeadbeef, ENOENT);
+		igt_assert_eq(mmap_offset, 0);
+
+		igt_panthor_free_bo(fd, &bo);
+	}
+
+	igt_describe_f("Create a buffer object whose size is not page-aligned, and check "
+		       "that the allocated size is rounded up to the next page size (%" PRIu64 ").",
+		       (uint64_t)getpagesize() * 2);
+	igt_subtest("bo_create_round_size") {
+		struct panthor_bo bo;
+		uint64_t expected_size = getpagesize() * 2;
+
+		igt_panthor_bo_create(fd, &bo, 5000, 0, 0);
+		igt_assert_neq(bo.handle, 0);
+		igt_assert_eq(bo.size, expected_size);
+
+		igt_panthor_free_bo(fd, &bo);
+	}
+
+	igt_fixture {
+		drm_close_driver(fd);
+	}
+}
diff --git a/tests/panthor/panthor_group.c b/tests/panthor/panthor_group.c
new file mode 100644
index 000000000..18c0fc8cb
--- /dev/null
+++ b/tests/panthor/panthor_group.c
@@ -0,0 +1,123 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
+
+#include <stdint.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "igt.h"
+#include "igt_panthor.h"
+#include "igt_syncobj.h"
+#include "panthor_drm.h"
+
+static size_t
+issue_store_multiple(uint8_t *cs, uint64_t kernel_va, uint32_t constant)
+{
+	const uint8_t kernel_va_reg = 68;
+	const uint8_t constant_reg = 70;
+	uint64_t instrs[] = {
+		/* MOV48: Load the source register ([r68; r69]) with the kernel address */
+		cs_mov48(kernel_va_reg, kernel_va),
+		/* MOV32: Load a known constant into r70 */
+		cs_mov32(constant_reg, constant),
+		/* STORE_MULTIPLE: Store the first register to the address pointed
+		 * to by [r68; r69]
+		 */
+		cs_stm32(kernel_va_reg, constant_reg, 0),
+		/* FLUSH all Wait for all cores */
+		cs_wait(0xff, false),
+		/* MOV32: Clear r70 to 0 */
+		cs_mov32(constant_reg, 0),
+		/* FLUSH_CACHE: Clean and invalidate all caches */
+		cs_flush(CS_FLUSH_MODE_CLEAN_AND_INVALIDATE,
+			 CS_FLUSH_MODE_CLEAN_AND_INVALIDATE,
+			 CS_FLUSH_MODE_INVALIDATE,
+			 0, constant_reg, 1),
+		cs_wait(0xff, false),
+	};
+
+	memcpy(cs, instrs, sizeof(instrs));
+	return sizeof(instrs);
+}
+
+igt_main {
+	int fd;
+
+	igt_fixture {
+		fd = drm_open_driver(DRIVER_PANTHOR);
+	}
+
+	igt_describe("Create and destroy a CSF group.");
+	igt_subtest("group_create") {
+		struct drm_panthor_vm_create vm_create = {};
+		struct drm_panthor_vm_destroy vm_destroy = {};
+		uint32_t group_handle;
+
+		vm_create.flags = 0;
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
+		igt_assert_neq(vm_create.id, 0);
+
+		group_handle = igt_panthor_group_create_simple(fd, vm_create.id, 0);
+		igt_assert_neq(group_handle, 0);
+
+		igt_panthor_group_destroy(fd, group_handle, 0);
+
+		vm_destroy = (struct drm_panthor_vm_destroy) { .id = vm_create.id };
+		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
+	}
+
+	igt_describe("Submit a job to a group and wait for completion. "
+				 "The job writes a known value to a buffer object that is then "
+				 "mmaped and checked.");
+	igt_subtest("group_submit") {
+		uint32_t vm_id;
+		uint32_t group_handle;
+		struct panthor_bo cmd_buf_bo = {};
+		struct panthor_bo result_bo = {};
+		uint64_t command_stream_gpu_addr;
+		uint32_t command_stream_size;
+		uint64_t result_gpu_addr;
+		uint32_t syncobj_handle;
+		const int INITIAL_VA = 0x1000000;
+
+		igt_panthor_vm_create(fd, &vm_id, 0);
+
+		igt_panthor_bo_create_mapped(fd, &cmd_buf_bo, 4096, 0, 0);
+		igt_panthor_vm_bind(fd, vm_id, cmd_buf_bo.handle, INITIAL_VA,
+				    cmd_buf_bo.size, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
+		command_stream_gpu_addr = INITIAL_VA;
+
+		/* Create the BO to receive the result of the store. */
+		igt_panthor_bo_create_mapped(fd, &result_bo, 4096, 0, 0);
+		/* Also bind the result BO. */
+		igt_panthor_vm_bind(fd, vm_id, result_bo.handle, INITIAL_VA + 4096,
+				    result_bo.size, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
+		result_gpu_addr = INITIAL_VA + 4096;
+
+		command_stream_size = issue_store_multiple(cmd_buf_bo.map, result_gpu_addr, 0xdeadbeef);
+
+		group_handle = igt_panthor_group_create_simple(fd, vm_id, 0);
+		igt_assert_neq(group_handle, 0);
+
+		syncobj_handle = syncobj_create(fd, 0);
+
+		igt_panthor_group_submit_simple(fd, group_handle, 0, command_stream_gpu_addr, command_stream_size, syncobj_handle, 0);
+
+		igt_assert(syncobj_wait(fd, &syncobj_handle, 1, INT64_MAX, 0, NULL));
+
+		igt_assert_eq(*(uint32_t *)result_bo.map, 0xdeadbeef);
+
+		syncobj_destroy(fd, syncobj_handle);
+
+		igt_panthor_group_destroy(fd, group_handle, 0);
+
+		igt_panthor_vm_destroy(fd, vm_id, 0);
+
+		igt_panthor_free_bo(fd, &cmd_buf_bo);
+		igt_panthor_free_bo(fd, &result_bo);
+	}
+
+	igt_fixture {
+		drm_close_driver(fd);
+	}
+}
diff --git a/tests/panthor/panthor_vm.c b/tests/panthor/panthor_vm.c
new file mode 100644
index 000000000..18a0622c7
--- /dev/null
+++ b/tests/panthor/panthor_vm.c
@@ -0,0 +1,84 @@
+// SPDX-License-Identifier: MIT
+// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
+
+#include "igt.h"
+#include "igt_core.h"
+#include "igt_panthor.h"
+#include "panthor_drm.h"
+
+igt_main {
+	int fd;
+
+	igt_fixture {
+		fd = drm_open_driver(DRIVER_PANTHOR);
+	}
+
+	igt_describe("Create and destroy a VM");
+	igt_subtest("vm_create_destroy") {
+		uint32_t vm_id;
+
+		igt_panthor_vm_create(fd, &vm_id, 0);
+		igt_assert_neq(vm_id, 0);
+
+		igt_panthor_vm_destroy(fd, vm_id, 0);
+	}
+
+	igt_subtest("vm_destroy_invalid") {
+		igt_panthor_vm_destroy(fd, 0xdeadbeef, EINVAL);
+	}
+
+	igt_describe("Test the VM_BIND API synchronously");
+	igt_subtest("vm_bind") {
+		uint32_t vm_id;
+		struct panthor_bo bo;
+		uint64_t bo_size = 0x1000;
+
+		igt_panthor_vm_create(fd, &vm_id, 0);
+		igt_assert_neq(vm_id, 0);
+
+		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
+		igt_panthor_vm_bind(fd, vm_id, bo.handle,
+				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
+
+		igt_panthor_vm_destroy(fd, vm_id, 0);
+	}
+
+	igt_describe("Test unbinding a previously bound range");
+	igt_subtest("vm_unbind") {
+		uint32_t vm_id;
+		struct panthor_bo bo;
+		uint64_t bo_size = 0x1000;
+
+		igt_panthor_vm_create(fd, &vm_id, 0);
+		igt_assert_neq(vm_id, 0);
+
+		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
+		igt_panthor_vm_bind(fd, vm_id, bo.handle,
+				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
+		igt_panthor_vm_bind(fd, vm_id, 0,
+				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP, 0);
+
+		igt_panthor_vm_destroy(fd, vm_id, 0);
+	}
+
+	igt_describe("Test unbinding an address range that was not previously bound");
+	igt_subtest("vm_unbind_invalid_address") {
+		uint32_t vm_id;
+		struct panthor_bo bo;
+		uint64_t bo_size = 0x1000;
+
+		igt_panthor_vm_create(fd, &vm_id, 0);
+		igt_assert_neq(vm_id, 0);
+
+		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
+
+		/* This was not bound previously*/
+		igt_panthor_vm_bind(fd, vm_id, bo.handle,
+				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP, EINVAL);
+		igt_panthor_vm_destroy(fd, vm_id, 0);
+	}
+
+	igt_fixture {
+		drm_close_driver(fd);
+	}
+}
-- 
2.51.0


^ permalink raw reply related	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests
  2025-11-04 20:28 ` [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests Daniel Almeida
@ 2025-11-04 21:04   ` Boris Brezillon
  2025-11-06 11:13     ` Daniel Almeida
  2025-11-06  9:26   ` Boris Brezillon
  1 sibling, 1 reply; 12+ messages in thread
From: Boris Brezillon @ 2025-11-04 21:04 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, liviu.dudau,
	steven.price, aliceryhl, jeffv, intel-gfx, igt-dev, dri-devel

On Tue,  4 Nov 2025 17:28:43 -0300
Daniel Almeida <daniel.almeida@collabora.com> wrote:

> +igt_main {
> +	int fd;
> +
> +	igt_fixture {
> +		fd = drm_open_driver(DRIVER_PANTHOR);
> +	}
> +
> +	igt_describe("Create and destroy a CSF group.");
> +	igt_subtest("group_create") {
> +		struct drm_panthor_vm_create vm_create = {};
> +		struct drm_panthor_vm_destroy vm_destroy = {};
> +		uint32_t group_handle;
> +
> +		vm_create.flags = 0;
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
> +		igt_assert_neq(vm_create.id, 0);
> +
> +		group_handle = igt_panthor_group_create_simple(fd, vm_create.id, 0);
> +		igt_assert_neq(group_handle, 0);
> +
> +		igt_panthor_group_destroy(fd, group_handle, 0);
> +
> +		vm_destroy = (struct drm_panthor_vm_destroy) { .id = vm_create.id };
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
> +	}
> +
> +	igt_describe("Submit a job to a group and wait for completion. "
> +				 "The job writes a known value to a buffer object that is then "
> +				 "mmaped and checked.");

nit: indentation is off ^

	igt_describe("Submit a job to a group and wait for completion. "
		     "The job writes a known value to a buffer object that is then "
		     "mmaped and checked.");

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 1/3] lib: add support for opening Panthor devices
  2025-11-04 20:28 ` [PATCH i-g-t v3 1/3] lib: add support for opening Panthor devices Daniel Almeida
@ 2025-11-06  9:24   ` Boris Brezillon
  0 siblings, 0 replies; 12+ messages in thread
From: Boris Brezillon @ 2025-11-06  9:24 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, liviu.dudau,
	steven.price, aliceryhl, jeffv, intel-gfx, igt-dev, dri-devel

On Tue,  4 Nov 2025 17:28:41 -0300
Daniel Almeida <daniel.almeida@collabora.com> wrote:

> We will be adding tests for Panthor in a subsequent patch, so first add
> the ability to open the device.
> 
> Reviewed-by: Kamil Konieczny <kamil.konieczny@linux.intel.com>
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>

> ---
>  lib/drmtest.c | 1 +
>  lib/drmtest.h | 1 +
>  2 files changed, 2 insertions(+)
> 
> diff --git a/lib/drmtest.c b/lib/drmtest.c
> index 590c4671a..3d8c8e79e 100644
> --- a/lib/drmtest.c
> +++ b/lib/drmtest.c
> @@ -220,6 +220,7 @@ static const struct module {
>  	{ DRIVER_INTEL, "i915", modprobe_i915 },
>  	{ DRIVER_MSM, "msm" },
>  	{ DRIVER_PANFROST, "panfrost" },
> +	{ DRIVER_PANTHOR, "panthor" },
>  	{ DRIVER_V3D, "v3d" },
>  	{ DRIVER_VC4, "vc4" },
>  	{ DRIVER_VGEM, "vgem" },
> diff --git a/lib/drmtest.h b/lib/drmtest.h
> index 8416aa491..4b0a71102 100644
> --- a/lib/drmtest.h
> +++ b/lib/drmtest.h
> @@ -57,6 +57,7 @@ int __get_drm_device_name(int fd, char *name, int name_size);
>  #define DRIVER_VMWGFX   (1 << 8)
>  #define DRIVER_VKMS	(1 << 9)
>  #define DRIVER_VIRTIO	(1 << 10)
> +#define DRIVER_PANTHOR	(1 << 11)
>  
>  /*
>   * Exclude DRIVER_VGEM and DRIVER_VIRTIO from DRIVER_ANY since if you run


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 2/3] panthor: add initial infrastructure
  2025-11-04 20:28 ` [PATCH i-g-t v3 2/3] panthor: add initial infrastructure Daniel Almeida
@ 2025-11-06  9:25   ` Boris Brezillon
  0 siblings, 0 replies; 12+ messages in thread
From: Boris Brezillon @ 2025-11-06  9:25 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, liviu.dudau,
	steven.price, aliceryhl, jeffv, intel-gfx, igt-dev, dri-devel

On Tue,  4 Nov 2025 17:28:42 -0300
Daniel Almeida <daniel.almeida@collabora.com> wrote:

> Add the necessary code needed to compile panthor tests as well as the
> basic infrastructure that will be used by the Panthor tests themselves.
> 
> To make sure everything is in order, add a basic test in
> panthor_query.c.
> 
> Acked-by: Kamil Konieczny <kamil.konieczny@linux.intel.com>
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>

> ---
>  lib/igt_panthor.c             |  41 ++++++++
>  lib/igt_panthor.h             | 193 ++++++++++++++++++++++++++++++++++
>  lib/meson.build               |   1 +
>  meson.build                   |   8 ++
>  tests/meson.build             |   2 +
>  tests/panthor/meson.build     |  12 +++
>  tests/panthor/panthor_query.c |  29 +++++
>  7 files changed, 286 insertions(+)
>  create mode 100644 lib/igt_panthor.c
>  create mode 100644 lib/igt_panthor.h
>  create mode 100644 tests/panthor/meson.build
>  create mode 100644 tests/panthor/panthor_query.c
> 
> diff --git a/lib/igt_panthor.c b/lib/igt_panthor.c
> new file mode 100644
> index 000000000..0b690f796
> --- /dev/null
> +++ b/lib/igt_panthor.c
> @@ -0,0 +1,41 @@
> +// SPDX-License-Identifier: MIT
> +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> +
> +#include "drmtest.h"
> +#include "igt_panthor.h"
> +#include "ioctl_wrappers.h"
> +#include "panthor_drm.h"
> +
> +/**
> + * SECTION:igt_panthor
> + * @short_description: Panthor support library
> + * @title: Panthor
> + * @include: igt.h
> + *
> + * This library provides various auxiliary helper functions for writing Panthor
> + * tests.
> + */
> +
> +/**
> + * igt_panthor_query:
> + * @fd: device file descriptor
> + * @type: query type (e.g., DRM_PANTHOR_DEV_QUERY_GPU_INFO)
> + * @data: pointer to a struct to store the query result
> + * @size: size of the result struct
> + * @err: expected error code, or 0 for success
> + *
> + * Query GPU information.
> + */
> +void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err)
> +{
> +	struct drm_panthor_dev_query query = {
> +		.type = type,
> +		.pointer = (uintptr_t)data,
> +		.size = size,
> +	};
> +
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_DEV_QUERY, &query, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_DEV_QUERY, &query);
> +}
> diff --git a/lib/igt_panthor.h b/lib/igt_panthor.h
> new file mode 100644
> index 000000000..6f94b8f79
> --- /dev/null
> +++ b/lib/igt_panthor.h
> @@ -0,0 +1,193 @@
> +/* SPDX-License-Identifier: MIT */
> +/* SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd. */
> +
> +#ifndef IGT_PANTHOR_H
> +#define IGT_PANTHOR_H
> +
> +#include <stddef.h>
> +#include <stdint.h>
> +#include <stdbool.h>
> +
> +void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err);
> +
> +enum cs_opcode {
> +	CS_OPCODE_NOP = 0,
> +	CS_OPCODE_MOVE48 = 1,
> +	CS_OPCODE_MOVE32 = 2,
> +	CS_OPCODE_WAIT = 3,
> +	CS_OPCODE_STM = 21,
> +	CS_OPCODE_FLUSH_CACHE = 36,
> +};
> +
> +enum cs_flush_mode {
> +	CS_FLUSH_MODE_NONE = 0,
> +	CS_FLUSH_MODE_CLEAN = 1,
> +	CS_FLUSH_MODE_INVALIDATE = 2,
> +	CS_FLUSH_MODE_CLEAN_AND_INVALIDATE = 3,
> +};
> +
> +/* There's no plan to support big endian in the UMD, so keep
> + * things simple and don't bother supporting it here either.
> + */
> +#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
> +#error "big endian not supported"
> +#endif
> +
> +struct cs_instr {
> +	union {
> +		struct {
> +			uint64_t data: 56;
> +			uint64_t opcode: 8;
> +		} any;
> +		struct {
> +			uint64_t unused: 56;
> +			uint64_t opcode: 8;
> +		} nop;
> +		struct {
> +			uint64_t immediate: 48;
> +			uint64_t dest: 8;
> +			uint64_t opcode: 8;
> +		} move48;
> +		struct {
> +			uint64_t immediate: 32;
> +			uint64_t unused: 16;
> +			uint64_t dest: 8;
> +			uint64_t opcode: 8;
> +		} move32;
> +		struct {
> +			uint64_t unused0: 16;
> +			uint64_t wait_mask: 16;
> +			uint64_t progress_inc: 1;
> +			uint64_t unused1: 23;
> +			uint64_t opcode: 8;
> +		} wait;
> +		struct {
> +			uint64_t offset: 16;
> +			uint64_t mask: 16;
> +			uint64_t unused: 8;
> +			uint64_t address: 8;
> +			uint64_t src: 8;
> +			uint64_t opcode: 8;
> +		} stm;
> +		struct {
> +			uint64_t l2_mode: 4;
> +			uint64_t lsc_mode: 4;
> +			uint64_t other_mode: 4;
> +			uint64_t unused0: 4;
> +			uint64_t wait_mask: 16;
> +			uint64_t unused1: 8;
> +			uint64_t flush_id: 8;
> +			uint64_t signal_slot: 4;
> +			uint64_t unused2: 4;
> +			uint64_t opcode: 8;
> +		} flush;
> +		uint64_t raw;
> +	};
> +};
> +
> +static inline uint64_t
> +cs_nop(void)
> +{
> +	struct cs_instr instr = {
> +		.nop = {
> +			.opcode = CS_OPCODE_NOP,
> +		},
> +	};
> +
> +	return instr.raw;
> +}
> +
> +static inline uint64_t
> +cs_mov48(uint8_t dst, uint64_t imm)
> +{
> +	struct cs_instr instr = {
> +		.move48 = {
> +			.opcode = CS_OPCODE_MOVE48,
> +			.dest = dst,
> +			.immediate = (uint64_t)imm & 0xffffffffffffull,
> +		},
> +	};
> +
> +	return instr.raw;
> +}
> +
> +static inline uint64_t
> +cs_mov32(uint8_t dst, uint32_t imm)
> +{
> +	struct cs_instr instr = {
> +		.move32 = {
> +			.opcode = CS_OPCODE_MOVE32,
> +			.dest = dst,
> +			.immediate = (uint32_t)imm,
> +		},
> +	};
> +
> +	return instr.raw;
> +}
> +
> +static inline uint64_t
> +cs_wait(uint16_t wait_mask, bool progress_inc)
> +{
> +	struct cs_instr instr = {
> +		.wait = {
> +			.opcode = CS_OPCODE_WAIT,
> +			.wait_mask = wait_mask,
> +			.progress_inc = progress_inc,
> +		},
> +	};
> +
> +	return instr.raw;
> +}
> +
> +static inline uint64_t
> +cs_stm(uint8_t address, uint8_t src, uint16_t mask, int16_t offset)
> +{
> +	struct cs_instr instr = {
> +		.stm = {
> +			.opcode = CS_OPCODE_STM,
> +			.offset = (uint16_t)offset,
> +			.mask = mask,
> +			.src = src,
> +			.address = address,
> +		},
> +	};
> +
> +	return instr.raw;
> +}
> +
> +static inline uint64_t
> +cs_stm32(uint8_t address, uint8_t src, int16_t offset)
> +{
> +	return cs_stm(address, src, 0x1, offset);
> +}
> +
> +static inline uint64_t
> +cs_stm64(uint8_t address, uint8_t src, int16_t offset)
> +{
> +	return cs_stm(address, src, 0x3, offset);
> +}
> +
> +static inline uint64_t
> +cs_flush(enum cs_flush_mode l2_mode,
> +	 enum cs_flush_mode lsc_mode,
> +	 enum cs_flush_mode other_mode,
> +	 uint16_t wait_mask,
> +	 uint8_t flush_id,
> +	 uint8_t signal_slot)
> +{
> +	struct cs_instr instr = {
> +		.flush = {
> +			.l2_mode = l2_mode,
> +			.lsc_mode = lsc_mode,
> +			.other_mode = other_mode,
> +			.wait_mask = wait_mask,
> +			.flush_id = flush_id,
> +			.signal_slot = signal_slot,
> +			.opcode = CS_OPCODE_FLUSH_CACHE,
> +		},
> +	};
> +
> +	return instr.raw;
> +}
> +
> +#endif /* IGT_PANTHOR_H */
> diff --git a/lib/meson.build b/lib/meson.build
> index 725a46e98..707ce6ff9 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -106,6 +106,7 @@ lib_sources = [
>  	'igt_kmod.c',
>  	'igt_ktap.c',
>  	'igt_panfrost.c',
> +	'igt_panthor.c',
>  	'igt_v3d.c',
>  	'igt_vc4.c',
>  	'igt_vmwgfx.c',
> diff --git a/meson.build b/meson.build
> index db6e09a94..bfcffbb9a 100644
> --- a/meson.build
> +++ b/meson.build
> @@ -288,6 +288,7 @@ libexecdir = join_paths(get_option('libexecdir'), 'igt-gpu-tools')
>  amdgpudir = join_paths(libexecdir, 'amdgpu')
>  msmdir = join_paths(libexecdir, 'msm')
>  panfrostdir = join_paths(libexecdir, 'panfrost')
> +panthordir = join_paths(libexecdir, 'panthor')
>  v3ddir = join_paths(libexecdir, 'v3d')
>  vc4dir = join_paths(libexecdir, 'vc4')
>  vkmsdir = join_paths(libexecdir, 'vkms')
> @@ -341,6 +342,12 @@ if get_option('use_rpath')
>  	endforeach
>  	panfrost_rpathdir = join_paths(panfrost_rpathdir, libdir)
>  
> +	panthor_rpathdir = '$ORIGIN'
> +	foreach p : panthordir.split('/')
> +		panthor_rpathdir = join_paths(panthor_rpathdir, '..')
> +	endforeach
> +	panthor_rpathdir = join_paths(panthor_rpathdir, libdir)
> +
>  	v3d_rpathdir = '$ORIGIN'
>  	foreach p : v3ddir.split('/')
>  		v3d_rpathdir = join_paths(v3d_rpathdir, '..')
> @@ -370,6 +377,7 @@ else
>  	amdgpudir_rpathdir = ''
>  	msm_rpathdir = ''
>  	panfrost_rpathdir = ''
> +	panthor_rpathdir = ''
>  	v3d_rpathdir = ''
>  	vc4_rpathdir = ''
>  	vkms_rpathdir = ''
> diff --git a/tests/meson.build b/tests/meson.build
> index f2267bc09..e1fdac2cf 100644
> --- a/tests/meson.build
> +++ b/tests/meson.build
> @@ -490,6 +490,8 @@ subdir('msm')
>  
>  subdir('panfrost')
>  
> +subdir('panthor')
> +
>  subdir('v3d')
>  
>  subdir('vc4')
> diff --git a/tests/panthor/meson.build b/tests/panthor/meson.build
> new file mode 100644
> index 000000000..ce13aebaa
> --- /dev/null
> +++ b/tests/panthor/meson.build
> @@ -0,0 +1,12 @@
> +panthor_progs = [
> +	'panthor_query'
> +]
> +
> +foreach prog : panthor_progs
> +	test_executables += executable(prog, prog + '.c',
> +				       dependencies : test_deps,
> +				       install_dir : panthordir,
> +				       install_rpath : panthor_rpathdir,
> +				       install : true)
> +	test_list += join_paths('panthor', prog)
> +endforeach
> diff --git a/tests/panthor/panthor_query.c b/tests/panthor/panthor_query.c
> new file mode 100644
> index 000000000..81c2ebd23
> --- /dev/null
> +++ b/tests/panthor/panthor_query.c
> @@ -0,0 +1,29 @@
> +// SPDX-License-Identifier: MIT
> +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> +
> +#include "igt.h"
> +#include "igt_core.h"
> +#include "igt_panthor.h"
> +#include "panthor_drm.h"
> +#include <stdint.h>
> +
> +igt_main {
> +	int fd;
> +
> +	igt_fixture {
> +		fd = drm_open_driver(DRIVER_PANTHOR);
> +	}
> +
> +	igt_describe("Query GPU information from ROM.");
> +	igt_subtest("query") {
> +		struct drm_panthor_gpu_info gpu = {};
> +
> +		igt_panthor_query(fd, DRM_PANTHOR_DEV_QUERY_GPU_INFO, &gpu, sizeof(gpu), 0);
> +
> +		igt_assert_neq(gpu.gpu_id, 0);
> +	}
> +
> +	igt_fixture {
> +		drm_close_driver(fd);
> +	}
> +}


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests
  2025-11-04 20:28 ` [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests Daniel Almeida
  2025-11-04 21:04   ` Boris Brezillon
@ 2025-11-06  9:26   ` Boris Brezillon
  2025-11-06 17:10     ` Kamil Konieczny
  1 sibling, 1 reply; 12+ messages in thread
From: Boris Brezillon @ 2025-11-06  9:26 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, liviu.dudau,
	steven.price, aliceryhl, jeffv, intel-gfx, igt-dev, dri-devel

On Tue,  4 Nov 2025 17:28:43 -0300
Daniel Almeida <daniel.almeida@collabora.com> wrote:

> Add an initial test suit covering query device properties, allocating
> memory, binding and unbinding VA ranges through VM_BIND and submitting a
> simple piece of work through GROUP_SUBMIT.
> 
> Acked-by: Kamil Konieczny <kamil.konieczny@linux.intel.com>
> Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>

With the indentation fixed, this is

Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>

> ---
>  lib/igt_panthor.c             | 331 ++++++++++++++++++++++++++++++++++
>  lib/igt_panthor.h             |  28 +++
>  tests/panthor/meson.build     |   5 +-
>  tests/panthor/panthor_gem.c   |  72 ++++++++
>  tests/panthor/panthor_group.c | 123 +++++++++++++
>  tests/panthor/panthor_vm.c    |  84 +++++++++
>  6 files changed, 642 insertions(+), 1 deletion(-)
>  create mode 100644 tests/panthor/panthor_gem.c
>  create mode 100644 tests/panthor/panthor_group.c
>  create mode 100644 tests/panthor/panthor_vm.c
> 
> diff --git a/lib/igt_panthor.c b/lib/igt_panthor.c
> index 0b690f796..73ada9c59 100644
> --- a/lib/igt_panthor.c
> +++ b/lib/igt_panthor.c
> @@ -6,6 +6,148 @@
>  #include "ioctl_wrappers.h"
>  #include "panthor_drm.h"
>  
> +/**
> + * igt_panthor_group_create:
> + * @fd: device file descriptor
> + * @group_create: pointer to group creation structure
> + * @err: expected error code, or 0 for success
> + *
> + * Create a group.
> + */
> +void igt_panthor_group_create(int fd, struct drm_panthor_group_create *group_create, int err)
> +{
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_CREATE, group_create, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_CREATE, group_create);
> +}
> +
> +/**
> + * igt_panthor_group_destroy:
> + * @fd: device file descriptor
> + * @group_handle: group handle to destroy
> + * @err: expected error code, or 0 for success
> + *
> + * Destroy a group.
> + */
> +void igt_panthor_group_destroy(int fd, uint32_t group_handle, int err)
> +{
> +	struct drm_panthor_group_destroy group_destroy = {
> +		.group_handle = group_handle,
> +	};
> +
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_DESTROY, &group_destroy, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_DESTROY, &group_destroy);
> +}
> +
> +/**
> + * igt_panthor_group_submit:
> + * @fd: device file descriptor
> + * @group_submit: pointer to group submission structure
> + * @err: expected error code, or 0 for success
> + *
> + * Submit work to a group.
> + */
> +void igt_panthor_group_submit(int fd, struct drm_panthor_group_submit *group_submit, int err)
> +{
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_SUBMIT, group_submit, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_SUBMIT, group_submit);
> +}
> +
> +/**
> + * igt_panthor_get_first_core:
> + * @cores_present: bitmask of available cores
> + *
> + * Get a mask with only the first available core bit set.
> + *
> + * Returns: core mask with first available core, or 0 if no cores available
> + */
> +uint64_t igt_panthor_get_first_core(uint64_t cores_present)
> +{
> +	if (cores_present == 0)
> +		return 0;
> +
> +	return 1ULL << (ffs(cores_present) - 1);
> +}
> +
> +/**
> + * igt_panthor_group_create_simple:
> + * @fd: device file descriptor
> + * @vm_id: VM ID to associate with the group
> + * @err: expected error code, or 0 for success
> + *
> + * Create a group with a single queue and reasonable defaults.
> + *
> + * Returns: group handle on success
> + */
> +uint32_t igt_panthor_group_create_simple(int fd, uint32_t vm_id, int err)
> +{
> +	struct drm_panthor_gpu_info gpu_info = {};
> +	struct drm_panthor_group_create group_create = {};
> +	struct drm_panthor_queue_create queue = {};
> +	struct drm_panthor_obj_array queues = {};
> +
> +	igt_panthor_query(fd, DRM_PANTHOR_DEV_QUERY_GPU_INFO, &gpu_info, sizeof(gpu_info), 0);
> +
> +	queue.priority = 0;
> +	queue.ringbuf_size = 4096;
> +	queues = (struct drm_panthor_obj_array)DRM_PANTHOR_OBJ_ARRAY(1, &queue);
> +
> +	group_create.queues = queues;
> +	group_create.max_compute_cores = 1;
> +	group_create.max_fragment_cores = 1;
> +	group_create.max_tiler_cores = 1;
> +	group_create.priority = PANTHOR_GROUP_PRIORITY_MEDIUM;
> +	group_create.compute_core_mask = igt_panthor_get_first_core(gpu_info.shader_present);
> +	group_create.fragment_core_mask = igt_panthor_get_first_core(gpu_info.shader_present);
> +	group_create.tiler_core_mask = igt_panthor_get_first_core(gpu_info.tiler_present);
> +	group_create.vm_id = vm_id;
> +
> +	igt_panthor_group_create(fd, &group_create, err);
> +	return group_create.group_handle;
> +}
> +
> +/**
> + * igt_panthor_group_submit_simple:
> + * @fd: device file descriptor
> + * @group_handle: group handle to submit to
> + * @queue_index: queue index within the group
> + * @stream_addr: GPU address of the command stream
> + * @stream_size: size of the command stream
> + * @syncobj_handle: sync object handle for completion signaling
> + * @err: expected error code, or 0 for success
> + *
> + * Submit work to a group queue with a simple interface.
> + */
> +void igt_panthor_group_submit_simple(int fd, uint32_t group_handle,
> +				     uint32_t queue_index, uint64_t stream_addr,
> +				     uint32_t stream_size, uint32_t syncobj_handle,
> +				     int err)
> +{
> +	struct drm_panthor_group_submit group_submit = {};
> +	struct drm_panthor_queue_submit queue_submit = {};
> +	struct drm_panthor_sync_op sync_op = {};
> +
> +	sync_op.handle = syncobj_handle;
> +	sync_op.flags = DRM_PANTHOR_SYNC_OP_SIGNAL;
> +
> +	queue_submit.syncs = (struct drm_panthor_obj_array)DRM_PANTHOR_OBJ_ARRAY(1, &sync_op);
> +	queue_submit.queue_index = queue_index;
> +	queue_submit.stream_size = stream_size;
> +	queue_submit.stream_addr = stream_addr;
> +	queue_submit.latest_flush = 0;
> +
> +	group_submit.group_handle = group_handle;
> +	group_submit.queue_submits = (struct drm_panthor_obj_array)
> +		DRM_PANTHOR_OBJ_ARRAY(1, &queue_submit);
> +
> +	igt_panthor_group_submit(fd, &group_submit, err);
> +}
> +
>  /**
>   * SECTION:igt_panthor
>   * @short_description: Panthor support library
> @@ -39,3 +181,192 @@ void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err)
>  	else
>  		do_ioctl(fd, DRM_IOCTL_PANTHOR_DEV_QUERY, &query);
>  }
> +
> +/**
> + * igt_panthor_vm_create:
> + * @fd: device file descriptor
> + * @vm_id: pointer to store the created VM ID
> + * @err: expected error code, or 0 for success
> + *
> + * Creates a VM.
> + */
> +void igt_panthor_vm_create(int fd, uint32_t *vm_id, int err)
> +{
> +	struct drm_panthor_vm_create vm_create = {};
> +
> +	if (err) {
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create, err);
> +	} else {
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
> +		*vm_id = vm_create.id;
> +	}
> +}
> +
> +/**
> + * igt_panthor_vm_destroy:
> + * @fd: device file descriptor
> + * @vm_id: VM ID to destroy
> + * @err: expected error code, or 0 for success
> + *
> + * Destroys a VM.
> + */
> +void igt_panthor_vm_destroy(int fd, uint32_t vm_id, int err)
> +{
> +	struct drm_panthor_vm_destroy vm_destroy = {
> +		.id = vm_id,
> +	};
> +
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
> +}
> +
> +/**
> + * igt_panthor_vm_bind:
> + * @fd: device file descriptor
> + * @vm_id: VM ID to bind the buffer to
> + * @bo_handle: buffer object handle to bind
> + * @va: virtual address to bind at
> + * @size: size of the binding
> + * @flags: binding flags
> + * @err: expected error code, or 0 for success
> + *
> + * Bind a buffer object to a virtual address in the specified VM.
> + */
> +void igt_panthor_vm_bind(int fd, uint32_t vm_id, uint32_t bo_handle,
> +			 uint64_t va, uint64_t size, uint32_t flags, int err)
> +{
> +	struct drm_panthor_vm_bind_op bind_op = {
> +		.flags = flags,
> +		.bo_handle = bo_handle,
> +		.va = va,
> +		.size = size,
> +	};
> +
> +	struct drm_panthor_vm_bind vm_bind = {
> +		.vm_id = vm_id,
> +		.flags = 0,
> +		.ops = DRM_PANTHOR_OBJ_ARRAY(1, &bind_op),
> +	};
> +
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_BIND, &vm_bind, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_BIND, &vm_bind);
> +}
> +
> +/**
> + * igt_panthor_bo_create:
> + * @fd: device file descriptor
> + * @bo: pointer to panthor_bo structure to initialize
> + * @size: requested buffer size in bytes
> + * @flags: buffer object creation flags
> + * @err: expected error code, or 0 for success
> + *
> + * Creates a new buffer object
> + */
> +void igt_panthor_bo_create(int fd, struct panthor_bo *bo,
> +			   uint64_t size, uint32_t flags, int err)
> +{
> +	struct drm_panthor_bo_create bo_create = {
> +		.size = size,
> +		.flags = flags,
> +	};
> +
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_BO_CREATE, &bo_create, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_BO_CREATE, &bo_create);
> +
> +	bo->handle = bo_create.handle;
> +	bo->size = bo_create.size;
> +	bo->offset = 0;
> +	bo->map = NULL;
> +}
> +
> +/**
> + * igt_panthor_bo_mmap_offset:
> + * @fd: device file descriptor
> + * @handle: buffer object handle
> + * @err: expected error code, or 0 for success
> + *
> + * Get the mmap offset for a buffer object.
> + *
> + * Returns: the mmap offset for the buffer object
> + */
> +uint64_t igt_panthor_bo_mmap_offset(int fd, uint32_t handle, int err)
> +{
> +	struct drm_panthor_bo_mmap_offset bo_mmap_offset = {
> +		.handle = handle,
> +	};
> +
> +	if (err)
> +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_BO_MMAP_OFFSET, &bo_mmap_offset, err);
> +	else
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_BO_MMAP_OFFSET, &bo_mmap_offset);
> +
> +	return bo_mmap_offset.offset;
> +}
> +
> +/**
> + * igt_panthor_mmap_bo:
> + * @fd: device file descriptor
> + * @handle: buffer object handle
> + * @size: size of the buffer to map
> + * @prot: memory protection flags (e.g., PROT_READ | PROT_WRITE)
> + * @offset: mmap offset for the buffer object
> + *
> + * Map a buffer object into the process address space.
> + *
> + * Returns: pointer to the mapped memory, or NULL on failure
> + */
> +void *igt_panthor_mmap_bo(int fd, uint32_t handle, uint64_t size,
> +			  unsigned int prot, uint64_t offset)
> +{
> +	void *ptr;
> +
> +	ptr = mmap(0, size, prot, MAP_SHARED, fd, offset);
> +	if (ptr == MAP_FAILED)
> +		return NULL;
> +	else
> +		return ptr;
> +}
> +
> +/**
> + * igt_panthor_bo_create_mapped:
> + * @fd: device file descriptor
> + * @bo: pointer to panthor_bo structure to initialize
> + * @size: requested buffer size in bytes
> + * @flags: buffer object creation flags
> + * @err: expected error code, or 0 for success
> + *
> + * Create a new buffer object on the panthor device and map it into
> + * the process address space.
> + */
> +void igt_panthor_bo_create_mapped(int fd, struct panthor_bo *bo, uint64_t size,
> +				  uint32_t flags, int err)
> +{
> +	igt_panthor_bo_create(fd, bo, size, flags, err);
> +	bo->offset = igt_panthor_bo_mmap_offset(fd, bo->handle, err);
> +	bo->map = igt_panthor_mmap_bo(fd, bo->handle, bo->size,
> +				      PROT_READ | PROT_WRITE, bo->offset);
> +}
> +
> +/**
> + * igt_panthor_free_bo:
> + * @fd: panthor device file descriptor
> + * @bo: pointer to panthor_bo structure to free
> + *
> + * Free a buffer object and unmap it if it was mapped.
> + */
> +void igt_panthor_free_bo(int fd, struct panthor_bo *bo)
> +{
> +	if (!bo)
> +		return;
> +
> +	if (bo->map)
> +		munmap(bo->map, bo->size);
> +
> +	gem_close(fd, bo->handle);
> +}
> diff --git a/lib/igt_panthor.h b/lib/igt_panthor.h
> index 6f94b8f79..dc90033c0 100644
> --- a/lib/igt_panthor.h
> +++ b/lib/igt_panthor.h
> @@ -7,8 +7,36 @@
>  #include <stddef.h>
>  #include <stdint.h>
>  #include <stdbool.h>
> +#include "panthor_drm.h"
> +
> +struct panthor_bo {
> +	int handle;
> +	uint64_t offset;
> +	uint64_t size;
> +	void *map;
> +};
>  
>  void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err);
> +void igt_panthor_vm_create(int fd, uint32_t *vm_id, int err);
> +void igt_panthor_vm_destroy(int fd, uint32_t vm_id, int err);
> +void igt_panthor_vm_bind(int fd, uint32_t vm_id, uint32_t bo_handle, uint64_t va,
> +			 uint64_t size, uint32_t flags, int err);
> +void igt_panthor_bo_create(int fd, struct panthor_bo *bo, uint64_t size, uint32_t flags, int err);
> +uint64_t igt_panthor_bo_mmap_offset(int fd, uint32_t handle, int err);
> +void igt_panthor_free_bo(int fd, struct panthor_bo *bo);
> +void igt_panthor_bo_create_mapped(int fd, struct panthor_bo *bo, uint64_t size,
> +				  uint32_t flags, int err);
> +void *igt_panthor_mmap_bo(int fd, uint32_t handle, uint64_t size,
> +			  unsigned int prot, uint64_t offset);
> +void igt_panthor_group_create(int fd, struct drm_panthor_group_create *group_create, int err);
> +void igt_panthor_group_destroy(int fd, uint32_t group_handle, int err);
> +void igt_panthor_group_submit(int fd, struct drm_panthor_group_submit *group_submit, int err);
> +uint32_t igt_panthor_group_create_simple(int fd, uint32_t vm_id, int err);
> +void igt_panthor_group_submit_simple(int fd, uint32_t group_handle,
> +				     uint32_t queue_index, uint64_t stream_addr,
> +				     uint32_t stream_size, uint32_t syncobj_handle,
> +				     int err);
> +uint64_t igt_panthor_get_first_core(uint64_t cores_present);
>  
>  enum cs_opcode {
>  	CS_OPCODE_NOP = 0,
> diff --git a/tests/panthor/meson.build b/tests/panthor/meson.build
> index ce13aebaa..42a46e993 100644
> --- a/tests/panthor/meson.build
> +++ b/tests/panthor/meson.build
> @@ -1,5 +1,8 @@
>  panthor_progs = [
> -	'panthor_query'
> +	'panthor_gem',
> +	'panthor_group',
> +	'panthor_query',
> +	'panthor_vm',
>  ]
>  
>  foreach prog : panthor_progs
> diff --git a/tests/panthor/panthor_gem.c b/tests/panthor/panthor_gem.c
> new file mode 100644
> index 000000000..57cd97e80
> --- /dev/null
> +++ b/tests/panthor/panthor_gem.c
> @@ -0,0 +1,72 @@
> +// SPDX-License-Identifier: MIT
> +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> +
> +#include <unistd.h>
> +
> +#include "igt.h"
> +#include "igt_core.h"
> +#include "igt_panthor.h"
> +
> +igt_main {
> +	int fd;
> +
> +	igt_fixture {
> +		fd = drm_open_driver(DRIVER_PANTHOR);
> +	}
> +
> +	igt_describe("Create a buffer object");
> +	igt_subtest("bo_create") {
> +		struct panthor_bo bo;
> +
> +		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
> +		igt_assert_neq(bo.handle, 0);
> +
> +		igt_panthor_free_bo(fd, &bo);
> +	}
> +
> +	igt_describe("Create a fake mmap offset for a buffer object");
> +	igt_subtest("bo_mmap_offset") {
> +		struct panthor_bo bo;
> +		uint64_t mmap_offset;
> +
> +		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
> +		igt_assert_neq(bo.handle, 0);
> +
> +		mmap_offset = igt_panthor_bo_mmap_offset(fd, bo.handle, 0);
> +		igt_assert_neq(mmap_offset, 0);
> +
> +		igt_panthor_free_bo(fd, &bo);
> +	}
> +
> +	igt_describe("Same as bo_mmap_offset but with an invalid handle");
> +	igt_subtest("bo_mmap_offset_invalid_handle") {
> +		struct panthor_bo bo;
> +		uint64_t mmap_offset;
> +
> +		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
> +		igt_assert_neq(bo.handle, 0);
> +
> +		mmap_offset = igt_panthor_bo_mmap_offset(fd, 0xdeadbeef, ENOENT);
> +		igt_assert_eq(mmap_offset, 0);
> +
> +		igt_panthor_free_bo(fd, &bo);
> +	}
> +
> +	igt_describe_f("Create a buffer object whose size is not page-aligned, and check "
> +		       "that the allocated size is rounded up to the next page size (%" PRIu64 ").",
> +		       (uint64_t)getpagesize() * 2);
> +	igt_subtest("bo_create_round_size") {
> +		struct panthor_bo bo;
> +		uint64_t expected_size = getpagesize() * 2;
> +
> +		igt_panthor_bo_create(fd, &bo, 5000, 0, 0);
> +		igt_assert_neq(bo.handle, 0);
> +		igt_assert_eq(bo.size, expected_size);
> +
> +		igt_panthor_free_bo(fd, &bo);
> +	}
> +
> +	igt_fixture {
> +		drm_close_driver(fd);
> +	}
> +}
> diff --git a/tests/panthor/panthor_group.c b/tests/panthor/panthor_group.c
> new file mode 100644
> index 000000000..18c0fc8cb
> --- /dev/null
> +++ b/tests/panthor/panthor_group.c
> @@ -0,0 +1,123 @@
> +// SPDX-License-Identifier: MIT
> +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> +
> +#include <stdint.h>
> +#include <sys/mman.h>
> +#include <unistd.h>
> +
> +#include "igt.h"
> +#include "igt_panthor.h"
> +#include "igt_syncobj.h"
> +#include "panthor_drm.h"
> +
> +static size_t
> +issue_store_multiple(uint8_t *cs, uint64_t kernel_va, uint32_t constant)
> +{
> +	const uint8_t kernel_va_reg = 68;
> +	const uint8_t constant_reg = 70;
> +	uint64_t instrs[] = {
> +		/* MOV48: Load the source register ([r68; r69]) with the kernel address */
> +		cs_mov48(kernel_va_reg, kernel_va),
> +		/* MOV32: Load a known constant into r70 */
> +		cs_mov32(constant_reg, constant),
> +		/* STORE_MULTIPLE: Store the first register to the address pointed
> +		 * to by [r68; r69]
> +		 */
> +		cs_stm32(kernel_va_reg, constant_reg, 0),
> +		/* FLUSH all Wait for all cores */
> +		cs_wait(0xff, false),
> +		/* MOV32: Clear r70 to 0 */
> +		cs_mov32(constant_reg, 0),
> +		/* FLUSH_CACHE: Clean and invalidate all caches */
> +		cs_flush(CS_FLUSH_MODE_CLEAN_AND_INVALIDATE,
> +			 CS_FLUSH_MODE_CLEAN_AND_INVALIDATE,
> +			 CS_FLUSH_MODE_INVALIDATE,
> +			 0, constant_reg, 1),
> +		cs_wait(0xff, false),
> +	};
> +
> +	memcpy(cs, instrs, sizeof(instrs));
> +	return sizeof(instrs);
> +}
> +
> +igt_main {
> +	int fd;
> +
> +	igt_fixture {
> +		fd = drm_open_driver(DRIVER_PANTHOR);
> +	}
> +
> +	igt_describe("Create and destroy a CSF group.");
> +	igt_subtest("group_create") {
> +		struct drm_panthor_vm_create vm_create = {};
> +		struct drm_panthor_vm_destroy vm_destroy = {};
> +		uint32_t group_handle;
> +
> +		vm_create.flags = 0;
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
> +		igt_assert_neq(vm_create.id, 0);
> +
> +		group_handle = igt_panthor_group_create_simple(fd, vm_create.id, 0);
> +		igt_assert_neq(group_handle, 0);
> +
> +		igt_panthor_group_destroy(fd, group_handle, 0);
> +
> +		vm_destroy = (struct drm_panthor_vm_destroy) { .id = vm_create.id };
> +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
> +	}
> +
> +	igt_describe("Submit a job to a group and wait for completion. "
> +				 "The job writes a known value to a buffer object that is then "
> +				 "mmaped and checked.");
> +	igt_subtest("group_submit") {
> +		uint32_t vm_id;
> +		uint32_t group_handle;
> +		struct panthor_bo cmd_buf_bo = {};
> +		struct panthor_bo result_bo = {};
> +		uint64_t command_stream_gpu_addr;
> +		uint32_t command_stream_size;
> +		uint64_t result_gpu_addr;
> +		uint32_t syncobj_handle;
> +		const int INITIAL_VA = 0x1000000;
> +
> +		igt_panthor_vm_create(fd, &vm_id, 0);
> +
> +		igt_panthor_bo_create_mapped(fd, &cmd_buf_bo, 4096, 0, 0);
> +		igt_panthor_vm_bind(fd, vm_id, cmd_buf_bo.handle, INITIAL_VA,
> +				    cmd_buf_bo.size, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> +		command_stream_gpu_addr = INITIAL_VA;
> +
> +		/* Create the BO to receive the result of the store. */
> +		igt_panthor_bo_create_mapped(fd, &result_bo, 4096, 0, 0);
> +		/* Also bind the result BO. */
> +		igt_panthor_vm_bind(fd, vm_id, result_bo.handle, INITIAL_VA + 4096,
> +				    result_bo.size, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> +		result_gpu_addr = INITIAL_VA + 4096;
> +
> +		command_stream_size = issue_store_multiple(cmd_buf_bo.map, result_gpu_addr, 0xdeadbeef);
> +
> +		group_handle = igt_panthor_group_create_simple(fd, vm_id, 0);
> +		igt_assert_neq(group_handle, 0);
> +
> +		syncobj_handle = syncobj_create(fd, 0);
> +
> +		igt_panthor_group_submit_simple(fd, group_handle, 0, command_stream_gpu_addr, command_stream_size, syncobj_handle, 0);
> +
> +		igt_assert(syncobj_wait(fd, &syncobj_handle, 1, INT64_MAX, 0, NULL));
> +
> +		igt_assert_eq(*(uint32_t *)result_bo.map, 0xdeadbeef);
> +
> +		syncobj_destroy(fd, syncobj_handle);
> +
> +		igt_panthor_group_destroy(fd, group_handle, 0);
> +
> +		igt_panthor_vm_destroy(fd, vm_id, 0);
> +
> +		igt_panthor_free_bo(fd, &cmd_buf_bo);
> +		igt_panthor_free_bo(fd, &result_bo);
> +	}
> +
> +	igt_fixture {
> +		drm_close_driver(fd);
> +	}
> +}
> diff --git a/tests/panthor/panthor_vm.c b/tests/panthor/panthor_vm.c
> new file mode 100644
> index 000000000..18a0622c7
> --- /dev/null
> +++ b/tests/panthor/panthor_vm.c
> @@ -0,0 +1,84 @@
> +// SPDX-License-Identifier: MIT
> +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> +
> +#include "igt.h"
> +#include "igt_core.h"
> +#include "igt_panthor.h"
> +#include "panthor_drm.h"
> +
> +igt_main {
> +	int fd;
> +
> +	igt_fixture {
> +		fd = drm_open_driver(DRIVER_PANTHOR);
> +	}
> +
> +	igt_describe("Create and destroy a VM");
> +	igt_subtest("vm_create_destroy") {
> +		uint32_t vm_id;
> +
> +		igt_panthor_vm_create(fd, &vm_id, 0);
> +		igt_assert_neq(vm_id, 0);
> +
> +		igt_panthor_vm_destroy(fd, vm_id, 0);
> +	}
> +
> +	igt_subtest("vm_destroy_invalid") {
> +		igt_panthor_vm_destroy(fd, 0xdeadbeef, EINVAL);
> +	}
> +
> +	igt_describe("Test the VM_BIND API synchronously");
> +	igt_subtest("vm_bind") {
> +		uint32_t vm_id;
> +		struct panthor_bo bo;
> +		uint64_t bo_size = 0x1000;
> +
> +		igt_panthor_vm_create(fd, &vm_id, 0);
> +		igt_assert_neq(vm_id, 0);
> +
> +		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
> +		igt_panthor_vm_bind(fd, vm_id, bo.handle,
> +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> +
> +		igt_panthor_vm_destroy(fd, vm_id, 0);
> +	}
> +
> +	igt_describe("Test unbinding a previously bound range");
> +	igt_subtest("vm_unbind") {
> +		uint32_t vm_id;
> +		struct panthor_bo bo;
> +		uint64_t bo_size = 0x1000;
> +
> +		igt_panthor_vm_create(fd, &vm_id, 0);
> +		igt_assert_neq(vm_id, 0);
> +
> +		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
> +		igt_panthor_vm_bind(fd, vm_id, bo.handle,
> +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> +		igt_panthor_vm_bind(fd, vm_id, 0,
> +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP, 0);
> +
> +		igt_panthor_vm_destroy(fd, vm_id, 0);
> +	}
> +
> +	igt_describe("Test unbinding an address range that was not previously bound");
> +	igt_subtest("vm_unbind_invalid_address") {
> +		uint32_t vm_id;
> +		struct panthor_bo bo;
> +		uint64_t bo_size = 0x1000;
> +
> +		igt_panthor_vm_create(fd, &vm_id, 0);
> +		igt_assert_neq(vm_id, 0);
> +
> +		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
> +
> +		/* This was not bound previously*/
> +		igt_panthor_vm_bind(fd, vm_id, bo.handle,
> +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP, EINVAL);
> +		igt_panthor_vm_destroy(fd, vm_id, 0);
> +	}
> +
> +	igt_fixture {
> +		drm_close_driver(fd);
> +	}
> +}


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 0/3] Add initial Panthor tests
  2025-11-04 20:28 [PATCH i-g-t v3 0/3] Add initial Panthor tests Daniel Almeida
                   ` (2 preceding siblings ...)
  2025-11-04 20:28 ` [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests Daniel Almeida
@ 2025-11-06  9:29 ` Boris Brezillon
  3 siblings, 0 replies; 12+ messages in thread
From: Boris Brezillon @ 2025-11-06  9:29 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, liviu.dudau,
	steven.price, aliceryhl, jeffv, intel-gfx, igt-dev, dri-devel

On Tue,  4 Nov 2025 17:28:40 -0300
Daniel Almeida <daniel.almeida@collabora.com> wrote:

> This series adds basic Panthor tests. In particular, these are being
> used to test both Panthor[0] and Tyr[1], i.e.: the new Rust GPU driver
> that implements Panthor's uAPI (i.e.: panthor_drm.h). Most of the
> initial tests were chosen in order to have something to test Tyr with,
> but this series lays the groundwork so that more interesting tests can
> be added to test more of Panthor itself.
> 
> This work is being tested on a RockPi 5, featuring an rk3588 SoC and
> Mali-G610 Valhall.
> 
> Note that there's a few (less than five?) remaining checkpatch.pl
> comments about long lines. IMHO there's no way to format them better so
> I hope we can live with this.
> 
> [0]: https://patchwork.freedesktop.org/patch/msgid/20240229162230.2634044-12-boris.brezillon@collabora.com
> [1]: https://lore.kernel.org/dri-devel/aMLB0Vs0dJ_AkU4z@google.com/

Any chance we can get this initial panthor support merged soon? I know
others (including me :-)) have been writing more panthor tests on top of
Daniel's branch, and it'd be great if we could submit those changes too.

> 
> Changes from v2:
> 
> Thanks, Boris {
>   - New helpers for command stream manipulation (see "struct cs_instr")
>   - Reworked bad indentation and formatting
>   - Removed BE support (UMD doesn't care about this either)
> }
> 
> Thanks, Daniel Stone {
>   - Introduced igt_panthor_get_first_core(), which correctly computes
>     the first available core using ffs()
>   - Stopped hardcoding the page size and switched to getpagesize()
>     instead,
>   - Switched to more specific versions of igt_assert as appropriate (like
>     igt_assert_neq(), etc)
>   - Simplified panthor_group.c considerably by relying on the helpers in
>     igt_panthor.[ch]
>   - Switched to the syncobj_create() and syncobj_destroy() helpers.
> }
> 
> - Added group creation helpers with sensible defaults (see
>   igt_panthor_group_create_simple(), for example)
> - Picked up tags
> 
> - Link to v2: https://lore.kernel.org/dri-devel/20250912181931.3738444-1-daniel.almeida@collabora.com/
> 
> Changes from v1:
> - Rebased on top of the latest master
> - Squashed patch 3 from v1 into patch 2.
> - Switched to /* */ comments in headers
> - Initialized padding fields to 0 as applicable in group_destroy and
>   vm_destroy
> - Removed wrong assert(gpu_rev != 0)
> - Changed indentation to use tabs
> - Rework igt_panthor_mmap_bo to take an offset (so we don't call the
>   mmap_offset ioctl twice)
> - Added igt_describe and docs to the functions igt_panthor.c
> - Linked to the driver in the cover letter and patch 1.
> - Improved the commit message for patch 1.
> Link to v1: https://lore.kernel.org/dri-devel/20250828130402.2549948-1-daniel.almeida@collabora.com/
> 
> 
> 
> Daniel Almeida (3):
>   lib: add support for opening Panthor devices
>   panthor: add initial infrastructure
>   tests/panthor: add panthor tests
> 
>  lib/drmtest.c                 |   1 +
>  lib/drmtest.h                 |   1 +
>  lib/igt_panthor.c             | 372 ++++++++++++++++++++++++++++++++++
>  lib/igt_panthor.h             | 221 ++++++++++++++++++++
>  lib/meson.build               |   1 +
>  meson.build                   |   8 +
>  tests/meson.build             |   2 +
>  tests/panthor/meson.build     |  15 ++
>  tests/panthor/panthor_gem.c   |  72 +++++++
>  tests/panthor/panthor_group.c | 123 +++++++++++
>  tests/panthor/panthor_query.c |  29 +++
>  tests/panthor/panthor_vm.c    |  84 ++++++++
>  12 files changed, 929 insertions(+)
>  create mode 100644 lib/igt_panthor.c
>  create mode 100644 lib/igt_panthor.h
>  create mode 100644 tests/panthor/meson.build
>  create mode 100644 tests/panthor/panthor_gem.c
>  create mode 100644 tests/panthor/panthor_group.c
>  create mode 100644 tests/panthor/panthor_query.c
>  create mode 100644 tests/panthor/panthor_vm.c
> 


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests
  2025-11-04 21:04   ` Boris Brezillon
@ 2025-11-06 11:13     ` Daniel Almeida
  2025-11-06 14:03       ` Pawel Sikora
  0 siblings, 1 reply; 12+ messages in thread
From: Daniel Almeida @ 2025-11-06 11:13 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: adrinael, arek, kamil.konieczny, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, liviu.dudau,
	steven.price, aliceryhl, jeffv, intel-gfx, igt-dev, dri-devel

Kamil, 

> On 4 Nov 2025, at 18:04, Boris Brezillon <boris.brezillon@collabora.com> wrote:
> 
> On Tue,  4 Nov 2025 17:28:43 -0300
> Daniel Almeida <daniel.almeida@collabora.com> wrote:
> 
>> +igt_main {
>> + int fd;
>> +
>> + igt_fixture {
>> + fd = drm_open_driver(DRIVER_PANTHOR);
>> + }
>> +
>> + igt_describe("Create and destroy a CSF group.");
>> + igt_subtest("group_create") {
>> + struct drm_panthor_vm_create vm_create = {};
>> + struct drm_panthor_vm_destroy vm_destroy = {};
>> + uint32_t group_handle;
>> +
>> + vm_create.flags = 0;
>> + do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
>> + igt_assert_neq(vm_create.id, 0);
>> +
>> + group_handle = igt_panthor_group_create_simple(fd, vm_create.id, 0);
>> + igt_assert_neq(group_handle, 0);
>> +
>> + igt_panthor_group_destroy(fd, group_handle, 0);
>> +
>> + vm_destroy = (struct drm_panthor_vm_destroy) { .id = vm_create.id };
>> + do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
>> + }
>> +
>> + igt_describe("Submit a job to a group and wait for completion. "
>> + "The job writes a known value to a buffer object that is then "
>> + "mmaped and checked.");
> 
> nit: indentation is off ^
> 
> igt_describe("Submit a job to a group and wait for completion. "
>     "The job writes a known value to a buffer object that is then "
>     "mmaped and checked.");

What should I do here?

Usually sending new versions so soon would be confusing. Given it's a trivial
fix, can you do it while applying? If not, should I wait a few days so that
this collects a few more tags?

— Daniel


^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests
  2025-11-06 11:13     ` Daniel Almeida
@ 2025-11-06 14:03       ` Pawel Sikora
  0 siblings, 0 replies; 12+ messages in thread
From: Pawel Sikora @ 2025-11-06 14:03 UTC (permalink / raw)
  To: Daniel Almeida
  Cc: Boris Brezillon, adrinael, arek, kamil.konieczny,
	juhapekka.heikkila, bhanuprakash.modem, ashutosh.dixit,
	karthik.b.s, liviu.dudau, steven.price, aliceryhl, jeffv,
	intel-gfx, igt-dev, pawel.sikora, dri-devel

Hi Daniel,

There is a new IGT GPU tools documentation site:
https://drm.pages.freedesktop.org/igt-gpu-tools/ which is available via Gitlab Pages.

Especially the part regarding hw platforms might be interested for you in case you
would like to add something about your tests:
https://drm.pages.freedesktop.org/igt-gpu-tools/platforms/
Modifications can be made in docs directory in markdown files and posted here(Patchwork)
for review for this or a new patch:
https://gitlab.freedesktop.org/drm/igt-gpu-tools/-/blob/master/docs/platforms.md

Please have a look in case you would like to add some note for others.

Kind Regards,
Pawel

On Thu, Nov 06, 2025 at 08:13:14AM -0300, Daniel Almeida wrote:
> Kamil, 
> 
> > On 4 Nov 2025, at 18:04, Boris Brezillon <boris.brezillon@collabora.com> wrote:
> > 
> > On Tue,  4 Nov 2025 17:28:43 -0300
> > Daniel Almeida <daniel.almeida@collabora.com> wrote:
> > 
> >> +igt_main {
> >> + int fd;
> >> +
> >> + igt_fixture {
> >> + fd = drm_open_driver(DRIVER_PANTHOR);
> >> + }
> >> +
> >> + igt_describe("Create and destroy a CSF group.");
> >> + igt_subtest("group_create") {
> >> + struct drm_panthor_vm_create vm_create = {};
> >> + struct drm_panthor_vm_destroy vm_destroy = {};
> >> + uint32_t group_handle;
> >> +
> >> + vm_create.flags = 0;
> >> + do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
> >> + igt_assert_neq(vm_create.id, 0);
> >> +
> >> + group_handle = igt_panthor_group_create_simple(fd, vm_create.id, 0);
> >> + igt_assert_neq(group_handle, 0);
> >> +
> >> + igt_panthor_group_destroy(fd, group_handle, 0);
> >> +
> >> + vm_destroy = (struct drm_panthor_vm_destroy) { .id = vm_create.id };
> >> + do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
> >> + }
> >> +
> >> + igt_describe("Submit a job to a group and wait for completion. "
> >> + "The job writes a known value to a buffer object that is then "
> >> + "mmaped and checked.");
> > 
> > nit: indentation is off ^
> > 
> > igt_describe("Submit a job to a group and wait for completion. "
> >     "The job writes a known value to a buffer object that is then "
> >     "mmaped and checked.");
> 
> What should I do here?
> 
> Usually sending new versions so soon would be confusing. Given it's a trivial
> fix, can you do it while applying? If not, should I wait a few days so that
> this collects a few more tags?
> 
> — Daniel
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

* Re: [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests
  2025-11-06  9:26   ` Boris Brezillon
@ 2025-11-06 17:10     ` Kamil Konieczny
  0 siblings, 0 replies; 12+ messages in thread
From: Kamil Konieczny @ 2025-11-06 17:10 UTC (permalink / raw)
  To: Boris Brezillon
  Cc: Daniel Almeida, adrinael, arek, juhapekka.heikkila,
	bhanuprakash.modem, ashutosh.dixit, karthik.b.s, liviu.dudau,
	steven.price, aliceryhl, jeffv, intel-gfx, igt-dev, dri-devel

Hi Boris,
On 2025-11-06 at 10:26:07 +0100, Boris Brezillon wrote:
> On Tue,  4 Nov 2025 17:28:43 -0300
> Daniel Almeida <daniel.almeida@collabora.com> wrote:
> 
> > Add an initial test suit covering query device properties, allocating
> > memory, binding and unbinding VA ranges through VM_BIND and submitting a
> > simple piece of work through GROUP_SUBMIT.
> > 
> > Acked-by: Kamil Konieczny <kamil.konieczny@linux.intel.com>
> > Signed-off-by: Daniel Almeida <daniel.almeida@collabora.com>
> 
> With the indentation fixed, this is
> 
> Reviewed-by: Boris Brezillon <boris.brezillon@collabora.com>
> 

I fixed it during merging, also fixed subject in first patch
and reformatted description (folding to 65-column).
Thank you all for this work!

Regards,
Kamil

> > ---
> >  lib/igt_panthor.c             | 331 ++++++++++++++++++++++++++++++++++
> >  lib/igt_panthor.h             |  28 +++
> >  tests/panthor/meson.build     |   5 +-
> >  tests/panthor/panthor_gem.c   |  72 ++++++++
> >  tests/panthor/panthor_group.c | 123 +++++++++++++
> >  tests/panthor/panthor_vm.c    |  84 +++++++++
> >  6 files changed, 642 insertions(+), 1 deletion(-)
> >  create mode 100644 tests/panthor/panthor_gem.c
> >  create mode 100644 tests/panthor/panthor_group.c
> >  create mode 100644 tests/panthor/panthor_vm.c
> > 
> > diff --git a/lib/igt_panthor.c b/lib/igt_panthor.c
> > index 0b690f796..73ada9c59 100644
> > --- a/lib/igt_panthor.c
> > +++ b/lib/igt_panthor.c
> > @@ -6,6 +6,148 @@
> >  #include "ioctl_wrappers.h"
> >  #include "panthor_drm.h"
> >  
> > +/**
> > + * igt_panthor_group_create:
> > + * @fd: device file descriptor
> > + * @group_create: pointer to group creation structure
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Create a group.
> > + */
> > +void igt_panthor_group_create(int fd, struct drm_panthor_group_create *group_create, int err)
> > +{
> > +	if (err)
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_CREATE, group_create, err);
> > +	else
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_CREATE, group_create);
> > +}
> > +
> > +/**
> > + * igt_panthor_group_destroy:
> > + * @fd: device file descriptor
> > + * @group_handle: group handle to destroy
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Destroy a group.
> > + */
> > +void igt_panthor_group_destroy(int fd, uint32_t group_handle, int err)
> > +{
> > +	struct drm_panthor_group_destroy group_destroy = {
> > +		.group_handle = group_handle,
> > +	};
> > +
> > +	if (err)
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_DESTROY, &group_destroy, err);
> > +	else
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_DESTROY, &group_destroy);
> > +}
> > +
> > +/**
> > + * igt_panthor_group_submit:
> > + * @fd: device file descriptor
> > + * @group_submit: pointer to group submission structure
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Submit work to a group.
> > + */
> > +void igt_panthor_group_submit(int fd, struct drm_panthor_group_submit *group_submit, int err)
> > +{
> > +	if (err)
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_GROUP_SUBMIT, group_submit, err);
> > +	else
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_GROUP_SUBMIT, group_submit);
> > +}
> > +
> > +/**
> > + * igt_panthor_get_first_core:
> > + * @cores_present: bitmask of available cores
> > + *
> > + * Get a mask with only the first available core bit set.
> > + *
> > + * Returns: core mask with first available core, or 0 if no cores available
> > + */
> > +uint64_t igt_panthor_get_first_core(uint64_t cores_present)
> > +{
> > +	if (cores_present == 0)
> > +		return 0;
> > +
> > +	return 1ULL << (ffs(cores_present) - 1);
> > +}
> > +
> > +/**
> > + * igt_panthor_group_create_simple:
> > + * @fd: device file descriptor
> > + * @vm_id: VM ID to associate with the group
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Create a group with a single queue and reasonable defaults.
> > + *
> > + * Returns: group handle on success
> > + */
> > +uint32_t igt_panthor_group_create_simple(int fd, uint32_t vm_id, int err)
> > +{
> > +	struct drm_panthor_gpu_info gpu_info = {};
> > +	struct drm_panthor_group_create group_create = {};
> > +	struct drm_panthor_queue_create queue = {};
> > +	struct drm_panthor_obj_array queues = {};
> > +
> > +	igt_panthor_query(fd, DRM_PANTHOR_DEV_QUERY_GPU_INFO, &gpu_info, sizeof(gpu_info), 0);
> > +
> > +	queue.priority = 0;
> > +	queue.ringbuf_size = 4096;
> > +	queues = (struct drm_panthor_obj_array)DRM_PANTHOR_OBJ_ARRAY(1, &queue);
> > +
> > +	group_create.queues = queues;
> > +	group_create.max_compute_cores = 1;
> > +	group_create.max_fragment_cores = 1;
> > +	group_create.max_tiler_cores = 1;
> > +	group_create.priority = PANTHOR_GROUP_PRIORITY_MEDIUM;
> > +	group_create.compute_core_mask = igt_panthor_get_first_core(gpu_info.shader_present);
> > +	group_create.fragment_core_mask = igt_panthor_get_first_core(gpu_info.shader_present);
> > +	group_create.tiler_core_mask = igt_panthor_get_first_core(gpu_info.tiler_present);
> > +	group_create.vm_id = vm_id;
> > +
> > +	igt_panthor_group_create(fd, &group_create, err);
> > +	return group_create.group_handle;
> > +}
> > +
> > +/**
> > + * igt_panthor_group_submit_simple:
> > + * @fd: device file descriptor
> > + * @group_handle: group handle to submit to
> > + * @queue_index: queue index within the group
> > + * @stream_addr: GPU address of the command stream
> > + * @stream_size: size of the command stream
> > + * @syncobj_handle: sync object handle for completion signaling
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Submit work to a group queue with a simple interface.
> > + */
> > +void igt_panthor_group_submit_simple(int fd, uint32_t group_handle,
> > +				     uint32_t queue_index, uint64_t stream_addr,
> > +				     uint32_t stream_size, uint32_t syncobj_handle,
> > +				     int err)
> > +{
> > +	struct drm_panthor_group_submit group_submit = {};
> > +	struct drm_panthor_queue_submit queue_submit = {};
> > +	struct drm_panthor_sync_op sync_op = {};
> > +
> > +	sync_op.handle = syncobj_handle;
> > +	sync_op.flags = DRM_PANTHOR_SYNC_OP_SIGNAL;
> > +
> > +	queue_submit.syncs = (struct drm_panthor_obj_array)DRM_PANTHOR_OBJ_ARRAY(1, &sync_op);
> > +	queue_submit.queue_index = queue_index;
> > +	queue_submit.stream_size = stream_size;
> > +	queue_submit.stream_addr = stream_addr;
> > +	queue_submit.latest_flush = 0;
> > +
> > +	group_submit.group_handle = group_handle;
> > +	group_submit.queue_submits = (struct drm_panthor_obj_array)
> > +		DRM_PANTHOR_OBJ_ARRAY(1, &queue_submit);
> > +
> > +	igt_panthor_group_submit(fd, &group_submit, err);
> > +}
> > +
> >  /**
> >   * SECTION:igt_panthor
> >   * @short_description: Panthor support library
> > @@ -39,3 +181,192 @@ void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err)
> >  	else
> >  		do_ioctl(fd, DRM_IOCTL_PANTHOR_DEV_QUERY, &query);
> >  }
> > +
> > +/**
> > + * igt_panthor_vm_create:
> > + * @fd: device file descriptor
> > + * @vm_id: pointer to store the created VM ID
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Creates a VM.
> > + */
> > +void igt_panthor_vm_create(int fd, uint32_t *vm_id, int err)
> > +{
> > +	struct drm_panthor_vm_create vm_create = {};
> > +
> > +	if (err) {
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create, err);
> > +	} else {
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
> > +		*vm_id = vm_create.id;
> > +	}
> > +}
> > +
> > +/**
> > + * igt_panthor_vm_destroy:
> > + * @fd: device file descriptor
> > + * @vm_id: VM ID to destroy
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Destroys a VM.
> > + */
> > +void igt_panthor_vm_destroy(int fd, uint32_t vm_id, int err)
> > +{
> > +	struct drm_panthor_vm_destroy vm_destroy = {
> > +		.id = vm_id,
> > +	};
> > +
> > +	if (err)
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy, err);
> > +	else
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
> > +}
> > +
> > +/**
> > + * igt_panthor_vm_bind:
> > + * @fd: device file descriptor
> > + * @vm_id: VM ID to bind the buffer to
> > + * @bo_handle: buffer object handle to bind
> > + * @va: virtual address to bind at
> > + * @size: size of the binding
> > + * @flags: binding flags
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Bind a buffer object to a virtual address in the specified VM.
> > + */
> > +void igt_panthor_vm_bind(int fd, uint32_t vm_id, uint32_t bo_handle,
> > +			 uint64_t va, uint64_t size, uint32_t flags, int err)
> > +{
> > +	struct drm_panthor_vm_bind_op bind_op = {
> > +		.flags = flags,
> > +		.bo_handle = bo_handle,
> > +		.va = va,
> > +		.size = size,
> > +	};
> > +
> > +	struct drm_panthor_vm_bind vm_bind = {
> > +		.vm_id = vm_id,
> > +		.flags = 0,
> > +		.ops = DRM_PANTHOR_OBJ_ARRAY(1, &bind_op),
> > +	};
> > +
> > +	if (err)
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_VM_BIND, &vm_bind, err);
> > +	else
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_BIND, &vm_bind);
> > +}
> > +
> > +/**
> > + * igt_panthor_bo_create:
> > + * @fd: device file descriptor
> > + * @bo: pointer to panthor_bo structure to initialize
> > + * @size: requested buffer size in bytes
> > + * @flags: buffer object creation flags
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Creates a new buffer object
> > + */
> > +void igt_panthor_bo_create(int fd, struct panthor_bo *bo,
> > +			   uint64_t size, uint32_t flags, int err)
> > +{
> > +	struct drm_panthor_bo_create bo_create = {
> > +		.size = size,
> > +		.flags = flags,
> > +	};
> > +
> > +	if (err)
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_BO_CREATE, &bo_create, err);
> > +	else
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_BO_CREATE, &bo_create);
> > +
> > +	bo->handle = bo_create.handle;
> > +	bo->size = bo_create.size;
> > +	bo->offset = 0;
> > +	bo->map = NULL;
> > +}
> > +
> > +/**
> > + * igt_panthor_bo_mmap_offset:
> > + * @fd: device file descriptor
> > + * @handle: buffer object handle
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Get the mmap offset for a buffer object.
> > + *
> > + * Returns: the mmap offset for the buffer object
> > + */
> > +uint64_t igt_panthor_bo_mmap_offset(int fd, uint32_t handle, int err)
> > +{
> > +	struct drm_panthor_bo_mmap_offset bo_mmap_offset = {
> > +		.handle = handle,
> > +	};
> > +
> > +	if (err)
> > +		do_ioctl_err(fd, DRM_IOCTL_PANTHOR_BO_MMAP_OFFSET, &bo_mmap_offset, err);
> > +	else
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_BO_MMAP_OFFSET, &bo_mmap_offset);
> > +
> > +	return bo_mmap_offset.offset;
> > +}
> > +
> > +/**
> > + * igt_panthor_mmap_bo:
> > + * @fd: device file descriptor
> > + * @handle: buffer object handle
> > + * @size: size of the buffer to map
> > + * @prot: memory protection flags (e.g., PROT_READ | PROT_WRITE)
> > + * @offset: mmap offset for the buffer object
> > + *
> > + * Map a buffer object into the process address space.
> > + *
> > + * Returns: pointer to the mapped memory, or NULL on failure
> > + */
> > +void *igt_panthor_mmap_bo(int fd, uint32_t handle, uint64_t size,
> > +			  unsigned int prot, uint64_t offset)
> > +{
> > +	void *ptr;
> > +
> > +	ptr = mmap(0, size, prot, MAP_SHARED, fd, offset);
> > +	if (ptr == MAP_FAILED)
> > +		return NULL;
> > +	else
> > +		return ptr;
> > +}
> > +
> > +/**
> > + * igt_panthor_bo_create_mapped:
> > + * @fd: device file descriptor
> > + * @bo: pointer to panthor_bo structure to initialize
> > + * @size: requested buffer size in bytes
> > + * @flags: buffer object creation flags
> > + * @err: expected error code, or 0 for success
> > + *
> > + * Create a new buffer object on the panthor device and map it into
> > + * the process address space.
> > + */
> > +void igt_panthor_bo_create_mapped(int fd, struct panthor_bo *bo, uint64_t size,
> > +				  uint32_t flags, int err)
> > +{
> > +	igt_panthor_bo_create(fd, bo, size, flags, err);
> > +	bo->offset = igt_panthor_bo_mmap_offset(fd, bo->handle, err);
> > +	bo->map = igt_panthor_mmap_bo(fd, bo->handle, bo->size,
> > +				      PROT_READ | PROT_WRITE, bo->offset);
> > +}
> > +
> > +/**
> > + * igt_panthor_free_bo:
> > + * @fd: panthor device file descriptor
> > + * @bo: pointer to panthor_bo structure to free
> > + *
> > + * Free a buffer object and unmap it if it was mapped.
> > + */
> > +void igt_panthor_free_bo(int fd, struct panthor_bo *bo)
> > +{
> > +	if (!bo)
> > +		return;
> > +
> > +	if (bo->map)
> > +		munmap(bo->map, bo->size);
> > +
> > +	gem_close(fd, bo->handle);
> > +}
> > diff --git a/lib/igt_panthor.h b/lib/igt_panthor.h
> > index 6f94b8f79..dc90033c0 100644
> > --- a/lib/igt_panthor.h
> > +++ b/lib/igt_panthor.h
> > @@ -7,8 +7,36 @@
> >  #include <stddef.h>
> >  #include <stdint.h>
> >  #include <stdbool.h>
> > +#include "panthor_drm.h"
> > +
> > +struct panthor_bo {
> > +	int handle;
> > +	uint64_t offset;
> > +	uint64_t size;
> > +	void *map;
> > +};
> >  
> >  void igt_panthor_query(int fd, int32_t type, void *data, size_t size, int err);
> > +void igt_panthor_vm_create(int fd, uint32_t *vm_id, int err);
> > +void igt_panthor_vm_destroy(int fd, uint32_t vm_id, int err);
> > +void igt_panthor_vm_bind(int fd, uint32_t vm_id, uint32_t bo_handle, uint64_t va,
> > +			 uint64_t size, uint32_t flags, int err);
> > +void igt_panthor_bo_create(int fd, struct panthor_bo *bo, uint64_t size, uint32_t flags, int err);
> > +uint64_t igt_panthor_bo_mmap_offset(int fd, uint32_t handle, int err);
> > +void igt_panthor_free_bo(int fd, struct panthor_bo *bo);
> > +void igt_panthor_bo_create_mapped(int fd, struct panthor_bo *bo, uint64_t size,
> > +				  uint32_t flags, int err);
> > +void *igt_panthor_mmap_bo(int fd, uint32_t handle, uint64_t size,
> > +			  unsigned int prot, uint64_t offset);
> > +void igt_panthor_group_create(int fd, struct drm_panthor_group_create *group_create, int err);
> > +void igt_panthor_group_destroy(int fd, uint32_t group_handle, int err);
> > +void igt_panthor_group_submit(int fd, struct drm_panthor_group_submit *group_submit, int err);
> > +uint32_t igt_panthor_group_create_simple(int fd, uint32_t vm_id, int err);
> > +void igt_panthor_group_submit_simple(int fd, uint32_t group_handle,
> > +				     uint32_t queue_index, uint64_t stream_addr,
> > +				     uint32_t stream_size, uint32_t syncobj_handle,
> > +				     int err);
> > +uint64_t igt_panthor_get_first_core(uint64_t cores_present);
> >  
> >  enum cs_opcode {
> >  	CS_OPCODE_NOP = 0,
> > diff --git a/tests/panthor/meson.build b/tests/panthor/meson.build
> > index ce13aebaa..42a46e993 100644
> > --- a/tests/panthor/meson.build
> > +++ b/tests/panthor/meson.build
> > @@ -1,5 +1,8 @@
> >  panthor_progs = [
> > -	'panthor_query'
> > +	'panthor_gem',
> > +	'panthor_group',
> > +	'panthor_query',
> > +	'panthor_vm',
> >  ]
> >  
> >  foreach prog : panthor_progs
> > diff --git a/tests/panthor/panthor_gem.c b/tests/panthor/panthor_gem.c
> > new file mode 100644
> > index 000000000..57cd97e80
> > --- /dev/null
> > +++ b/tests/panthor/panthor_gem.c
> > @@ -0,0 +1,72 @@
> > +// SPDX-License-Identifier: MIT
> > +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> > +
> > +#include <unistd.h>
> > +
> > +#include "igt.h"
> > +#include "igt_core.h"
> > +#include "igt_panthor.h"
> > +
> > +igt_main {
> > +	int fd;
> > +
> > +	igt_fixture {
> > +		fd = drm_open_driver(DRIVER_PANTHOR);
> > +	}
> > +
> > +	igt_describe("Create a buffer object");
> > +	igt_subtest("bo_create") {
> > +		struct panthor_bo bo;
> > +
> > +		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
> > +		igt_assert_neq(bo.handle, 0);
> > +
> > +		igt_panthor_free_bo(fd, &bo);
> > +	}
> > +
> > +	igt_describe("Create a fake mmap offset for a buffer object");
> > +	igt_subtest("bo_mmap_offset") {
> > +		struct panthor_bo bo;
> > +		uint64_t mmap_offset;
> > +
> > +		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
> > +		igt_assert_neq(bo.handle, 0);
> > +
> > +		mmap_offset = igt_panthor_bo_mmap_offset(fd, bo.handle, 0);
> > +		igt_assert_neq(mmap_offset, 0);
> > +
> > +		igt_panthor_free_bo(fd, &bo);
> > +	}
> > +
> > +	igt_describe("Same as bo_mmap_offset but with an invalid handle");
> > +	igt_subtest("bo_mmap_offset_invalid_handle") {
> > +		struct panthor_bo bo;
> > +		uint64_t mmap_offset;
> > +
> > +		igt_panthor_bo_create(fd, &bo, 4096, 0, 0);
> > +		igt_assert_neq(bo.handle, 0);
> > +
> > +		mmap_offset = igt_panthor_bo_mmap_offset(fd, 0xdeadbeef, ENOENT);
> > +		igt_assert_eq(mmap_offset, 0);
> > +
> > +		igt_panthor_free_bo(fd, &bo);
> > +	}
> > +
> > +	igt_describe_f("Create a buffer object whose size is not page-aligned, and check "
> > +		       "that the allocated size is rounded up to the next page size (%" PRIu64 ").",
> > +		       (uint64_t)getpagesize() * 2);
> > +	igt_subtest("bo_create_round_size") {
> > +		struct panthor_bo bo;
> > +		uint64_t expected_size = getpagesize() * 2;
> > +
> > +		igt_panthor_bo_create(fd, &bo, 5000, 0, 0);
> > +		igt_assert_neq(bo.handle, 0);
> > +		igt_assert_eq(bo.size, expected_size);
> > +
> > +		igt_panthor_free_bo(fd, &bo);
> > +	}
> > +
> > +	igt_fixture {
> > +		drm_close_driver(fd);
> > +	}
> > +}
> > diff --git a/tests/panthor/panthor_group.c b/tests/panthor/panthor_group.c
> > new file mode 100644
> > index 000000000..18c0fc8cb
> > --- /dev/null
> > +++ b/tests/panthor/panthor_group.c
> > @@ -0,0 +1,123 @@
> > +// SPDX-License-Identifier: MIT
> > +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> > +
> > +#include <stdint.h>
> > +#include <sys/mman.h>
> > +#include <unistd.h>
> > +
> > +#include "igt.h"
> > +#include "igt_panthor.h"
> > +#include "igt_syncobj.h"
> > +#include "panthor_drm.h"
> > +
> > +static size_t
> > +issue_store_multiple(uint8_t *cs, uint64_t kernel_va, uint32_t constant)
> > +{
> > +	const uint8_t kernel_va_reg = 68;
> > +	const uint8_t constant_reg = 70;
> > +	uint64_t instrs[] = {
> > +		/* MOV48: Load the source register ([r68; r69]) with the kernel address */
> > +		cs_mov48(kernel_va_reg, kernel_va),
> > +		/* MOV32: Load a known constant into r70 */
> > +		cs_mov32(constant_reg, constant),
> > +		/* STORE_MULTIPLE: Store the first register to the address pointed
> > +		 * to by [r68; r69]
> > +		 */
> > +		cs_stm32(kernel_va_reg, constant_reg, 0),
> > +		/* FLUSH all Wait for all cores */
> > +		cs_wait(0xff, false),
> > +		/* MOV32: Clear r70 to 0 */
> > +		cs_mov32(constant_reg, 0),
> > +		/* FLUSH_CACHE: Clean and invalidate all caches */
> > +		cs_flush(CS_FLUSH_MODE_CLEAN_AND_INVALIDATE,
> > +			 CS_FLUSH_MODE_CLEAN_AND_INVALIDATE,
> > +			 CS_FLUSH_MODE_INVALIDATE,
> > +			 0, constant_reg, 1),
> > +		cs_wait(0xff, false),
> > +	};
> > +
> > +	memcpy(cs, instrs, sizeof(instrs));
> > +	return sizeof(instrs);
> > +}
> > +
> > +igt_main {
> > +	int fd;
> > +
> > +	igt_fixture {
> > +		fd = drm_open_driver(DRIVER_PANTHOR);
> > +	}
> > +
> > +	igt_describe("Create and destroy a CSF group.");
> > +	igt_subtest("group_create") {
> > +		struct drm_panthor_vm_create vm_create = {};
> > +		struct drm_panthor_vm_destroy vm_destroy = {};
> > +		uint32_t group_handle;
> > +
> > +		vm_create.flags = 0;
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_CREATE, &vm_create);
> > +		igt_assert_neq(vm_create.id, 0);
> > +
> > +		group_handle = igt_panthor_group_create_simple(fd, vm_create.id, 0);
> > +		igt_assert_neq(group_handle, 0);
> > +
> > +		igt_panthor_group_destroy(fd, group_handle, 0);
> > +
> > +		vm_destroy = (struct drm_panthor_vm_destroy) { .id = vm_create.id };
> > +		do_ioctl(fd, DRM_IOCTL_PANTHOR_VM_DESTROY, &vm_destroy);
> > +	}
> > +
> > +	igt_describe("Submit a job to a group and wait for completion. "
> > +				 "The job writes a known value to a buffer object that is then "
> > +				 "mmaped and checked.");
> > +	igt_subtest("group_submit") {
> > +		uint32_t vm_id;
> > +		uint32_t group_handle;
> > +		struct panthor_bo cmd_buf_bo = {};
> > +		struct panthor_bo result_bo = {};
> > +		uint64_t command_stream_gpu_addr;
> > +		uint32_t command_stream_size;
> > +		uint64_t result_gpu_addr;
> > +		uint32_t syncobj_handle;
> > +		const int INITIAL_VA = 0x1000000;
> > +
> > +		igt_panthor_vm_create(fd, &vm_id, 0);
> > +
> > +		igt_panthor_bo_create_mapped(fd, &cmd_buf_bo, 4096, 0, 0);
> > +		igt_panthor_vm_bind(fd, vm_id, cmd_buf_bo.handle, INITIAL_VA,
> > +				    cmd_buf_bo.size, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> > +		command_stream_gpu_addr = INITIAL_VA;
> > +
> > +		/* Create the BO to receive the result of the store. */
> > +		igt_panthor_bo_create_mapped(fd, &result_bo, 4096, 0, 0);
> > +		/* Also bind the result BO. */
> > +		igt_panthor_vm_bind(fd, vm_id, result_bo.handle, INITIAL_VA + 4096,
> > +				    result_bo.size, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> > +		result_gpu_addr = INITIAL_VA + 4096;
> > +
> > +		command_stream_size = issue_store_multiple(cmd_buf_bo.map, result_gpu_addr, 0xdeadbeef);
> > +
> > +		group_handle = igt_panthor_group_create_simple(fd, vm_id, 0);
> > +		igt_assert_neq(group_handle, 0);
> > +
> > +		syncobj_handle = syncobj_create(fd, 0);
> > +
> > +		igt_panthor_group_submit_simple(fd, group_handle, 0, command_stream_gpu_addr, command_stream_size, syncobj_handle, 0);
> > +
> > +		igt_assert(syncobj_wait(fd, &syncobj_handle, 1, INT64_MAX, 0, NULL));
> > +
> > +		igt_assert_eq(*(uint32_t *)result_bo.map, 0xdeadbeef);
> > +
> > +		syncobj_destroy(fd, syncobj_handle);
> > +
> > +		igt_panthor_group_destroy(fd, group_handle, 0);
> > +
> > +		igt_panthor_vm_destroy(fd, vm_id, 0);
> > +
> > +		igt_panthor_free_bo(fd, &cmd_buf_bo);
> > +		igt_panthor_free_bo(fd, &result_bo);
> > +	}
> > +
> > +	igt_fixture {
> > +		drm_close_driver(fd);
> > +	}
> > +}
> > diff --git a/tests/panthor/panthor_vm.c b/tests/panthor/panthor_vm.c
> > new file mode 100644
> > index 000000000..18a0622c7
> > --- /dev/null
> > +++ b/tests/panthor/panthor_vm.c
> > @@ -0,0 +1,84 @@
> > +// SPDX-License-Identifier: MIT
> > +// SPDX-FileCopyrightText: Copyright (C) 2025 Collabora Ltd.
> > +
> > +#include "igt.h"
> > +#include "igt_core.h"
> > +#include "igt_panthor.h"
> > +#include "panthor_drm.h"
> > +
> > +igt_main {
> > +	int fd;
> > +
> > +	igt_fixture {
> > +		fd = drm_open_driver(DRIVER_PANTHOR);
> > +	}
> > +
> > +	igt_describe("Create and destroy a VM");
> > +	igt_subtest("vm_create_destroy") {
> > +		uint32_t vm_id;
> > +
> > +		igt_panthor_vm_create(fd, &vm_id, 0);
> > +		igt_assert_neq(vm_id, 0);
> > +
> > +		igt_panthor_vm_destroy(fd, vm_id, 0);
> > +	}
> > +
> > +	igt_subtest("vm_destroy_invalid") {
> > +		igt_panthor_vm_destroy(fd, 0xdeadbeef, EINVAL);
> > +	}
> > +
> > +	igt_describe("Test the VM_BIND API synchronously");
> > +	igt_subtest("vm_bind") {
> > +		uint32_t vm_id;
> > +		struct panthor_bo bo;
> > +		uint64_t bo_size = 0x1000;
> > +
> > +		igt_panthor_vm_create(fd, &vm_id, 0);
> > +		igt_assert_neq(vm_id, 0);
> > +
> > +		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
> > +		igt_panthor_vm_bind(fd, vm_id, bo.handle,
> > +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> > +
> > +		igt_panthor_vm_destroy(fd, vm_id, 0);
> > +	}
> > +
> > +	igt_describe("Test unbinding a previously bound range");
> > +	igt_subtest("vm_unbind") {
> > +		uint32_t vm_id;
> > +		struct panthor_bo bo;
> > +		uint64_t bo_size = 0x1000;
> > +
> > +		igt_panthor_vm_create(fd, &vm_id, 0);
> > +		igt_assert_neq(vm_id, 0);
> > +
> > +		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
> > +		igt_panthor_vm_bind(fd, vm_id, bo.handle,
> > +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_MAP, 0);
> > +		igt_panthor_vm_bind(fd, vm_id, 0,
> > +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP, 0);
> > +
> > +		igt_panthor_vm_destroy(fd, vm_id, 0);
> > +	}
> > +
> > +	igt_describe("Test unbinding an address range that was not previously bound");
> > +	igt_subtest("vm_unbind_invalid_address") {
> > +		uint32_t vm_id;
> > +		struct panthor_bo bo;
> > +		uint64_t bo_size = 0x1000;
> > +
> > +		igt_panthor_vm_create(fd, &vm_id, 0);
> > +		igt_assert_neq(vm_id, 0);
> > +
> > +		igt_panthor_bo_create(fd, &bo, bo_size, 0, 0);
> > +
> > +		/* This was not bound previously*/
> > +		igt_panthor_vm_bind(fd, vm_id, bo.handle,
> > +				    0x1000, 0x1000, DRM_PANTHOR_VM_BIND_OP_TYPE_UNMAP, EINVAL);
> > +		igt_panthor_vm_destroy(fd, vm_id, 0);
> > +	}
> > +
> > +	igt_fixture {
> > +		drm_close_driver(fd);
> > +	}
> > +}
> 

^ permalink raw reply	[flat|nested] 12+ messages in thread

end of thread, other threads:[~2025-11-06 17:13 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-04 20:28 [PATCH i-g-t v3 0/3] Add initial Panthor tests Daniel Almeida
2025-11-04 20:28 ` [PATCH i-g-t v3 1/3] lib: add support for opening Panthor devices Daniel Almeida
2025-11-06  9:24   ` Boris Brezillon
2025-11-04 20:28 ` [PATCH i-g-t v3 2/3] panthor: add initial infrastructure Daniel Almeida
2025-11-06  9:25   ` Boris Brezillon
2025-11-04 20:28 ` [PATCH i-g-t v3 3/3] tests/panthor: add panthor tests Daniel Almeida
2025-11-04 21:04   ` Boris Brezillon
2025-11-06 11:13     ` Daniel Almeida
2025-11-06 14:03       ` Pawel Sikora
2025-11-06  9:26   ` Boris Brezillon
2025-11-06 17:10     ` Kamil Konieczny
2025-11-06  9:29 ` [PATCH i-g-t v3 0/3] Add initial Panthor tests Boris Brezillon

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).