From 44046bfbcb30a19c45416113a2a82a4d17a1a998 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Mon, 14 Aug 2023 14:06:45 +0200
Subject: [PATCH 01/13] main: update GPUs comment for dbus property

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 src/net.hadess.SwitcherooControl.xml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/net.hadess.SwitcherooControl.xml b/src/net.hadess.SwitcherooControl.xml
index e52bc1a..59a8896 100644
--- a/src/net.hadess.SwitcherooControl.xml
+++ b/src/net.hadess.SwitcherooControl.xml
@@ -38,7 +38,8 @@
         will contain a user-facing name for the GPU, the "Environment" (as) key will
         contain an array of even number of strings, each being an environment
         variable to set to use the GPU, followed by its value, the "Default" (b) key
-        will tag the default (usually integrated) GPU.
+        will tag the default GPU, the "Discrete" (b) key tags if the GPU is a
+        dedicated component.
     -->
     <property name="GPUs" type="aa{sv}" access="read"/>
 
-- 
2.49.0


From 4f31415cb61a50c2bcba1510a7511518417d0970 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Mon, 11 Sep 2023 15:21:46 +0200
Subject: [PATCH 02/13] main: add Discrete key

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 .gitlab-ci.yml                     |   1 +
 data/30-discrete-gpu.rules.in      |   3 +
 data/meson.build                   |   7 ++
 meson.build                        |   9 +++
 meson_options.txt                  |  24 +++++++
 src/discrete-detection/amdgpu.c    |  46 +++++++++++++
 src/discrete-detection/meson.build |  18 +++++
 src/discrete-detection/nouveau.c   | 105 +++++++++++++++++++++++++++++
 src/meson.build                    |   4 +-
 src/switcheroo-control.c           |  16 +++++
 10 files changed, 232 insertions(+), 1 deletion(-)
 create mode 100644 data/30-discrete-gpu.rules.in
 create mode 100644 src/discrete-detection/amdgpu.c
 create mode 100644 src/discrete-detection/meson.build
 create mode 100644 src/discrete-detection/nouveau.c

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index a6aa3c7..a09fe20 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -3,6 +3,7 @@ image: fedora:rawhide
 variables:
   DEPENDENCIES: glib2-devel
                 libgudev-devel
+                libdrm-devel
                 gtk-doc
                 gcc
                 gcc-c++
diff --git a/data/30-discrete-gpu.rules.in b/data/30-discrete-gpu.rules.in
new file mode 100644
index 0000000..a803ed4
--- /dev/null
+++ b/data/30-discrete-gpu.rules.in
@@ -0,0 +1,3 @@
+DRIVERS=="amdgpu", SUBSYSTEM=="drm", PROGRAM="@libexecdir@/check-discrete-amdgpu $env{DEVNAME}", TAG+="switcheroo-discrete-gpu"
+DRIVERS=="nouveau", SUBSYSTEM=="drm", PROGRAM="@libexecdir@/check-discrete-nouveau $env{DEVNAME}", TAG+="switcheroo-discrete-gpu"
+DRIVERS=="nvidia", TAG+="switcheroo-discrete-gpu"
diff --git a/data/meson.build b/data/meson.build
index 85e5c93..38cf96c 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -17,3 +17,10 @@ install_data(
   '30-pci-intel-gpu.hwdb',
   install_dir: hwdb_dir,
 )
+
+configure_file(
+  input: '30-discrete-gpu.rules.in',
+  output: '30-discrete-gpu.rules',
+  configuration: data_conf,
+  install_dir: rules_dir,
+)
diff --git a/meson.build b/meson.build
index b8f4bff..b3aaf0c 100644
--- a/meson.build
+++ b/meson.build
@@ -20,6 +20,9 @@ gnome = import('gnome')
 glib = dependency('glib-2.0', version: '>= 2.56.0')
 gio = dependency('gio-2.0', version: '>= 2.56.0')
 gudev = dependency('gudev-1.0', version: '>= 232')
+libdrm = dependency('libdrm', version: '>= 2.4.97', required: get_option('libdrm'))
+libdrm_nouveau = dependency('libdrm_nouveau', version: '>= 2.4.97', required: get_option('libdrm_nouveau'))
+libdrm_amdgpu = dependency('libdrm_amdgpu', version: '>= 2.4.97', required: get_option('libdrm_amdgpu'))
 
 systemd_systemunitdir = get_option('systemdsystemunitdir')
 if systemd_systemunitdir == ''
@@ -32,6 +35,12 @@ if hwdb_dir == ''
   hwdb_dir = udevdir / 'hwdb.d'
 endif
 
+rules_dir = get_option('rulesdir')
+if rules_dir == ''
+  udevdir = dependency('udev').get_pkgconfig_variable('udevdir')
+  rules_dir = udevdir / 'rules.d'
+endif
+
 # Make like license available in the build root for docs
 configure_file(
   input: 'COPYING',
diff --git a/meson_options.txt b/meson_options.txt
index c8d9619..b8d671a 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -10,6 +10,12 @@ option('hwdbdir',
   description: 'Directory for hwdb files',
 )
 
+option('rulesdir',
+  type: 'string',
+  value: '',
+  description: 'Directory for ruke files',
+)
+
 option('gtk_doc',
   type: 'boolean',
   value: false,
@@ -21,3 +27,21 @@ option('tests',
   type: 'boolean',
   value: false
 )
+
+option('libdrm',
+  description: 'Whether libdrm should be used to probe GPUs',
+  type: 'feature',
+  value: 'auto'
+)
+
+option('libdrm_nouveau',
+  description: 'Whether libdrm_nouveau should be used to probe Nvidia GPUs',
+  type: 'feature',
+  value: 'auto'
+)
+
+option('libdrm_amdgpu',
+  description: 'Whether libdrm_amdgpu should be used to probe AMD GPUs',
+  type: 'feature',
+  value: 'auto'
+)
diff --git a/src/discrete-detection/amdgpu.c b/src/discrete-detection/amdgpu.c
new file mode 100644
index 0000000..2d9804f
--- /dev/null
+++ b/src/discrete-detection/amdgpu.c
@@ -0,0 +1,46 @@
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <gio/gio.h>
+#include <gudev/gudev.h>
+
+#include <amdgpu.h>
+#include <amdgpu_drm.h>
+
+typedef int handle;
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(handle, close, -1)
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(amdgpu_device_handle, free);
+
+int main(int argc, char** argv)
+{
+	if (argc < 2)
+	{
+		puts ("check-discrete-amdgpu [DEVNAME]");
+		return EXIT_FAILURE;
+	}
+
+	const char *devname;
+	g_auto(handle) fd = -1;
+	g_autoptr(GUdevDevice) parent = NULL;
+	struct drm_amdgpu_info_device device_info = {0};
+	amdgpu_device_handle device = NULL;
+	uint32_t drm_major, drm_minor;
+
+	devname = argv[1];
+	fd = open (devname, O_RDWR);
+	if (fd < 0)
+		return EXIT_FAILURE;
+
+	if (amdgpu_device_initialize (fd, &drm_major, &drm_minor, &device))
+		return EXIT_FAILURE;
+
+	if (amdgpu_query_info (device, AMDGPU_INFO_DEV_INFO, sizeof(device_info), &device_info))
+		return EXIT_FAILURE;
+
+	/* AMDGPU_IDS_FLAGS_FUSION is set for all APUs */
+	if (device_info.ids_flags & AMDGPU_IDS_FLAGS_FUSION)
+		return EXIT_FAILURE;
+
+	return EXIT_SUCCESS;
+}
diff --git a/src/discrete-detection/meson.build b/src/discrete-detection/meson.build
new file mode 100644
index 0000000..3a6c03f
--- /dev/null
+++ b/src/discrete-detection/meson.build
@@ -0,0 +1,18 @@
+
+if libdrm_amdgpu.found()
+  executable('check-discrete-amdgpu',
+    files('amdgpu.c'),
+    dependencies: deps,
+    install: true,
+    install_dir: libexecdir,
+  )
+endif
+
+if libdrm.found() and libdrm_nouveau.found()
+  executable('check-discrete-nouveau',
+    files('nouveau.c'),
+    dependencies: deps,
+    install: true,
+    install_dir: libexecdir,
+  )
+endif
diff --git a/src/discrete-detection/nouveau.c b/src/discrete-detection/nouveau.c
new file mode 100644
index 0000000..0a1f220
--- /dev/null
+++ b/src/discrete-detection/nouveau.c
@@ -0,0 +1,105 @@
+
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <gio/gio.h>
+#include <gudev/gudev.h>
+
+#include <xf86drm.h>
+#include <nouveau_drm.h>
+#include <nouveau/nvif/ioctl.h>
+#include <nvif/cl0080.h>
+#include <nvif/class.h>
+
+typedef int handle;
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(handle, close, -1)
+
+int main(int argc, char** argv)
+{
+	if (argc < 2)
+	{
+		puts ("check-discrete-nouveau [DEVNAME]");
+		return EXIT_FAILURE;
+	}
+
+	const char *devname;
+	g_auto(handle) fd = -1;
+
+	devname = argv[1];
+	fd = open (devname, O_RDWR);
+	if (fd < 0)
+		return EXIT_FAILURE;
+
+	g_autofree void *device = malloc(352);
+
+	/* Init device */
+	{	
+		struct {
+			struct nvif_ioctl_v0 ioctl;
+			struct nvif_ioctl_new_v0 new;
+			struct nv_device_v0 dev;
+		} init_args = {
+		  .ioctl = {
+			 .object = 0,
+			 .owner = NVIF_IOCTL_V0_OWNER_ANY,
+			 .route = 0x00,
+			 .type = NVIF_IOCTL_V0_NEW,
+			 .version = 0,
+		  },
+		  .new = {
+			 .handle = 0,
+			 .object = (uintptr_t)device,
+			 .oclass = NV_DEVICE,
+			 .route = NVIF_IOCTL_V0_ROUTE_NVIF,
+			 .token = (uintptr_t)device,
+			 .version = 0,
+		  },
+		  .dev = {
+			 .device = ~0ULL,
+		  },
+	   };
+
+		if (drmCommandWrite (fd, DRM_NOUVEAU_NVIF, &init_args, sizeof(init_args)))
+			return EXIT_FAILURE;
+	}
+
+	/* Query device info */
+	struct {
+		struct nvif_ioctl_v0 ioctl;
+		struct nvif_ioctl_mthd_v0 mthd;
+		struct nv_device_info_v0 info;
+	} args = {
+		.ioctl = {
+			.object = (uintptr_t)device,
+			.owner = NVIF_IOCTL_V0_OWNER_ANY,
+			.route = 0x00,
+			.type = NVIF_IOCTL_V0_MTHD,
+			.version = 0,
+		},
+		.mthd = {
+			.method = NV_DEVICE_V0_INFO,
+			.version = 0,
+		},
+		.info = {
+			.version = 0,
+		},
+	};
+
+	if (drmCommandWriteRead (fd, DRM_NOUVEAU_NVIF, &args, sizeof(args)))
+		return EXIT_FAILURE;
+
+
+	switch (args.info.platform)
+	{
+		case NV_DEVICE_INFO_V0_IGP:
+		case NV_DEVICE_INFO_V0_SOC:
+			return EXIT_FAILURE;
+
+		case NV_DEVICE_INFO_V0_PCI:
+		case NV_DEVICE_INFO_V0_AGP:
+		case NV_DEVICE_INFO_V0_PCIE:
+		default:
+			return EXIT_SUCCESS;
+	}
+	return EXIT_FAILURE;
+}
diff --git a/src/meson.build b/src/meson.build
index ab3a77d..da4267f 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,4 +1,4 @@
-deps = [glib, gio, gudev]
+deps = [glib, gio, gudev, libdrm, libdrm_nouveau, libdrm_amdgpu]
 
 sources = [
   'info-cleanup.c',
@@ -34,3 +34,5 @@ configure_file(
   configuration: switcherooctl_conf,
   install_dir: get_option('bindir')
 )
+
+subdir('discrete-detection')
diff --git a/src/switcheroo-control.c b/src/switcheroo-control.c
index abd8154..e407bfb 100644
--- a/src/switcheroo-control.c
+++ b/src/switcheroo-control.c
@@ -31,6 +31,7 @@ typedef struct {
 	char *name;
 	GPtrArray *env;
 	gboolean is_default;
+	gboolean is_discrete;
 } CardData;
 
 typedef struct {
@@ -94,6 +95,8 @@ build_gpus_variant (ControlData *data)
 				       g_variant_new_strv ((const gchar * const *) card->env->pdata, card->env->len));
 		g_variant_builder_add (&asv_builder, "{sv}", "Default",
 				       g_variant_new_boolean (card->is_default));
+		g_variant_builder_add (&asv_builder, "{sv}", "Discrete",
+				       g_variant_new_boolean (card->is_discrete));
 
 		g_variant_builder_add (&builder, "a{sv}", &asv_builder);
 	}
@@ -312,6 +315,18 @@ get_card_is_default (GUdevDevice *d)
 	return g_udev_device_get_sysfs_attr_as_boolean (parent, "boot_vga");
 }
 
+static gboolean
+get_card_is_discrete (GUdevDevice *d)
+{
+	const char * const * tags;
+	g_autoptr (GUdevDevice) platform_device = NULL;
+
+	tags = g_udev_device_get_tags (d);
+	if (tags && g_strv_contains (tags, "switcheroo-discrete-gpu"))
+		return TRUE;
+	return FALSE;
+}
+
 static CardData *
 get_card_data (GUdevClient *client,
 	       GUdevDevice *d)
@@ -328,6 +343,7 @@ get_card_data (GUdevClient *client,
 	data->name = get_card_name (d);
 	data->env = env;
 	data->is_default = get_card_is_default (d);
+	data->is_discrete = get_card_is_discrete (d);
 
 	return data;
 }
-- 
2.49.0


From 1b115ed72e03ff1169cbfddd79ef10890baca133 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Tue, 12 Sep 2023 15:53:40 +0200
Subject: [PATCH 03/13] tests: fix integration tests without UMockdev
 `gi.require_version` throws ValueError if the dependency cannot be found

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 tests/integration-test.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/integration-test.py b/tests/integration-test.py
index d8dea16..e3dd996 100755
--- a/tests/integration-test.py
+++ b/tests/integration-test.py
@@ -37,7 +37,7 @@ except ImportError as e:
 try:
     gi.require_version('UMockdev', '1.0')
     from gi.repository import UMockdev
-except ImportError:
+except (ImportError, ValueError):
     sys.stderr.write('Skipping tests, umockdev not available (https://github.com/martinpitt/umockdev)\n')
     sys.exit(0)
 
-- 
2.49.0


From d933e96bdb15679ae7653f929461982aa66973ba Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Tue, 12 Sep 2023 15:58:16 +0200
Subject: [PATCH 04/13] tests: add tests for discrete detection with mock libs
 Both tests have 4 different ways of testing: - Invalid Device - Unexpected
 Device - Non Discrete GPU (iGPU/APU) - Discrete GPU

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 src/discrete-detection/meson.build            |  4 +-
 tests/discrete-detection/libdrm_amdgpu_mock.c | 57 +++++++++++++
 .../discrete-detection/libdrm_nouveau_mock.c  | 68 ++++++++++++++++
 tests/discrete-detection/meson.build          | 80 +++++++++++++++++++
 tests/meson.build                             |  2 +
 5 files changed, 209 insertions(+), 2 deletions(-)
 create mode 100644 tests/discrete-detection/libdrm_amdgpu_mock.c
 create mode 100644 tests/discrete-detection/libdrm_nouveau_mock.c
 create mode 100644 tests/discrete-detection/meson.build

diff --git a/src/discrete-detection/meson.build b/src/discrete-detection/meson.build
index 3a6c03f..8eb8437 100644
--- a/src/discrete-detection/meson.build
+++ b/src/discrete-detection/meson.build
@@ -1,6 +1,6 @@
 
 if libdrm_amdgpu.found()
-  executable('check-discrete-amdgpu',
+  amdgpu_discrete = executable('check-discrete-amdgpu',
     files('amdgpu.c'),
     dependencies: deps,
     install: true,
@@ -9,7 +9,7 @@ if libdrm_amdgpu.found()
 endif
 
 if libdrm.found() and libdrm_nouveau.found()
-  executable('check-discrete-nouveau',
+  nouveau_discrete = executable('check-discrete-nouveau',
     files('nouveau.c'),
     dependencies: deps,
     install: true,
diff --git a/tests/discrete-detection/libdrm_amdgpu_mock.c b/tests/discrete-detection/libdrm_amdgpu_mock.c
new file mode 100644
index 0000000..20fa4aa
--- /dev/null
+++ b/tests/discrete-detection/libdrm_amdgpu_mock.c
@@ -0,0 +1,57 @@
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <amdgpu_drm.h>
+
+enum {
+	OTHER_GPU,
+	AMD_APU,
+	AMD_GPU,
+};
+
+/* Mock open(2) so we can test multiple devices configurations */
+int open(const char *pathname, int flags)
+{
+	if (!strcmp(pathname, "OTHER_GPU"))
+		return OTHER_GPU;
+	if (!strcmp (pathname, "AMD_APU"))
+		return AMD_APU;
+	if (!strcmp (pathname, "AMD_GPU"))
+		return AMD_GPU;
+
+	return -1;
+}
+
+/* open64 may be used for large file support */
+int open64(const char *pathname, int flags)
+{
+	return open (pathname, flags);
+}
+
+int amdgpu_device_initialize(int fd, uint32_t *major_version, uint32_t *minor_version, int *device_handle)
+{
+	// Store the fd in the device handle for access in query_info
+	*device_handle = fd;
+
+	if (fd != AMD_GPU && fd != AMD_APU)
+		return 1;
+
+	return 0;
+}
+
+int amdgpu_query_info(int device_handle, unsigned info_id, unsigned size, void *value)
+{
+	struct drm_amdgpu_info_device* device_info = value;
+
+	if (device_handle == AMD_GPU) {
+		device_info->ids_flags = 0;
+		return 0;
+	}
+	if (device_handle == AMD_APU) {
+		device_info->ids_flags = AMDGPU_IDS_FLAGS_FUSION;
+		return 0;
+	}
+
+	return 1;
+}
diff --git a/tests/discrete-detection/libdrm_nouveau_mock.c b/tests/discrete-detection/libdrm_nouveau_mock.c
new file mode 100644
index 0000000..cdbfda8
--- /dev/null
+++ b/tests/discrete-detection/libdrm_nouveau_mock.c
@@ -0,0 +1,68 @@
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#include <xf86drm.h>
+#include <nouveau_drm.h>
+#include <nouveau/nvif/ioctl.h>
+#include <nvif/cl0080.h>
+#include <nvif/class.h>
+
+enum {
+	OTHER_GPU,
+	NVIDIA_IGPU,
+	NVIDIA_GPU,
+};
+
+/* Mock open(2) so we can test multiple devices configurations */
+int open(const char *pathname, int flags)
+{
+	if (!strcmp(pathname, "OTHER_GPU"))
+		return OTHER_GPU;
+	if (!strcmp (pathname, "NVIDIA_IGPU"))
+		return NVIDIA_IGPU;
+	if (!strcmp (pathname, "NVIDIA_GPU"))
+		return NVIDIA_GPU;
+
+	return -1;
+}
+
+/* open64 may be used for large file support */
+int open64(const char *pathname, int flags)
+{
+	return open (pathname, flags);
+}
+
+int drmCommandWrite(int fd, unsigned long drmCommandIndex, void *data, unsigned long size)
+{
+	if (drmCommandIndex != DRM_NOUVEAU_NVIF)
+		return 1;
+
+	if (fd != NVIDIA_GPU && fd != NVIDIA_IGPU)
+		return 1;
+
+	return 0;
+}
+
+int drmCommandWriteRead(int fd, unsigned long drmCommandIndex, void *data, unsigned long size)
+{
+	if (drmCommandIndex != DRM_NOUVEAU_NVIF)
+		return 1;
+
+	struct {
+		struct nvif_ioctl_v0 ioctl;
+		struct nvif_ioctl_mthd_v0 mthd;
+		struct nv_device_info_v0 info;
+	} *args = data;
+
+	if (fd == NVIDIA_GPU) {
+		args->info.platform = NV_DEVICE_INFO_V0_PCIE;
+		return 0;
+	}
+	if (fd == NVIDIA_IGPU) {
+		args->info.platform = NV_DEVICE_INFO_V0_IGP;
+		return 0;	
+	}
+
+	return 1;
+}
\ No newline at end of file
diff --git a/tests/discrete-detection/meson.build b/tests/discrete-detection/meson.build
new file mode 100644
index 0000000..f01a014
--- /dev/null
+++ b/tests/discrete-detection/meson.build
@@ -0,0 +1,80 @@
+
+if libdrm_amdgpu.found()
+    amdgpu_mock_lib = shared_library(
+        'drm_amdgpu_mock',
+        files('libdrm_amdgpu_mock.c'),
+        dependencies: libdrm_amdgpu
+    )
+
+    test(
+        'test amdgpu detection with invalid device',
+        amdgpu_discrete,
+        args: ['NO_GPU'],
+        env: environment({'LD_PRELOAD': amdgpu_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test amdgpu detection with non-AMD GPU',
+        amdgpu_discrete,
+        args: ['OTHER_GPU'],
+        env: environment({'LD_PRELOAD': amdgpu_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test amdgpu detection with AMD APU',
+        amdgpu_discrete,
+        args: ['AMD_APU'],
+        env: environment({'LD_PRELOAD': amdgpu_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test amdgpu detection with AMD GPU',
+        amdgpu_discrete,
+        args: ['AMD_GPU'],
+        env: environment({'LD_PRELOAD': amdgpu_mock_lib.full_path()}),
+        should_fail: false
+    )
+endif
+
+if libdrm.found() and libdrm_nouveau.found()
+    nouveau_mock_lib = shared_library(
+        'drm_nouveau_mock',
+        files('libdrm_nouveau_mock.c'),
+        dependencies: [libdrm, libdrm_nouveau]
+    )
+
+    test(
+        'test nouveau detection with invalid device',
+        nouveau_discrete,
+        args: ['NO_GPU'],
+        env: environment({'LD_PRELOAD': nouveau_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test nouveau detection with non-Nvidia GPU',
+        nouveau_discrete,
+        args: ['OTHER_GPU'],
+        env: environment({'LD_PRELOAD': nouveau_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test nouveau detection with Nvidia iGPU',
+        nouveau_discrete,
+        args: ['NVIDIA_IGPU'],
+        env: environment({'LD_PRELOAD': nouveau_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test nouveau detection with Nvidia GPU',
+        nouveau_discrete,
+        args: ['NVIDIA_GPU'],
+        env: environment({'LD_PRELOAD': nouveau_mock_lib.full_path()}),
+        should_fail: false
+    )
+endif
\ No newline at end of file
diff --git a/tests/meson.build b/tests/meson.build
index b0b7476..61ef00c 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -15,3 +15,5 @@ foreach ut: unit_tests
          env: envs,
         )
 endforeach
+
+subdir('discrete-detection')
-- 
2.49.0


From c102b643945dc076d881497dd2ca5865938f7053 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Tue, 12 Sep 2023 15:57:47 +0200
Subject: [PATCH 05/13] main: remove leftover and fix typo

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 meson_options.txt        | 2 +-
 src/switcheroo-control.c | 1 -
 2 files changed, 1 insertion(+), 2 deletions(-)

diff --git a/meson_options.txt b/meson_options.txt
index b8d671a..c77fea8 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -13,7 +13,7 @@ option('hwdbdir',
 option('rulesdir',
   type: 'string',
   value: '',
-  description: 'Directory for ruke files',
+  description: 'Directory for rule files',
 )
 
 option('gtk_doc',
diff --git a/src/switcheroo-control.c b/src/switcheroo-control.c
index e407bfb..0f6a548 100644
--- a/src/switcheroo-control.c
+++ b/src/switcheroo-control.c
@@ -319,7 +319,6 @@ static gboolean
 get_card_is_discrete (GUdevDevice *d)
 {
 	const char * const * tags;
-	g_autoptr (GUdevDevice) platform_device = NULL;
 
 	tags = g_udev_device_get_tags (d);
 	if (tags && g_strv_contains (tags, "switcheroo-discrete-gpu"))
-- 
2.49.0


From f764db4eb565c19ba14155791fbfced3fb5d34c8 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Tue, 12 Sep 2023 15:58:27 +0200
Subject: [PATCH 06/13] main: move discrete dependencies out of main deps

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 src/discrete-detection/meson.build | 5 +++--
 src/meson.build                    | 2 +-
 2 files changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/discrete-detection/meson.build b/src/discrete-detection/meson.build
index 8eb8437..353316f 100644
--- a/src/discrete-detection/meson.build
+++ b/src/discrete-detection/meson.build
@@ -1,8 +1,9 @@
+discrete_deps = deps + [libdrm, libdrm_nouveau, libdrm_amdgpu]
 
 if libdrm_amdgpu.found()
   amdgpu_discrete = executable('check-discrete-amdgpu',
     files('amdgpu.c'),
-    dependencies: deps,
+    dependencies: discrete_deps,
     install: true,
     install_dir: libexecdir,
   )
@@ -11,7 +12,7 @@ endif
 if libdrm.found() and libdrm_nouveau.found()
   nouveau_discrete = executable('check-discrete-nouveau',
     files('nouveau.c'),
-    dependencies: deps,
+    dependencies: discrete_deps,
     install: true,
     install_dir: libexecdir,
   )
diff --git a/src/meson.build b/src/meson.build
index da4267f..22d69e7 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -1,4 +1,4 @@
-deps = [glib, gio, gudev, libdrm, libdrm_nouveau, libdrm_amdgpu]
+deps = [glib, gio, gudev]
 
 sources = [
   'info-cleanup.c',
-- 
2.49.0


From d2ecc29469d5572fd171926c9d1dbb1b851c7b09 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Tue, 12 Sep 2023 17:12:00 +0200
Subject: [PATCH 07/13] main: use glib for discrete command-line arguments

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 src/discrete-detection/amdgpu.c  | 29 ++++++++++++++++++++---------
 src/discrete-detection/nouveau.c | 29 ++++++++++++++++++++---------
 2 files changed, 40 insertions(+), 18 deletions(-)

diff --git a/src/discrete-detection/amdgpu.c b/src/discrete-detection/amdgpu.c
index 2d9804f..5a9a4ab 100644
--- a/src/discrete-detection/amdgpu.c
+++ b/src/discrete-detection/amdgpu.c
@@ -1,9 +1,10 @@
-
 #include <fcntl.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <locale.h>
 #include <gio/gio.h>
 #include <gudev/gudev.h>
+#include <glib.h>
 
 #include <amdgpu.h>
 #include <amdgpu_drm.h>
@@ -12,22 +13,32 @@ typedef int handle;
 G_DEFINE_AUTO_CLEANUP_FREE_FUNC(handle, close, -1)
 G_DEFINE_AUTOPTR_CLEANUP_FUNC(amdgpu_device_handle, free);
 
-int main(int argc, char** argv)
+int main (int argc, char** argv)
 {
-	if (argc < 2)
-	{
-		puts ("check-discrete-amdgpu [DEVNAME]");
-		return EXIT_FAILURE;
-	}
-
 	const char *devname;
 	g_auto(handle) fd = -1;
-	g_autoptr(GUdevDevice) parent = NULL;
 	struct drm_amdgpu_info_device device_info = {0};
 	amdgpu_device_handle device = NULL;
 	uint32_t drm_major, drm_minor;
+	g_autoptr(GOptionContext) option_context = NULL;
+	g_autoptr(GError) error = NULL;
 
+	setlocale (LC_ALL, "");
+	option_context = g_option_context_new ("");
+
+	if (!g_option_context_parse (option_context, &argc, &argv, &error)) {
+		g_print ("Failed to parse arguments: %s\n", error->message);
+		return EXIT_FAILURE;
+	}
+
+	if (argc < 2)
+	{
+		g_print ("%s\n", g_option_context_get_help (option_context, TRUE, NULL));
+		return EXIT_FAILURE;
+	}
 	devname = argv[1];
+;
+
 	fd = open (devname, O_RDWR);
 	if (fd < 0)
 		return EXIT_FAILURE;
diff --git a/src/discrete-detection/nouveau.c b/src/discrete-detection/nouveau.c
index 0a1f220..1d61cbb 100644
--- a/src/discrete-detection/nouveau.c
+++ b/src/discrete-detection/nouveau.c
@@ -1,9 +1,10 @@
-
 #include <fcntl.h>
 #include <stdlib.h>
 #include <stdio.h>
+#include <locale.h>
 #include <gio/gio.h>
 #include <gudev/gudev.h>
+#include <glib.h>
 
 #include <xf86drm.h>
 #include <nouveau_drm.h>
@@ -14,23 +15,34 @@
 typedef int handle;
 G_DEFINE_AUTO_CLEANUP_FREE_FUNC(handle, close, -1)
 
-int main(int argc, char** argv)
+int main (int argc, char** argv)
 {
+	const gchar *devname = NULL;
+	g_auto(handle) fd = -1;
+	g_autofree void *device = NULL;
+	g_autoptr(GOptionContext) option_context = NULL;
+	g_autoptr(GError) error = NULL;
+
+	setlocale (LC_ALL, "");
+	option_context = g_option_context_new ("");
+
+	if (!g_option_context_parse (option_context, &argc, &argv, &error)) {
+		g_print ("Failed to parse arguments: %s\n", error->message);
+		return EXIT_FAILURE;
+	}
+
 	if (argc < 2)
 	{
-		puts ("check-discrete-nouveau [DEVNAME]");
+		g_print ("%s\n", g_option_context_get_help (option_context, TRUE, NULL));
 		return EXIT_FAILURE;
 	}
-
-	const char *devname;
-	g_auto(handle) fd = -1;
-
 	devname = argv[1];
+
 	fd = open (devname, O_RDWR);
 	if (fd < 0)
 		return EXIT_FAILURE;
 
-	g_autofree void *device = malloc(352);
+	device = malloc(352);
 
 	/* Init device */
 	{	
@@ -88,7 +100,6 @@ int main(int argc, char** argv)
 	if (drmCommandWriteRead (fd, DRM_NOUVEAU_NVIF, &args, sizeof(args)))
 		return EXIT_FAILURE;
 
-
 	switch (args.info.platform)
 	{
 		case NV_DEVICE_INFO_V0_IGP:
-- 
2.49.0


From 462b09f02de37dfd2965d23cc7c4137bcf45a4ae Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Wed, 14 Feb 2024 20:25:42 +0100
Subject: [PATCH 08/13] main: add udev rule for i915 checking a lot of systems
 has shown that the intel iGPU will always be available at `0000:00:02.0`.
 Using ID_PATH would have been cleaner, but I couldn't get it to work.

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 data/30-discrete-gpu.rules.in | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/data/30-discrete-gpu.rules.in b/data/30-discrete-gpu.rules.in
index a803ed4..f30f315 100644
--- a/data/30-discrete-gpu.rules.in
+++ b/data/30-discrete-gpu.rules.in
@@ -1,3 +1,4 @@
 DRIVERS=="amdgpu", SUBSYSTEM=="drm", PROGRAM="@libexecdir@/check-discrete-amdgpu $env{DEVNAME}", TAG+="switcheroo-discrete-gpu"
 DRIVERS=="nouveau", SUBSYSTEM=="drm", PROGRAM="@libexecdir@/check-discrete-nouveau $env{DEVNAME}", TAG+="switcheroo-discrete-gpu"
-DRIVERS=="nvidia", TAG+="switcheroo-discrete-gpu"
+DRIVERS=="nvidia", SUBSYSTEM=="drm", TAG+="switcheroo-discrete-gpu"
+DRIVERS=="i915", SUBSYSTEM=="drm", DEVPATH!="/devices/pci0000:00/0000:00:02.0/drm/*", TAG+="switcheroo-discrete-gpu"
-- 
2.49.0


From 55db3aeaeb962952881f73e94432f750cfb64fc8 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Thu, 15 Feb 2024 16:24:00 +0100
Subject: [PATCH 09/13] main: use Discrete key in switcherooctl

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 src/switcherooctl.in | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/switcherooctl.in b/src/switcherooctl.in
index 96c21cc..c0e3f07 100755
--- a/src/switcherooctl.in
+++ b/src/switcherooctl.in
@@ -77,6 +77,7 @@ def print_gpu(gpu, index):
     print('Device:', index)
     print('  Name:       ', gpu['Name'])
     print('  Default:    ', "yes" if gpu['Default'] else "no")
+    print('  Discrete:   ', "yes" if gpu['Discrete'] else "no")
     print('  Environment:', env_to_str(gpu['Environment']))
 
 def _list():
@@ -126,7 +127,7 @@ def get_discrete_gpu():
         return None
 
     try:
-        gpu = next(gpu for gpu in gpus if not gpu['Default'])
+        gpu = next(gpu for gpu in gpus if gpu['Discrete'])
     except StopIteration:
         return None
     else:
-- 
2.49.0


From 4232c75fe41158bb5063d630d36b3ffd6a8a57ec Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Fri, 6 Sep 2024 22:31:56 +0200
Subject: [PATCH 10/13] main: use new GPU list on uevent the amount of GPUs may
 still be the same but underlying attributes may have changed On the ASUS TUF
 Dash F15 running Fedora 40 6.10.7-200.fc40.x86_64 the udev tags are not
 applied at the time switcheroo-control starts but at a later uevent they are
 correct. Memory gets allocated anyways to check if the GPU count has changed,
 so this shouldn't affect memory usage.

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 src/switcheroo-control.c | 16 ++++++----------
 1 file changed, 6 insertions(+), 10 deletions(-)

diff --git a/src/switcheroo-control.c b/src/switcheroo-control.c
index 0f6a548..01954c7 100644
--- a/src/switcheroo-control.c
+++ b/src/switcheroo-control.c
@@ -438,16 +438,12 @@ uevent_cb (GUdevClient *client,
 
 	cards = get_drm_cards (data);
 	num_gpus = cards->len;
-	if (num_gpus != data->num_gpus) {
-		g_debug ("GPUs added or removed (old: %d new: %d)",
-			 data->num_gpus, num_gpus);
-		g_ptr_array_free (data->cards, TRUE);
-		data->cards = cards;
-		data->num_gpus = cards->len;
-		send_dbus_event (data);
-	} else {
-		g_ptr_array_free (cards, TRUE);
-	}
+	g_debug ("GPUs updated (old: %d new: %d)",
+		 data->num_gpus, num_gpus);
+	g_ptr_array_free (data->cards, TRUE);
+	data->cards = cards;
+	data->num_gpus = cards->len;
+	send_dbus_event (data);
 }
 
 static void
-- 
2.49.0


From ad09f908cc9e56cccfc946b285f2e7ddd014c587 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Mon, 28 Apr 2025 21:41:47 +0200
Subject: [PATCH 11/13] main: add xe discrete detection

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 data/30-discrete-gpu.rules.in      |  1 +
 meson.build                        |  1 +
 meson_options.txt                  |  6 +++
 src/discrete-detection/meson.build |  9 +++++
 src/discrete-detection/xe.c        | 63 ++++++++++++++++++++++++++++++
 5 files changed, 80 insertions(+)
 create mode 100644 src/discrete-detection/xe.c

diff --git a/data/30-discrete-gpu.rules.in b/data/30-discrete-gpu.rules.in
index f30f315..ab60bf2 100644
--- a/data/30-discrete-gpu.rules.in
+++ b/data/30-discrete-gpu.rules.in
@@ -2,3 +2,4 @@ DRIVERS=="amdgpu", SUBSYSTEM=="drm", PROGRAM="@libexecdir@/check-discrete-amdgpu
 DRIVERS=="nouveau", SUBSYSTEM=="drm", PROGRAM="@libexecdir@/check-discrete-nouveau $env{DEVNAME}", TAG+="switcheroo-discrete-gpu"
 DRIVERS=="nvidia", SUBSYSTEM=="drm", TAG+="switcheroo-discrete-gpu"
 DRIVERS=="i915", SUBSYSTEM=="drm", DEVPATH!="/devices/pci0000:00/0000:00:02.0/drm/*", TAG+="switcheroo-discrete-gpu"
+DRIVERS=="xe", SUBSYSTEM=="drm", PROGRAM="@libexecdir@/check-discrete-xe $env{DEVNAME}", TAG+="switcheroo-discrete-gpu"
diff --git a/meson.build b/meson.build
index b3aaf0c..31dfa95 100644
--- a/meson.build
+++ b/meson.build
@@ -23,6 +23,7 @@ gudev = dependency('gudev-1.0', version: '>= 232')
 libdrm = dependency('libdrm', version: '>= 2.4.97', required: get_option('libdrm'))
 libdrm_nouveau = dependency('libdrm_nouveau', version: '>= 2.4.97', required: get_option('libdrm_nouveau'))
 libdrm_amdgpu = dependency('libdrm_amdgpu', version: '>= 2.4.97', required: get_option('libdrm_amdgpu'))
+drm_xe = cc.has_header('drm/xe_drm.h', required: get_option('drm_xe'))
 
 systemd_systemunitdir = get_option('systemdsystemunitdir')
 if systemd_systemunitdir == ''
diff --git a/meson_options.txt b/meson_options.txt
index c77fea8..058dbdd 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -45,3 +45,9 @@ option('libdrm_amdgpu',
   type: 'feature',
   value: 'auto'
 )
+
+option('drm_xe',
+  description: 'Whether the kernel provided xe drm header should be used to probe Intel GPUs',
+  type: 'feature',
+  value: 'auto'
+)
diff --git a/src/discrete-detection/meson.build b/src/discrete-detection/meson.build
index 353316f..7e0b324 100644
--- a/src/discrete-detection/meson.build
+++ b/src/discrete-detection/meson.build
@@ -17,3 +17,12 @@ if libdrm.found() and libdrm_nouveau.found()
     install_dir: libexecdir,
   )
 endif
+
+if drm_xe
+  xe_discrete = executable('check-discrete-xe',
+    files('xe.c'),
+    dependencies: deps,
+    install: true,
+    install_dir: libexecdir,
+  )
+endif
diff --git a/src/discrete-detection/xe.c b/src/discrete-detection/xe.c
new file mode 100644
index 0000000..5ee6ede
--- /dev/null
+++ b/src/discrete-detection/xe.c
@@ -0,0 +1,63 @@
+#include <fcntl.h>
+#include <stdint.h>
+#include <locale.h>
+#include <sys/ioctl.h>
+#include <gio/gio.h>
+
+#include <drm/xe_drm.h>
+
+typedef int handle;
+G_DEFINE_AUTO_CLEANUP_FREE_FUNC(handle, close, -1)
+
+int main (int argc, char** argv)
+{
+	const char *devname;
+	g_auto(handle) fd = -1;
+	g_autofree struct drm_xe_query_config *config = NULL;
+	g_autoptr(GOptionContext) option_context = NULL;
+	g_autoptr(GError) error = NULL;
+
+	setlocale (LC_ALL, "");
+	option_context = g_option_context_new ("");
+
+	if (!g_option_context_parse (option_context, &argc, &argv, &error)) {
+		g_print ("Failed to parse arguments: %s\n", error->message);
+		return EXIT_FAILURE;
+	}
+
+	if (argc < 2)
+	{
+		g_print ("%s\n", g_option_context_get_help (option_context, TRUE, NULL));
+		return EXIT_FAILURE;
+	}
+	devname = argv[1];
+
+	fd = open (devname, O_RDWR);
+	if (fd < 0)
+		return EXIT_FAILURE;
+
+	struct drm_xe_device_query query = {
+		.extensions = 0,
+		.query = DRM_XE_DEVICE_QUERY_CONFIG,
+		.size = 0,
+		.data = 0,
+	};
+
+	if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query))
+		return EXIT_FAILURE;
+
+	config = malloc(query.size);
+	if (!config)
+	   return EXIT_FAILURE;
+
+	query.data = (uintptr_t)config;
+	if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query))
+		return EXIT_FAILURE;
+
+	// if the GPU has dedicated vram then its discrrete
+	if (config->info[DRM_XE_QUERY_CONFIG_FLAGS] & DRM_XE_QUERY_CONFIG_FLAG_HAS_VRAM)
+		return EXIT_SUCCESS;
+
+	return EXIT_FAILURE;
+
+}
-- 
2.49.0


From cefec04bfc28a32bbec1e5a25999a768a6a7bf9c Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Mon, 28 Apr 2025 21:39:48 +0200
Subject: [PATCH 12/13] tests: fix switcherooctl output test

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 tests/integration-test.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tests/integration-test.py b/tests/integration-test.py
index e3dd996..5b4b4bb 100755
--- a/tests/integration-test.py
+++ b/tests/integration-test.py
@@ -512,7 +512,7 @@ class Tests(dbusmock.DBusTestCase):
 
         out = subprocess.run([tool_path], capture_output=True)
         self.assertEqual(out.returncode, 0, "'switcherooctl' call failed")
-        self.assertEqual(out.stdout, b'Device: 0\n  Name:        Intel\xc2\xae UHD Graphics 620 (Kabylake GT2)\n  Default:     yes\n  Environment: DRI_PRIME=pci-0000_00_02_0\n\nDevice: 1\n  Name:        GM108M [GeForce 930MX]\n  Default:     no\n  Environment: DRI_PRIME=pci-0000_01_00_0\n')
+        self.assertEqual(out.stdout, b'Device: 0\n  Name:        Intel\xc2\xae UHD Graphics 620 (Kabylake GT2)\n  Default:     yes\n  Discrete:    no\n  Environment: DRI_PRIME=pci-0000_00_02_0\n\nDevice: 1\n  Name:        GM108M [GeForce 930MX]\n  Default:     no\n  Discrete:    no\n  Environment: DRI_PRIME=pci-0000_01_00_0\n')
 
         out = subprocess.run([tool_path, 'launch', '--gpu', '0', 'env'], capture_output=True)
         self.assertEqual(out.returncode, 0, "'switcherooctl launch --gpu 0' failed")
-- 
2.49.0


From a446971f7aef77218625eb8627ec34e86e403737 Mon Sep 17 00:00:00 2001
From: Jan200101 <sentrycraft123@gmail.com>
Date: Mon, 28 Apr 2025 21:53:21 +0200
Subject: [PATCH 13/13] tests: add tests for xe discrete detection

Signed-off-by: Jan200101 <sentrycraft123@gmail.com>
---
 tests/discrete-detection/drm_xe_mock.c | 47 ++++++++++++++++++++++++++
 tests/discrete-detection/meson.build   | 42 ++++++++++++++++++++++-
 2 files changed, 88 insertions(+), 1 deletion(-)
 create mode 100644 tests/discrete-detection/drm_xe_mock.c

diff --git a/tests/discrete-detection/drm_xe_mock.c b/tests/discrete-detection/drm_xe_mock.c
new file mode 100644
index 0000000..a9da460
--- /dev/null
+++ b/tests/discrete-detection/drm_xe_mock.c
@@ -0,0 +1,47 @@
+#include <string.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <errno.h>
+
+#include <drm/xe_drm.h>
+enum {
+	OTHER_GPU,
+	INTEL_IGPU,
+	INTEL_GPU,
+};
+
+/* Mock open(2) so we can test multiple devices configurations */
+int open(const char *pathname, int flags)
+{
+	if (!strcmp(pathname, "OTHER_GPU"))
+		return OTHER_GPU;
+	if (!strcmp (pathname, "INTEL_IGPU"))
+		return INTEL_IGPU;
+	if (!strcmp (pathname, "INTEL_GPU"))
+		return INTEL_GPU;
+
+	return -1;
+}
+
+/* open64 may be used for large file support */
+int open64(const char *pathname, int flags)
+{
+	return open (pathname, flags);
+}
+
+int ioctl(int fd, unsigned long op, struct drm_xe_device_query* query)
+{
+	if (op != DRM_IOCTL_XE_DEVICE_QUERY)
+		return EINVAL;
+	if (fd == OTHER_GPU)
+		return ENODEV;
+
+	if (query->size == 0) {
+		query->size = sizeof(struct drm_xe_query_config);
+	} else {
+		struct drm_xe_query_config *config = (struct drm_xe_query_config*)query->data;
+		config->info[DRM_XE_QUERY_CONFIG_FLAGS] = fd == INTEL_GPU ? DRM_XE_QUERY_CONFIG_FLAG_HAS_VRAM : 0;
+	}
+
+	return 0;
+}
diff --git a/tests/discrete-detection/meson.build b/tests/discrete-detection/meson.build
index f01a014..998229f 100644
--- a/tests/discrete-detection/meson.build
+++ b/tests/discrete-detection/meson.build
@@ -77,4 +77,44 @@ if libdrm.found() and libdrm_nouveau.found()
         env: environment({'LD_PRELOAD': nouveau_mock_lib.full_path()}),
         should_fail: false
     )
-endif
\ No newline at end of file
+endif
+
+if drm_xe
+    xe_mock_lib = shared_library(
+        'drm_xe_mock',
+        files('drm_xe_mock.c')
+    )
+
+    test(
+        'test xe detection with invalid device',
+        xe_discrete,
+        args: ['NO_GPU'],
+        env: environment({'LD_PRELOAD': xe_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test xe detection with non-Intel GPU',
+        xe_discrete,
+        args: ['OTHER_GPU'],
+        env: environment({'LD_PRELOAD': xe_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test xe detection with Intel Integrated GPU',
+        xe_discrete,
+        args: ['INTEL_IGPU'],
+        env: environment({'LD_PRELOAD': xe_mock_lib.full_path()}),
+        should_fail: true
+    )
+
+    test(
+        'test xe detection with Intel Discrete GPU',
+        xe_discrete,
+        args: ['INTEL_GPU'],
+        env: environment({'LD_PRELOAD': xe_mock_lib.full_path()}),
+        should_fail: false
+    )
+
+endif
-- 
2.49.0

