summaryrefslogtreecommitdiffstats
path: root/src/detection/gpu
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/gpu')
-rw-r--r--src/detection/gpu/adl.h66
-rw-r--r--src/detection/gpu/asahi_drm.h1194
-rw-r--r--src/detection/gpu/d3dkmthk.h362
-rw-r--r--src/detection/gpu/gpu.c208
-rw-r--r--src/detection/gpu/gpu.h89
-rw-r--r--src/detection/gpu/gpu_amd.c460
-rw-r--r--src/detection/gpu/gpu_android.c55
-rw-r--r--src/detection/gpu/gpu_apple.c208
-rw-r--r--src/detection/gpu/gpu_apple.m87
-rw-r--r--src/detection/gpu/gpu_bsd.c230
-rw-r--r--src/detection/gpu/gpu_driver_specific.h90
-rw-r--r--src/detection/gpu/gpu_drm.c380
-rw-r--r--src/detection/gpu/gpu_gnu.c146
-rw-r--r--src/detection/gpu/gpu_haiku.c51
-rw-r--r--src/detection/gpu/gpu_intel.c231
-rw-r--r--src/detection/gpu/gpu_linux.c663
-rw-r--r--src/detection/gpu/gpu_mthreads.c201
-rw-r--r--src/detection/gpu/gpu_nbsd.c120
-rw-r--r--src/detection/gpu/gpu_nosupport.c6
-rw-r--r--src/detection/gpu/gpu_nvidia.c299
-rw-r--r--src/detection/gpu/gpu_obsd.c101
-rw-r--r--src/detection/gpu/gpu_pci.c251
-rw-r--r--src/detection/gpu/gpu_sunos.c54
-rw-r--r--src/detection/gpu/gpu_windows.c605
-rw-r--r--src/detection/gpu/gpu_windows.cpp83
-rw-r--r--src/detection/gpu/igcl.h216
-rw-r--r--src/detection/gpu/intel_drm.h125
-rw-r--r--src/detection/gpu/mtml.h110
-rw-r--r--src/detection/gpu/nvapi.h50
-rw-r--r--src/detection/gpu/nvml.h152
30 files changed, 6893 insertions, 0 deletions
diff --git a/src/detection/gpu/adl.h b/src/detection/gpu/adl.h
new file mode 100644
index 0000000..346eece
--- /dev/null
+++ b/src/detection/gpu/adl.h
@@ -0,0 +1,66 @@
+#pragma once
+
+#include "3rdparty/display-library/adl_sdk.h"
+
+// https://gpuopen-librariesandsdks.github.io/adl/modules.html
+
+// Function to initialize the ADL2 interface and to obtain client's context handle.
+extern int ADL2_Main_Control_Create(ADL_MAIN_MALLOC_CALLBACK callback, int iEnumConnectedAdapters, ADL_CONTEXT_HANDLE* context);
+
+// Destroy client's ADL context.
+extern int ADL2_Main_Control_Destroy(ADL_CONTEXT_HANDLE context);
+
+// Retrieves adapter information for given adapter or all OS-known adapters.
+// Return ADL_OK on success, DESPITE THE OFFICIAL DOCUMENT SAYS IT RETURNS 1 FOR SUCCESS!
+extern int ADL2_Adapter_AdapterInfoX3_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* numAdapters, AdapterInfo** lppAdapterInfo);
+
+// Function to retrieve Graphic Core Info.
+extern int ADL2_Adapter_Graphic_Core_Info_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLGraphicCoreInfo* pGraphicCoreInfo);
+
+// Function to retrieve memory information from the adapter. Version 2
+extern int ADL2_Adapter_MemoryInfo2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLMemoryInfo2* lpMemoryInfo2);
+
+// This function retrieves the Dedicated VRAM usage of given adapter.
+extern int ADL2_Adapter_DedicatedVRAMUsage_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* iVRAMUsageInMB);
+
+// Function to get the ASICFamilyType from the adapter.
+extern int ADL2_Adapter_ASICFamilyType_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* lpAsicTypes, int* lpValids);
+
+// Function to retrieve current power management capabilities.
+extern int ADL2_Overdrive_Caps(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* iSupported, int* iEnabled, int* iVersion);
+
+/////////// Overdrive 6 functions
+
+// Function to retrieve current Overdrive and performance-related activity.
+extern int ADL2_Overdrive6_CurrentStatus_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLOD6CurrentStatus* lpCurrentStatus);
+
+// Function to retrieve GPU temperature from the thermal controller.
+extern int ADL2_Overdrive6_Temperature_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int* lpTemperature);
+
+// Function to retrieve the current or default Overdrive clock ranges.
+extern int ADL2_Overdrive6_StateInfo_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int iStateType, ADLOD6StateInfo* lpStateInfo);
+
+/// Overdrive N functions
+
+// Despite the name (N means Next), this is actually Overdrive7 API
+// https://github.com/GPUOpen-LibrariesAndSDKs/display-library/blob/master/Sample/OverdriveN/OverdriveN.cpp#L209
+
+// Function to retrieve the OverdriveN capabilities.
+extern int ADL2_OverdriveN_CapabilitiesX2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNCapabilitiesX2* lpODCapabilities);
+
+// Function to retrieve the current OD performance status.
+extern int ADL2_OverdriveN_PerformanceStatus_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNPerformanceStatus* lpODPerformanceStatus);
+
+// Function to retrieve the current temperature.
+extern int ADL2_OverdriveN_Temperature_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, int iTemperatureType, int* iTemperature);
+
+// Function to retrieve the current GPU clocks settings.
+extern int ADL2_OverdriveN_SystemClocksX2_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLODNPerformanceLevelsX2* lpODPerformanceLevels);
+
+/// Overdrive 8 functions
+
+// Function to retrieve the Overdrive8 current settings.
+extern int ADL2_Overdrive8_Current_Setting_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLOD8CurrentSetting* lpCurrentSetting);
+
+// Function to retrieve the Overdrive8 current settings.
+extern int ADL2_New_QueryPMLogData_Get(ADL_CONTEXT_HANDLE context, int iAdapterIndex, ADLPMLogDataOutput* lpDataOutput);
diff --git a/src/detection/gpu/asahi_drm.h b/src/detection/gpu/asahi_drm.h
new file mode 100644
index 0000000..69b537b
--- /dev/null
+++ b/src/detection/gpu/asahi_drm.h
@@ -0,0 +1,1194 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright (C) The Asahi Linux Contributors
+ * Copyright (C) 2018-2023 Collabora Ltd.
+ * Copyright (C) 2014-2018 Broadcom
+ */
+#ifndef _ASAHI_DRM_H_
+#define _ASAHI_DRM_H_
+
+#include <drm.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * DOC: Introduction to the Asahi UAPI
+ *
+ * This documentation describes the Asahi IOCTLs.
+ *
+ * Just a few generic rules about the data passed to the Asahi IOCTLs (cribbed
+ * from Panthor):
+ *
+ * - Structures must be aligned on 64-bit/8-byte. If the object is not
+ * naturally aligned, a padding field must be added.
+ * - Fields must be explicitly aligned to their natural type alignment with
+ * pad[0..N] fields.
+ * - All padding fields will be checked by the driver to make sure they are
+ * zeroed.
+ * - Flags can be added, but not removed/replaced.
+ * - New fields can be added to the main structures (the structures
+ * directly passed to the ioctl). Those fields can be added at the end of
+ * the structure, or replace existing padding fields. Any new field being
+ * added must preserve the behavior that existed before those fields were
+ * added when a value of zero is passed.
+ * - New fields can be added to indirect objects (objects pointed by the
+ * main structure), iff those objects are passed a size to reflect the
+ * size known by the userspace driver (see
+ * drm_asahi_cmd_header::size).
+ * - If the kernel driver is too old to know some fields, those will be
+ * ignored if zero, and otherwise rejected (and so will be zero on output).
+ * - If userspace is too old to know some fields, those will be zeroed
+ * (input) before the structure is parsed by the kernel driver.
+ * - Each new flag/field addition must come with a driver version update so
+ * the userspace driver doesn't have to guess which flags are supported.
+ * - Structures should not contain unions, as this would defeat the
+ * extensibility of such structures.
+ * - IOCTLs can't be removed or replaced. New IOCTL IDs should be placed
+ * at the end of the drm_asahi_ioctl_id enum.
+ */
+
+/**
+ * enum drm_asahi_ioctl_id - IOCTL IDs
+ *
+ * Place new ioctls at the end, don't re-order, don't replace or remove entries.
+ *
+ * These IDs are not meant to be used directly. Use the DRM_IOCTL_ASAHI_xxx
+ * definitions instead.
+ */
+enum drm_asahi_ioctl_id {
+ /** @DRM_ASAHI_GET_PARAMS: Query device properties. */
+ DRM_ASAHI_GET_PARAMS = 0,
+
+ /** @DRM_ASAHI_GET_TIME: Query device time. */
+ DRM_ASAHI_GET_TIME,
+
+ /** @DRM_ASAHI_VM_CREATE: Create a GPU VM address space. */
+ DRM_ASAHI_VM_CREATE,
+
+ /** @DRM_ASAHI_VM_DESTROY: Destroy a VM. */
+ DRM_ASAHI_VM_DESTROY,
+
+ /** @DRM_ASAHI_VM_BIND: Bind/unbind memory to a VM. */
+ DRM_ASAHI_VM_BIND,
+
+ /** @DRM_ASAHI_GEM_CREATE: Create a buffer object. */
+ DRM_ASAHI_GEM_CREATE,
+
+ /**
+ * @DRM_ASAHI_GEM_MMAP_OFFSET: Get offset to pass to mmap() to map a
+ * given GEM handle.
+ */
+ DRM_ASAHI_GEM_MMAP_OFFSET,
+
+ /** @DRM_ASAHI_GEM_BIND_OBJECT: Bind memory as a special object */
+ DRM_ASAHI_GEM_BIND_OBJECT,
+
+ /** @DRM_ASAHI_QUEUE_CREATE: Create a scheduling queue. */
+ DRM_ASAHI_QUEUE_CREATE,
+
+ /** @DRM_ASAHI_QUEUE_DESTROY: Destroy a scheduling queue. */
+ DRM_ASAHI_QUEUE_DESTROY,
+
+ /** @DRM_ASAHI_SUBMIT: Submit commands to a queue. */
+ DRM_ASAHI_SUBMIT,
+};
+
+#define DRM_ASAHI_MAX_CLUSTERS 64
+
+/**
+ * struct drm_asahi_params_global - Global parameters.
+ *
+ * This struct may be queried by drm_asahi_get_params.
+ */
+struct drm_asahi_params_global {
+ /** @features: Feature bits from drm_asahi_feature */
+ __u64 features;
+
+ /** @gpu_generation: GPU generation, e.g. 13 for G13G */
+ __u32 gpu_generation;
+
+ /** @gpu_variant: GPU variant as a character, e.g. 'C' for G13C */
+ __u32 gpu_variant;
+
+ /**
+ * @gpu_revision: GPU revision in BCD, e.g. 0x00 for 'A0' or
+ * 0x21 for 'C1'
+ */
+ __u32 gpu_revision;
+
+ /** @chip_id: Chip ID in BCD, e.g. 0x8103 for T8103 */
+ __u32 chip_id;
+
+ /** @num_dies: Number of dies in the SoC */
+ __u32 num_dies;
+
+ /** @num_clusters_total: Number of GPU clusters (across all dies) */
+ __u32 num_clusters_total;
+
+ /**
+ * @num_cores_per_cluster: Number of logical cores per cluster
+ * (including inactive/nonexistent)
+ */
+ __u32 num_cores_per_cluster;
+
+ /** @max_frequency_khz: Maximum GPU core clock frequency */
+ __u32 max_frequency_khz;
+
+ /** @core_masks: Bitmask of present/enabled cores per cluster */
+ __u64 core_masks[DRM_ASAHI_MAX_CLUSTERS];
+
+ /**
+ * @vm_start: VM range start VMA. Together with @vm_end, this defines
+ * the window of valid GPU VAs. Userspace is expected to subdivide VAs
+ * out of this window.
+ *
+ * This window contains all virtual addresses that userspace needs to
+ * know about. There may be kernel-internal GPU VAs outside this range,
+ * but that detail is not relevant here.
+ */
+ __u64 vm_start;
+
+ /** @vm_end: VM range end VMA */
+ __u64 vm_end;
+
+ /**
+ * @vm_kernel_min_size: Minimum kernel VMA window size.
+ *
+ * When creating a VM, userspace is required to carve out a section of
+ * virtual addresses (within the range given by @vm_start and
+ * @vm_end). The kernel will allocate various internal structures
+ * within the specified VA range.
+ *
+ * Allowing userspace to choose the VA range for the kernel, rather than
+ * the kernel reserving VAs and requiring userspace to cope, can assist
+ * in implementing SVM.
+ */
+ __u64 vm_kernel_min_size;
+
+ /**
+ * @max_commands_per_submission: Maximum number of supported commands
+ * per submission. This mirrors firmware limits. Userspace must split up
+ * larger command buffers, which may require inserting additional
+ * synchronization.
+ */
+ __u32 max_commands_per_submission;
+
+ /**
+ * @max_attachments: Maximum number of drm_asahi_attachment's per
+ * command
+ */
+ __u32 max_attachments;
+
+ /**
+ * @command_timestamp_frequency_hz: Timebase frequency for timestamps
+ * written during command exeuction, specified via drm_asahi_timestamp
+ * structures. As this rate is controlled by the firmware, it is a
+ * queryable parameter.
+ *
+ * Userspace must divide by this frequency to convert timestamps to
+ * seconds, rather than hardcoding a particular firmware's rate.
+ */
+ __u64 command_timestamp_frequency_hz;
+};
+
+/**
+ * enum drm_asahi_feature - Feature bits
+ *
+ * This covers only features that userspace cannot infer from the architecture
+ * version. Most features don't need to be here.
+ */
+enum drm_asahi_feature {
+ /**
+ * @DRM_ASAHI_FEATURE_SOFT_FAULTS: GPU has "soft fault" enabled. Shader
+ * loads of unmapped memory will return zero. Shader stores to unmapped
+ * memory will be silently discarded. Note that only shader load/store
+ * is affected. Other hardware units are not affected, notably including
+ * texture sampling.
+ *
+ * Soft fault is set when initializing the GPU and cannot be runtime
+ * toggled. Therefore, it is exposed as a feature bit and not a
+ * userspace-settable flag on the VM. When soft fault is enabled,
+ * userspace can speculate memory accesses more aggressively.
+ */
+ DRM_ASAHI_FEATURE_SOFT_FAULTS = (1UL) << 0,
+};
+
+/**
+ * struct drm_asahi_get_params - Arguments passed to DRM_IOCTL_ASAHI_GET_PARAMS
+ */
+struct drm_asahi_get_params {
+ /** @param_group: Parameter group to fetch (MBZ) */
+ __u32 param_group;
+
+ /** @pad: MBZ */
+ __u32 pad;
+
+ /** @pointer: User pointer to write parameter struct */
+ __u64 pointer;
+
+ /**
+ * @size: Size of the user buffer. In case of older userspace, this may
+ * be less than sizeof(struct drm_asahi_params_global). The kernel will
+ * not write past the length specified here, allowing extensibility.
+ */
+ __u64 size;
+};
+
+/**
+ * struct drm_asahi_vm_create - Arguments passed to DRM_IOCTL_ASAHI_VM_CREATE
+ */
+struct drm_asahi_vm_create {
+ /**
+ * @kernel_start: Start of the kernel-reserved address range. See
+ * drm_asahi_params_global::vm_kernel_min_size.
+ *
+ * Both @kernel_start and @kernel_end must be within the range of
+ * valid VAs given by drm_asahi_params_global::vm_start and
+ * drm_asahi_params_global::vm_end. The size of the kernel range
+ * (@kernel_end - @kernel_start) must be at least
+ * drm_asahi_params_global::vm_kernel_min_size.
+ *
+ * Userspace must not bind any memory on this VM into this reserved
+ * range, it is for kernel use only.
+ */
+ __u64 kernel_start;
+
+ /**
+ * @kernel_end: End of the kernel-reserved address range. See
+ * @kernel_start.
+ */
+ __u64 kernel_end;
+
+ /** @vm_id: Returned VM ID */
+ __u32 vm_id;
+
+ /** @pad: MBZ */
+ __u32 pad;
+};
+
+/**
+ * struct drm_asahi_vm_destroy - Arguments passed to DRM_IOCTL_ASAHI_VM_DESTROY
+ */
+struct drm_asahi_vm_destroy {
+ /** @vm_id: VM ID to be destroyed */
+ __u32 vm_id;
+
+ /** @pad: MBZ */
+ __u32 pad;
+};
+
+/**
+ * enum drm_asahi_gem_flags - Flags for GEM creation
+ */
+enum drm_asahi_gem_flags {
+ /**
+ * @DRM_ASAHI_GEM_WRITEBACK: BO should be CPU-mapped as writeback.
+ *
+ * Map as writeback instead of write-combine. This optimizes for CPU
+ * reads.
+ */
+ DRM_ASAHI_GEM_WRITEBACK = (1L << 0),
+
+ /**
+ * @DRM_ASAHI_GEM_VM_PRIVATE: BO is private to this GPU VM (no exports).
+ */
+ DRM_ASAHI_GEM_VM_PRIVATE = (1L << 1),
+};
+
+/**
+ * struct drm_asahi_gem_create - Arguments passed to DRM_IOCTL_ASAHI_GEM_CREATE
+ */
+struct drm_asahi_gem_create {
+ /** @size: Size of the BO */
+ __u64 size;
+
+ /** @flags: Combination of drm_asahi_gem_flags flags. */
+ __u32 flags;
+
+ /**
+ * @vm_id: VM ID to assign to the BO, if DRM_ASAHI_GEM_VM_PRIVATE is set
+ */
+ __u32 vm_id;
+
+ /** @handle: Returned GEM handle for the BO */
+ __u32 handle;
+
+ /** @pad: MBZ */
+ __u32 pad;
+};
+
+/**
+ * struct drm_asahi_gem_mmap_offset - Arguments passed to
+ * DRM_IOCTL_ASAHI_GEM_MMAP_OFFSET
+ */
+struct drm_asahi_gem_mmap_offset {
+ /** @handle: Handle for the object being mapped. */
+ __u32 handle;
+
+ /** @flags: Must be zero */
+ __u32 flags;
+
+ /** @offset: The fake offset to use for subsequent mmap call */
+ __u64 offset;
+};
+
+/**
+ * enum drm_asahi_bind_flags - Flags for GEM binding
+ */
+enum drm_asahi_bind_flags {
+ /**
+ * @DRM_ASAHI_BIND_UNBIND: Instead of binding a GEM object to the range,
+ * simply unbind the GPU VMA range.
+ */
+ DRM_ASAHI_BIND_UNBIND = (1L << 0),
+
+ /** @DRM_ASAHI_BIND_READ: Map BO with GPU read permission */
+ DRM_ASAHI_BIND_READ = (1L << 1),
+
+ /** @DRM_ASAHI_BIND_WRITE: Map BO with GPU write permission */
+ DRM_ASAHI_BIND_WRITE = (1L << 2),
+
+ /**
+ * @DRM_ASAHI_BIND_SINGLE_PAGE: Map a single page of the BO repeatedly
+ * across the VA range.
+ *
+ * This is useful to fill a VA range with scratch pages or zero pages.
+ * It is intended as a mechanism to accelerate sparse.
+ */
+ DRM_ASAHI_BIND_SINGLE_PAGE = (1L << 3),
+};
+
+/**
+ * struct drm_asahi_gem_bind_op - Description of a single GEM bind operation.
+ */
+struct drm_asahi_gem_bind_op {
+ /** @flags: Combination of drm_asahi_bind_flags flags. */
+ __u32 flags;
+
+ /** @handle: GEM object to bind (except for UNBIND) */
+ __u32 handle;
+
+ /**
+ * @offset: Offset into the object (except for UNBIND).
+ *
+ * For a regular bind, this is the beginning of the region of the GEM
+ * object to bind.
+ *
+ * For a single-page bind, this is the offset to the single page that
+ * will be repeatedly bound.
+ *
+ * Must be page-size aligned.
+ */
+ __u64 offset;
+
+ /**
+ * @range: Number of bytes to bind/unbind to @addr.
+ *
+ * Must be page-size aligned.
+ */
+ __u64 range;
+
+ /**
+ * @addr: Address to bind to.
+ *
+ * Must be page-size aligned.
+ */
+ __u64 addr;
+};
+
+/**
+ * struct drm_asahi_vm_bind - Arguments passed to
+ * DRM_IOCTL_ASAHI_VM_BIND
+ */
+struct drm_asahi_vm_bind {
+ /** @vm_id: The ID of the VM to bind to */
+ __u32 vm_id;
+
+ /** @num_binds: number of binds in this IOCTL. */
+ __u32 num_binds;
+
+ /**
+ * @stride: Stride in bytes between consecutive binds. This allows
+ * extensibility of drm_asahi_gem_bind_op.
+ */
+ __u32 stride;
+
+ /** @pad: MBZ */
+ __u32 pad;
+
+ /**
+ * @userptr: User pointer to an array of @num_binds structures of type
+ * @drm_asahi_gem_bind_op and size @stride bytes.
+ */
+ __u64 userptr;
+};
+
+/**
+ * enum drm_asahi_bind_object_op - Special object bind operation
+ */
+enum drm_asahi_bind_object_op {
+ /** @DRM_ASAHI_BIND_OBJECT_OP_BIND: Bind a BO as a special GPU object */
+ DRM_ASAHI_BIND_OBJECT_OP_BIND = 0,
+
+ /** @DRM_ASAHI_BIND_OBJECT_OP_UNBIND: Unbind a special GPU object */
+ DRM_ASAHI_BIND_OBJECT_OP_UNBIND = 1,
+};
+
+/**
+ * enum drm_asahi_bind_object_flags - Special object bind flags
+ */
+enum drm_asahi_bind_object_flags {
+ /**
+ * @DRM_ASAHI_BIND_OBJECT_USAGE_TIMESTAMPS: Map a BO as a timestamp
+ * buffer.
+ */
+ DRM_ASAHI_BIND_OBJECT_USAGE_TIMESTAMPS = (1L << 0),
+};
+
+/**
+ * struct drm_asahi_gem_bind_object - Arguments passed to
+ * DRM_IOCTL_ASAHI_GEM_BIND_OBJECT
+ */
+struct drm_asahi_gem_bind_object {
+ /** @op: Bind operation (enum drm_asahi_bind_object_op) */
+ __u32 op;
+
+ /** @flags: Combination of drm_asahi_bind_object_flags flags. */
+ __u32 flags;
+
+ /** @handle: GEM object to bind/unbind (BIND) */
+ __u32 handle;
+
+ /** @vm_id: The ID of the VM to operate on (MBZ currently) */
+ __u32 vm_id;
+
+ /** @offset: Offset into the object (BIND only) */
+ __u64 offset;
+
+ /** @range: Number of bytes to bind/unbind (BIND only) */
+ __u64 range;
+
+ /** @object_handle: Object handle (out for BIND, in for UNBIND) */
+ __u32 object_handle;
+
+ /** @pad: MBZ */
+ __u32 pad;
+};
+
+/**
+ * enum drm_asahi_cmd_type - Command type
+ */
+enum drm_asahi_cmd_type {
+ /**
+ * @DRM_ASAHI_CMD_RENDER: Render command, executing on the render
+ * subqueue. Combined vertex and fragment operation.
+ *
+ * Followed by a @drm_asahi_cmd_render payload.
+ */
+ DRM_ASAHI_CMD_RENDER = 0,
+
+ /**
+ * @DRM_ASAHI_CMD_COMPUTE: Compute command on the compute subqueue.
+ *
+ * Followed by a @drm_asahi_cmd_compute payload.
+ */
+ DRM_ASAHI_CMD_COMPUTE = 1,
+
+ /**
+ * @DRM_ASAHI_SET_VERTEX_ATTACHMENTS: Software command to set
+ * attachments for subsequent vertex shaders in the same submit.
+ *
+ * Followed by (possibly multiple) @drm_asahi_attachment payloads.
+ */
+ DRM_ASAHI_SET_VERTEX_ATTACHMENTS = 2,
+
+ /**
+ * @DRM_ASAHI_SET_FRAGMENT_ATTACHMENTS: Software command to set
+ * attachments for subsequent fragment shaders in the same submit.
+ *
+ * Followed by (possibly multiple) @drm_asahi_attachment payloads.
+ */
+ DRM_ASAHI_SET_FRAGMENT_ATTACHMENTS = 3,
+
+ /**
+ * @DRM_ASAHI_SET_COMPUTE_ATTACHMENTS: Software command to set
+ * attachments for subsequent compute shaders in the same submit.
+ *
+ * Followed by (possibly multiple) @drm_asahi_attachment payloads.
+ */
+ DRM_ASAHI_SET_COMPUTE_ATTACHMENTS = 4,
+};
+
+/**
+ * enum drm_asahi_priority - Scheduling queue priority.
+ *
+ * These priorities are forwarded to the firmware to influence firmware
+ * scheduling. The exact policy is ultimately decided by firmware, but
+ * these enums allow userspace to communicate the intentions.
+ */
+enum drm_asahi_priority {
+ /** @DRM_ASAHI_PRIORITY_LOW: Low priority queue. */
+ DRM_ASAHI_PRIORITY_LOW = 0,
+
+ /** @DRM_ASAHI_PRIORITY_MEDIUM: Medium priority queue. */
+ DRM_ASAHI_PRIORITY_MEDIUM = 1,
+
+ /**
+ * @DRM_ASAHI_PRIORITY_HIGH: High priority queue.
+ *
+ * Reserved for future extension.
+ */
+ DRM_ASAHI_PRIORITY_HIGH = 2,
+
+ /**
+ * @DRM_ASAHI_PRIORITY_REALTIME: Real-time priority queue.
+ *
+ * Reserved for future extension.
+ */
+ DRM_ASAHI_PRIORITY_REALTIME = 3,
+};
+
+/**
+ * struct drm_asahi_queue_create - Arguments passed to
+ * DRM_IOCTL_ASAHI_QUEUE_CREATE
+ */
+struct drm_asahi_queue_create {
+ /** @flags: MBZ */
+ __u32 flags;
+
+ /** @vm_id: The ID of the VM this queue is bound to */
+ __u32 vm_id;
+
+ /** @priority: One of drm_asahi_priority */
+ __u32 priority;
+
+ /** @queue_id: The returned queue ID */
+ __u32 queue_id;
+
+ /**
+ * @usc_exec_base: GPU base address for all USC binaries (shaders) on
+ * this queue. USC addresses are 32-bit relative to this 64-bit base.
+ *
+ * This sets the following registers on all queue commands:
+ *
+ * USC_EXEC_BASE_TA (vertex)
+ * USC_EXEC_BASE_ISP (fragment)
+ * USC_EXEC_BASE_CP (compute)
+ *
+ * While the hardware lets us configure these independently per command,
+ * we do not have a use case for this. Instead, we expect userspace to
+ * fix a 4GiB VA carveout for USC memory and pass its base address here.
+ */
+ __u64 usc_exec_base;
+};
+
+/**
+ * struct drm_asahi_queue_destroy - Arguments passed to
+ * DRM_IOCTL_ASAHI_QUEUE_DESTROY
+ */
+struct drm_asahi_queue_destroy {
+ /** @queue_id: The queue ID to be destroyed */
+ __u32 queue_id;
+
+ /** @pad: MBZ */
+ __u32 pad;
+};
+
+/**
+ * enum drm_asahi_sync_type - Sync item type
+ */
+enum drm_asahi_sync_type {
+ /** @DRM_ASAHI_SYNC_SYNCOBJ: Binary sync object */
+ DRM_ASAHI_SYNC_SYNCOBJ = 0,
+
+ /** @DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ: Timeline sync object */
+ DRM_ASAHI_SYNC_TIMELINE_SYNCOBJ = 1,
+};
+
+/**
+ * struct drm_asahi_sync - Sync item
+ */
+struct drm_asahi_sync {
+ /** @sync_type: One of drm_asahi_sync_type */
+ __u32 sync_type;
+
+ /** @handle: The sync object handle */
+ __u32 handle;
+
+ /** @timeline_value: Timeline value for timeline sync objects */
+ __u64 timeline_value;
+};
+
+/**
+ * define DRM_ASAHI_BARRIER_NONE - Command index for no barrier
+ *
+ * This special value may be passed in to drm_asahi_command::vdm_barrier or
+ * drm_asahi_command::cdm_barrier to indicate that the respective subqueue
+ * should not wait on any previous work.
+ */
+#define DRM_ASAHI_BARRIER_NONE (0xFFFFu)
+
+/**
+ * struct drm_asahi_cmd_header - Top level command structure
+ *
+ * This struct is core to the command buffer definition and therefore is not
+ * extensible.
+ */
+struct drm_asahi_cmd_header {
+ /** @cmd_type: One of drm_asahi_cmd_type */
+ __u16 cmd_type;
+
+ /**
+ * @size: Size of this command, not including this header.
+ *
+ * For hardware commands, this enables extensibility of commands without
+ * requiring extra command types. Passing a command that is shorter
+ * than expected is explicitly allowed for backwards-compatibility.
+ * Truncated fields will be zeroed.
+ *
+ * For the synthetic attachment setting commands, this implicitly
+ * encodes the number of attachments. These commands take multiple
+ * fixed-size @drm_asahi_attachment structures as their payload, so size
+ * equals number of attachments * sizeof(struct drm_asahi_attachment).
+ */
+ __u16 size;
+
+ /**
+ * @vdm_barrier: VDM (render) command index to wait on.
+ *
+ * Barriers are indices relative to the beginning of a given submit. A
+ * barrier of 0 waits on commands submitted to the respective subqueue
+ * in previous submit ioctls. A barrier of N waits on N previous
+ * commands on the subqueue within the current submit ioctl. As a
+ * special case, passing @DRM_ASAHI_BARRIER_NONE avoids waiting on any
+ * commands in the subqueue.
+ *
+ * Examples:
+ *
+ * 0: This waits on all previous work.
+ *
+ * NONE: This does not wait for anything on this subqueue.
+ *
+ * 1: This waits on the first render command in the submit.
+ * This is valid only if there are multiple render commands in the
+ * same submit.
+ *
+ * Barriers are valid only for hardware commands. Synthetic software
+ * commands to set attachments must pass NONE here.
+ */
+ __u16 vdm_barrier;
+
+ /**
+ * @cdm_barrier: CDM (compute) command index to wait on.
+ *
+ * See @vdm_barrier, and replace VDM/render with CDM/compute.
+ */
+ __u16 cdm_barrier;
+};
+
+/**
+ * struct drm_asahi_submit - Arguments passed to DRM_IOCTL_ASAHI_SUBMIT
+ */
+struct drm_asahi_submit {
+ /**
+ * @syncs: An optional pointer to an array of drm_asahi_sync. The first
+ * @in_sync_count elements are in-syncs, then the remaining
+ * @out_sync_count elements are out-syncs. Using a single array with
+ * explicit partitioning simplifies handling.
+ */
+ __u64 syncs;
+
+ /**
+ * @cmdbuf: Pointer to the command buffer to submit.
+ *
+ * This is a flat command buffer. By design, it contains no CPU
+ * pointers, which makes it suitable for a virtgpu wire protocol without
+ * requiring any serializing/deserializing step.
+ *
+ * It consists of a series of commands. Each command begins with a
+ * fixed-size @drm_asahi_cmd_header header and is followed by a
+ * variable-length payload according to the type and size in the header.
+ *
+ * The combined count of "real" hardware commands must be nonzero and at
+ * most drm_asahi_params_global::max_commands_per_submission.
+ */
+ __u64 cmdbuf;
+
+ /** @flags: Flags for command submission (MBZ) */
+ __u32 flags;
+
+ /** @queue_id: The queue ID to be submitted to */
+ __u32 queue_id;
+
+ /**
+ * @in_sync_count: Number of sync objects to wait on before starting
+ * this job.
+ */
+ __u32 in_sync_count;
+
+ /**
+ * @out_sync_count: Number of sync objects to signal upon completion of
+ * this job.
+ */
+ __u32 out_sync_count;
+
+ /** @cmdbuf_size: Command buffer size in bytes */
+ __u32 cmdbuf_size;
+
+ /** @pad: MBZ */
+ __u32 pad;
+};
+
+/**
+ * struct drm_asahi_attachment - Describe an "attachment".
+ *
+ * Attachments are any memory written by shaders, notably including render
+ * target attachments written by the end-of-tile program. This is purely a hint
+ * about the accessed memory regions. It is optional to specify, which is
+ * fortunate as it cannot be specified precisely with bindless access anyway.
+ * But where possible, it's probably a good idea for userspace to include these
+ * hints, forwarded to the firmware.
+ *
+ * This struct is implicitly sized and therefore is not extensible.
+ */
+struct drm_asahi_attachment {
+ /** @pointer: Base address of the attachment */
+ __u64 pointer;
+
+ /** @size: Size of the attachment in bytes */
+ __u64 size;
+
+ /** @pad: MBZ */
+ __u32 pad;
+
+ /** @flags: MBZ */
+ __u32 flags;
+};
+
+enum drm_asahi_render_flags {
+ /**
+ * @DRM_ASAHI_RENDER_VERTEX_SCRATCH: A vertex stage shader uses scratch
+ * memory.
+ */
+ DRM_ASAHI_RENDER_VERTEX_SCRATCH = (1U << 0),
+
+ /**
+ * @DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES: Process even empty tiles.
+ * This must be set when clearing render targets.
+ */
+ DRM_ASAHI_RENDER_PROCESS_EMPTY_TILES = (1U << 1),
+
+ /**
+ * @DRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING: Run vertex stage on a single
+ * cluster (on multi-cluster GPUs)
+ *
+ * This harms performance but can workaround certain sync/coherency
+ * bugs, and therefore is useful for debugging.
+ */
+ DRM_ASAHI_RENDER_NO_VERTEX_CLUSTERING = (1U << 2),
+
+ /**
+ * @DRM_ASAHI_RENDER_DBIAS_IS_INT: Use integer depth bias formula.
+ *
+ * Graphics specifications contain two alternate formulas for depth
+ * bias, a float formula used with floating-point depth buffers and an
+ * integer formula using with unorm depth buffers. This flag specifies
+ * that the integer formula should be used. If omitted, the float
+ * formula is used instead.
+ *
+ * This corresponds to bit 18 of the relevant hardware control register,
+ * so we match that here for efficiency.
+ */
+ DRM_ASAHI_RENDER_DBIAS_IS_INT = (1U << 18),
+};
+
+/**
+ * struct drm_asahi_zls_buffer - Describe a depth or stencil buffer.
+ *
+ * These fields correspond to hardware registers in the ZLS (Z Load/Store) unit.
+ * There are three hardware registers for each field respectively for loads,
+ * stores, and partial renders. In practice, it makes sense to set all to the
+ * same values, except in exceptional cases not yet implemented in userspace, so
+ * we do not duplicate here for simplicity/efficiency.
+ *
+ * This struct is embedded in other structs and therefore is not extensible.
+ */
+struct drm_asahi_zls_buffer {
+ /** @base: Base address of the buffer */
+ __u64 base;
+
+ /**
+ * @comp_base: If the load buffer is compressed, address of the
+ * compression metadata section.
+ */
+ __u64 comp_base;
+
+ /**
+ * @stride: If layered rendering is enabled, the number of bytes
+ * between each layer of the buffer.
+ */
+ __u32 stride;
+
+ /**
+ * @comp_stride: If layered rendering is enabled, the number of bytes
+ * between each layer of the compression metadata.
+ */
+ __u32 comp_stride;
+};
+
+/**
+ * struct drm_asahi_timestamp - Describe a timestamp write.
+ *
+ * The firmware can optionally write the GPU timestamp at render pass
+ * granularities, but it needs to be mapped specially via
+ * DRM_IOCTL_ASAHI_GEM_BIND_OBJECT. This structure therefore describes where to
+ * write as a handle-offset pair, rather than a GPU address like normal.
+ *
+ * This struct is embedded in other structs and therefore is not extensible.
+ */
+struct drm_asahi_timestamp {
+ /**
+ * @handle: Handle of the timestamp buffer, or 0 to skip this
+ * timestamp. If nonzero, this must equal the value returned in
+ * drm_asahi_gem_bind_object::object_handle.
+ */
+ __u32 handle;
+
+ /** @offset: Offset to write into the timestamp buffer */
+ __u32 offset;
+};
+
+/**
+ * struct drm_asahi_timestamps - Describe timestamp writes.
+ *
+ * Each operation that can be timestamped, can be timestamped at the start and
+ * end. Therefore, drm_asahi_timestamp structs always come in pairs, bundled
+ * together into drm_asahi_timestamps.
+ *
+ * This struct is embedded in other structs and therefore is not extensible.
+ */
+struct drm_asahi_timestamps {
+ /** @start: Timestamp recorded at the start of the operation */
+ struct drm_asahi_timestamp start;
+
+ /** @end: Timestamp recorded at the end of the operation */
+ struct drm_asahi_timestamp end;
+};
+
+/**
+ * struct drm_asahi_helper_program - Describe helper program configuration.
+ *
+ * The helper program is a compute-like kernel required for various hardware
+ * functionality. Its most important role is dynamically allocating
+ * scratch/stack memory for individual subgroups, by partitioning a static
+ * allocation shared for the whole device. It is supplied by userspace via
+ * drm_asahi_helper_program and internally dispatched by the hardware as needed.
+ *
+ * This struct is embedded in other structs and therefore is not extensible.
+ */
+struct drm_asahi_helper_program {
+ /**
+ * @binary: USC address to the helper program binary. This is a tagged
+ * pointer with configuration in the bottom bits.
+ */
+ __u32 binary;
+
+ /** @cfg: Additional configuration bits for the helper program. */
+ __u32 cfg;
+
+ /**
+ * @data: Data passed to the helper program. This value is not
+ * interpreted by the kernel, firmware, or hardware in any way. It is
+ * simply a sideband for userspace, set with the submit ioctl and read
+ * via special registers inside the helper program.
+ *
+ * In practice, userspace will pass a 64-bit GPU VA here pointing to the
+ * actual arguments, which presumably don't fit in 64-bits.
+ */
+ __u64 data;
+};
+
+/**
+ * struct drm_asahi_bg_eot - Describe a background or end-of-tile program.
+ *
+ * The background and end-of-tile programs are dispatched by the hardware at the
+ * beginning and end of rendering. As the hardware "tilebuffer" is simply local
+ * memory, these programs are necessary to implement API-level render targets.
+ * The fragment-like background program is responsible for loading either the
+ * clear colour or the existing render target contents, while the compute-like
+ * end-of-tile program stores the tilebuffer contents to memory.
+ *
+ * This struct is embedded in other structs and therefore is not extensible.
+ */
+struct drm_asahi_bg_eot {
+ /**
+ * @usc: USC address of the hardware USC words binding resources
+ * (including images and uniforms) and the program itself. Note this is
+ * an additional layer of indirection compared to the helper program,
+ * avoiding the need for a sideband for data. This is a tagged pointer
+ * with additional configuration in the bottom bits.
+ */
+ __u32 usc;
+
+ /**
+ * @rsrc_spec: Resource specifier for the program. This is a packed
+ * hardware data structure describing the required number of registers,
+ * uniforms, bound textures, and bound samplers.
+ */
+ __u32 rsrc_spec;
+};
+
+/**
+ * struct drm_asahi_cmd_render - Command to submit 3D
+ *
+ * This command submits a single render pass. The hardware control stream may
+ * include many draws and subpasses, but within the command, the framebuffer
+ * dimensions and attachments are fixed.
+ *
+ * The hardware requires the firmware to set a large number of Control Registers
+ * setting up state at render pass granularity before each command rendering 3D.
+ * The firmware bundles this state into data structures. Unfortunately, we
+ * cannot expose either any of that directly to userspace, because the
+ * kernel-firmware ABI is not stable. Although we can guarantee the firmware
+ * updates in tandem with the kernel, we cannot break old userspace when
+ * upgrading the firmware and kernel. Therefore, we need to abstract well the
+ * data structures to avoid tying our hands with future firmwares.
+ *
+ * The bulk of drm_asahi_cmd_render therefore consists of values of hardware
+ * control registers, marshalled via the firmware interface.
+ *
+ * The framebuffer/tilebuffer dimensions are also specified here. In addition to
+ * being passed to the firmware/hardware, the kernel requires these dimensions
+ * to calculate various essential tiling-related data structures. It is
+ * unfortunate that our submits are heavier than on vendors with saner
+ * hardware-software interfaces. The upshot is all of this information is
+ * readily available to userspace with all current APIs.
+ *
+ * It looks odd - but it's not overly burdensome and it ensures we can remain
+ * compatible with old userspace.
+ */
+struct drm_asahi_cmd_render {
+ /** @flags: Combination of drm_asahi_render_flags flags. */
+ __u32 flags;
+
+ /**
+ * @isp_zls_pixels: ISP_ZLS_PIXELS register value. This contains the
+ * depth/stencil width/height, which may differ from the framebuffer
+ * width/height.
+ */
+ __u32 isp_zls_pixels;
+
+ /**
+ * @vdm_ctrl_stream_base: VDM_CTRL_STREAM_BASE register value. GPU
+ * address to the beginning of the VDM control stream.
+ */
+ __u64 vdm_ctrl_stream_base;
+
+ /** @vertex_helper: Helper program used for the vertex shader */
+ struct drm_asahi_helper_program vertex_helper;
+
+ /** @fragment_helper: Helper program used for the fragment shader */
+ struct drm_asahi_helper_program fragment_helper;
+
+ /**
+ * @isp_scissor_base: ISP_SCISSOR_BASE register value. GPU address of an
+ * array of scissor descriptors indexed in the render pass.
+ */
+ __u64 isp_scissor_base;
+
+ /**
+ * @isp_dbias_base: ISP_DBIAS_BASE register value. GPU address of an
+ * array of depth bias values indexed in the render pass.
+ */
+ __u64 isp_dbias_base;
+
+ /**
+ * @isp_oclqry_base: ISP_OCLQRY_BASE register value. GPU address of an
+ * array of occlusion query results written by the render pass.
+ */
+ __u64 isp_oclqry_base;
+
+ /** @depth: Depth buffer */
+ struct drm_asahi_zls_buffer depth;
+
+ /** @stencil: Stencil buffer */
+ struct drm_asahi_zls_buffer stencil;
+
+ /** @zls_ctrl: ZLS_CTRL register value */
+ __u64 zls_ctrl;
+
+ /** @ppp_multisamplectl: PPP_MULTISAMPLECTL register value */
+ __u64 ppp_multisamplectl;
+
+ /**
+ * @sampler_heap: Base address of the sampler heap. This heap is used
+ * for both vertex shaders and fragment shaders. The registers are
+ * per-stage, but there is no known use case for separate heaps.
+ */
+ __u64 sampler_heap;
+
+ /** @ppp_ctrl: PPP_CTRL register value */
+ __u32 ppp_ctrl;
+
+ /** @width_px: Framebuffer width in pixels */
+ __u16 width_px;
+
+ /** @height_px: Framebuffer height in pixels */
+ __u16 height_px;
+
+ /** @layers: Number of layers in the framebuffer */
+ __u16 layers;
+
+ /** @sampler_count: Number of samplers in the sampler heap. */
+ __u16 sampler_count;
+
+ /** @utile_width_px: Width of a logical tilebuffer tile in pixels */
+ __u8 utile_width_px;
+
+ /** @utile_height_px: Height of a logical tilebuffer tile in pixels */
+ __u8 utile_height_px;
+
+ /** @samples: # of samples in the framebuffer. Must be 1, 2, or 4. */
+ __u8 samples;
+
+ /** @sample_size_B: # of bytes in the tilebuffer required per sample. */
+ __u8 sample_size_B;
+
+ /**
+ * @isp_merge_upper_x: 32-bit float used in the hardware triangle
+ * merging. Calculate as: tan(60 deg) * width.
+ *
+ * Making these values UAPI avoids requiring floating-point calculations
+ * in the kernel in the hot path.
+ */
+ __u32 isp_merge_upper_x;
+
+ /**
+ * @isp_merge_upper_y: 32-bit float. Calculate as: tan(60 deg) * height.
+ * See @isp_merge_upper_x.
+ */
+ __u32 isp_merge_upper_y;
+
+ /** @bg: Background program run for each tile at the start */
+ struct drm_asahi_bg_eot bg;
+
+ /** @eot: End-of-tile program ran for each tile at the end */
+ struct drm_asahi_bg_eot eot;
+
+ /**
+ * @partial_bg: Background program ran at the start of each tile when
+ * resuming the render pass during a partial render.
+ */
+ struct drm_asahi_bg_eot partial_bg;
+
+ /**
+ * @partial_eot: End-of-tile program ran at the end of each tile when
+ * pausing the render pass during a partial render.
+ */
+ struct drm_asahi_bg_eot partial_eot;
+
+ /**
+ * @isp_bgobjdepth: ISP_BGOBJDEPTH register value. This is the depth
+ * buffer clear value, encoded in the depth buffer's format: either a
+ * 32-bit float or a 16-bit unorm (with upper bits zeroed).
+ */
+ __u32 isp_bgobjdepth;
+
+ /**
+ * @isp_bgobjvals: ISP_BGOBJVALS register value. The bottom 8-bits
+ * contain the stencil buffer clear value.
+ */
+ __u32 isp_bgobjvals;
+
+ /** @ts_vtx: Timestamps for the vertex portion of the render */
+ struct drm_asahi_timestamps ts_vtx;
+
+ /** @ts_frag: Timestamps for the fragment portion of the render */
+ struct drm_asahi_timestamps ts_frag;
+};
+
+/**
+ * struct drm_asahi_cmd_compute - Command to submit compute
+ *
+ * This command submits a control stream consisting of compute dispatches. There
+ * is essentially no limit on how many compute dispatches may be included in a
+ * single compute command, although timestamps are at command granularity.
+ */
+struct drm_asahi_cmd_compute {
+ /** @flags: MBZ */
+ __u32 flags;
+
+ /** @sampler_count: Number of samplers in the sampler heap. */
+ __u32 sampler_count;
+
+ /**
+ * @cdm_ctrl_stream_base: CDM_CTRL_STREAM_BASE register value. GPU
+ * address to the beginning of the CDM control stream.
+ */
+ __u64 cdm_ctrl_stream_base;
+
+ /**
+ * @cdm_ctrl_stream_end: GPU base address to the end of the hardware
+ * control stream. Note this only considers the first contiguous segment
+ * of the control stream, as the stream might jump elsewhere.
+ */
+ __u64 cdm_ctrl_stream_end;
+
+ /** @sampler_heap: Base address of the sampler heap. */
+ __u64 sampler_heap;
+
+ /** @helper: Helper program used for this compute command */
+ struct drm_asahi_helper_program helper;
+
+ /** @ts: Timestamps for the compute command */
+ struct drm_asahi_timestamps ts;
+};
+
+/**
+ * struct drm_asahi_get_time - Arguments passed to DRM_IOCTL_ASAHI_GET_TIME
+ */
+struct drm_asahi_get_time {
+ /** @flags: MBZ. */
+ __u64 flags;
+
+ /** @gpu_timestamp: On return, the GPU timestamp in nanoseconds. */
+ __u64 gpu_timestamp;
+};
+
+/**
+ * DRM_IOCTL_ASAHI() - Build an Asahi IOCTL number
+ * @__access: Access type. Must be R, W or RW.
+ * @__id: One of the DRM_ASAHI_xxx id.
+ * @__type: Suffix of the type being passed to the IOCTL.
+ *
+ * Don't use this macro directly, use the DRM_IOCTL_ASAHI_xxx
+ * values instead.
+ *
+ * Return: An IOCTL number to be passed to ioctl() from userspace.
+ */
+#define DRM_IOCTL_ASAHI(__access, __id, __type) \
+ DRM_IO##__access(DRM_COMMAND_BASE + DRM_ASAHI_##__id, \
+ struct drm_asahi_##__type)
+
+/* Note: this is an enum so that it can be resolved by Rust bindgen. */
+enum {
+ DRM_IOCTL_ASAHI_GET_PARAMS = DRM_IOCTL_ASAHI(W, GET_PARAMS, get_params),
+ DRM_IOCTL_ASAHI_GET_TIME = DRM_IOCTL_ASAHI(WR, GET_TIME, get_time),
+ DRM_IOCTL_ASAHI_VM_CREATE = DRM_IOCTL_ASAHI(WR, VM_CREATE, vm_create),
+ DRM_IOCTL_ASAHI_VM_DESTROY = DRM_IOCTL_ASAHI(W, VM_DESTROY, vm_destroy),
+ DRM_IOCTL_ASAHI_VM_BIND = DRM_IOCTL_ASAHI(W, VM_BIND, vm_bind),
+ DRM_IOCTL_ASAHI_GEM_CREATE = DRM_IOCTL_ASAHI(WR, GEM_CREATE, gem_create),
+ DRM_IOCTL_ASAHI_GEM_MMAP_OFFSET = DRM_IOCTL_ASAHI(WR, GEM_MMAP_OFFSET, gem_mmap_offset),
+ DRM_IOCTL_ASAHI_GEM_BIND_OBJECT = DRM_IOCTL_ASAHI(WR, GEM_BIND_OBJECT, gem_bind_object),
+ DRM_IOCTL_ASAHI_QUEUE_CREATE = DRM_IOCTL_ASAHI(WR, QUEUE_CREATE, queue_create),
+ DRM_IOCTL_ASAHI_QUEUE_DESTROY = DRM_IOCTL_ASAHI(W, QUEUE_DESTROY, queue_destroy),
+ DRM_IOCTL_ASAHI_SUBMIT = DRM_IOCTL_ASAHI(W, SUBMIT, submit),
+};
+
+#if defined(__cplusplus)
+}
+#endif
+
+#endif /* _ASAHI_DRM_H_ */
diff --git a/src/detection/gpu/d3dkmthk.h b/src/detection/gpu/d3dkmthk.h
new file mode 100644
index 0000000..da0087b
--- /dev/null
+++ b/src/detection/gpu/d3dkmthk.h
@@ -0,0 +1,362 @@
+#pragma once
+
+#include <stdint.h>
+#include <limits.h>
+#include <assert.h>
+#include <stdalign.h>
+
+#if _WIN32
+ #include <ntdef.h>
+ #include <windef.h>
+#else
+ #include <sys/ioctl.h>
+ #include <uchar.h>
+ #include <errno.h>
+
+typedef struct _LUID {
+ uint32_t LowPart;
+ uint32_t HighPart;
+} LUID;
+
+typedef uint32_t UINT;
+typedef uint64_t ULONGLONG;
+typedef uint64_t UINT64;
+typedef uint32_t UINT32;
+typedef uint16_t UINT16;
+typedef uint32_t ULONG;
+typedef uint16_t USHORT;
+typedef uint8_t UCHAR;
+typedef void VOID;
+typedef char16_t WCHAR;
+typedef void* HANDLE;
+typedef uint8_t BYTE;
+typedef int32_t BOOL;
+typedef uint8_t BOOLEAN;
+typedef union {
+ struct {
+ uint32_t LowPart;
+ int32_t HighPart;
+ } u;
+ int64_t QuadPart;
+} LARGE_INTEGER;
+typedef int32_t NTSTATUS; // 0 for success, -1 for failure
+ #define NT_SUCCESS(Status) ((NTSTATUS) (Status) >= 0)
+ #define _In_range_(low, hi)
+#endif
+
+#define D3DKMT_ALIGN64 alignas(8)
+
+typedef struct D3DKMT_HANDLE {
+ union {
+ struct {
+ uint32_t Instance : 6;
+ uint32_t Index : 24;
+ uint32_t Unique : 2;
+ };
+ uint32_t Value;
+ };
+} D3DKMT_HANDLE;
+
+typedef struct _D3DKMT_OPENADAPTERFROMLUID {
+ LUID AdapterLuid;
+ D3DKMT_HANDLE hAdapter;
+} D3DKMT_OPENADAPTERFROMLUID;
+
+typedef struct _D3DKMT_ADAPTERINFO {
+ D3DKMT_HANDLE hAdapter;
+ LUID AdapterLuid;
+ ULONG NumOfSources;
+ BOOL bPrecisePresentRegionsPreferred;
+} D3DKMT_ADAPTERINFO;
+
+#define MAX_ENUM_ADAPTERS 16
+typedef struct _D3DKMT_ENUMADAPTERS {
+ _In_range_(0, MAX_ENUM_ADAPTERS) ULONG NumAdapters;
+ D3DKMT_ADAPTERINFO Adapters[MAX_ENUM_ADAPTERS];
+} D3DKMT_ENUMADAPTERS;
+
+typedef struct _D3DKMT_ENUMADAPTERS2 {
+ ULONG NumAdapters; // in/out: On input, the count of the pAdapters array buffer. On output, the number of adapters enumerated.
+ D3DKMT_ADAPTERINFO* pAdapters; // out: Array of enumerated adapters containing NumAdapters elements
+} D3DKMT_ENUMADAPTERS2;
+static_assert(sizeof(D3DKMT_ENUMADAPTERS2) ==
+#if SIZE_MAX == UINT64_MAX
+ 0x10
+#else
+ 0x08
+#endif
+ ,
+ "D3DKMT_ENUMADAPTERS2 structure size mismatch");
+
+typedef struct _D3DKMT_ADAPTERREGISTRYINFO {
+ WCHAR AdapterString[260];
+ WCHAR BiosString[260];
+ WCHAR DacType[260];
+ WCHAR ChipType[260];
+} D3DKMT_ADAPTERREGISTRYINFO;
+
+typedef struct _D3DKMT_CLOSEADAPTER {
+ D3DKMT_HANDLE hAdapter; // in: adapter handle
+} D3DKMT_CLOSEADAPTER;
+
+typedef struct _D3DKMT_SEGMENTSIZEINFO {
+ D3DKMT_ALIGN64 ULONGLONG DedicatedVideoMemorySize;
+ D3DKMT_ALIGN64 ULONGLONG DedicatedSystemMemorySize;
+ D3DKMT_ALIGN64 ULONGLONG SharedSystemMemorySize;
+} D3DKMT_SEGMENTSIZEINFO;
+
+typedef struct _D3DKMT_ADAPTERTYPE {
+ union {
+ struct {
+ UINT RenderSupported : 1; // WDDM 1.2, Windows 8
+ UINT DisplaySupported : 1;
+ UINT SoftwareDevice : 1;
+ UINT PostDevice : 1;
+ UINT HybridDiscrete : 1; // WDDM 1.3, Windows 8.1
+ UINT HybridIntegrated : 1;
+ UINT IndirectDisplayDevice : 1;
+ UINT Paravirtualized : 1; // WDDM 2.3, Windows 10 Fall Creators Update (version 1709)
+ UINT ACGSupported : 1;
+ UINT SupportSetTimingsFromVidPn : 1;
+ UINT Detachable : 1;
+ UINT ComputeOnly : 1; // WDDM 2.6, Windows 10 May 2019 Update (Version 1903)
+ UINT Prototype : 1;
+ UINT RuntimePowerManagement : 1; // WDDM 2.9, Windows 10 Insider Preview "Iron"
+ UINT Reserved : 18;
+ };
+ UINT Value;
+ };
+} D3DKMT_ADAPTERTYPE;
+
+typedef struct _D3DKMT_ADAPTERADDRESS {
+ UINT BusNumber; // Bus number on which the physical device is located.
+ UINT DeviceNumber; // Index of the physical device on the bus.
+ UINT FunctionNumber; // Function number of the adapter on the physical device.
+} D3DKMT_ADAPTERADDRESS;
+
+typedef struct _D3DKMT_DEVICE_IDS {
+ UINT VendorID;
+ UINT DeviceID;
+ UINT SubVendorID;
+ UINT SubSystemID;
+ UINT RevisionID;
+ UINT BusType;
+} D3DKMT_DEVICE_IDS;
+
+typedef struct _D3DKMT_UMD_DRIVER_VERSION {
+ D3DKMT_ALIGN64 LARGE_INTEGER DriverVersion;
+} D3DKMT_UMD_DRIVER_VERSION;
+
+typedef struct _D3DKMT_QUERY_DEVICE_IDS {
+ UINT PhysicalAdapterIndex; // in:
+ D3DKMT_DEVICE_IDS DeviceIds; // out:
+} D3DKMT_QUERY_DEVICE_IDS;
+
+typedef enum _QAI_DRIVERVERSION {
+ KMT_DRIVERVERSION_WDDM_1_0 = 1000, // Windows Vista
+ KMT_DRIVERVERSION_WDDM_1_1_PRERELEASE = 1102, // Windows Vista with prereleased Win7 features
+ KMT_DRIVERVERSION_WDDM_1_1 = 1105, // Windows 7
+ KMT_DRIVERVERSION_WDDM_1_2 = 1200, // Windows 8
+ KMT_DRIVERVERSION_WDDM_1_3 = 1300, // Windows 8.1
+ KMT_DRIVERVERSION_WDDM_2_0 = 2000, // Windows 10
+ KMT_DRIVERVERSION_WDDM_2_1 = 2100, // Windows 10 (1607)
+ KMT_DRIVERVERSION_WDDM_2_2 = 2200, // Windows 10 (1703)
+ KMT_DRIVERVERSION_WDDM_2_3 = 2300, // Windows 10 (1709)
+ KMT_DRIVERVERSION_WDDM_2_4 = 2400, // Windows 10 (1803)
+ KMT_DRIVERVERSION_WDDM_2_5 = 2500, // Windows 10 (1809)
+ KMT_DRIVERVERSION_WDDM_2_6 = 2600, // Windows 10 (1903)
+ KMT_DRIVERVERSION_WDDM_2_7 = 2700, // Windows 10 (2004)
+ KMT_DRIVERVERSION_WDDM_2_8 = 2800, // Windows 11 Insider Preview Manganese
+ KMT_DRIVERVERSION_WDDM_2_9 = 2900, // Windows 11 Insider Preview Iron
+ KMT_DRIVERVERSION_WDDM_3_0 = 3000, // Windows 11 (21H2)
+ KMT_DRIVERVERSION_WDDM_3_1 = 3100, // Windows 11 (22H2)
+ KMT_DRIVERVERSION_WDDM_3_2 = 3200, // Windows 11 (24H2)
+} D3DKMT_DRIVERVERSION;
+
+typedef struct _D3DKMT_QUERY_ADAPTER_UNIQUE_GUID {
+ WCHAR AdapterUniqueGUID[40];
+} D3DKMT_QUERY_ADAPTER_UNIQUE_GUID;
+
+typedef enum _KMTQUERYADAPTERINFOTYPE {
+ KMTQAITYPE_GETSEGMENTSIZE = 3,
+ KMTQAITYPE_ADAPTERGUID = 4,
+ KMTQAITYPE_ADAPTERADDRESS = 6,
+ KMTQAITYPE_ADAPTERREGISTRYINFO = 8,
+ KMTQAITYPE_DRIVERVERSION = 13,
+ KMTQAITYPE_ADAPTERTYPE = 15, // WDDM 1.2, Windows 8
+ KMTQAITYPE_UMD_DRIVER_VERSION = 18,
+ KMTQAITYPE_NODEMETADATA = 25, // WDDM 2.0, Windows 10
+ KMTQAITYPE_PHYSICALADAPTERDEVICEIDS = 31,
+ KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID = 60, // WDDM 2.4, Windows 10 (1803)
+ KMTQAITYPE_NODEPERFDATA = 61,
+ KMTQAITYPE_ADAPTERPERFDATA = 62,
+} KMTQUERYADAPTERINFOTYPE;
+
+typedef struct _D3DKMT_QUERYADAPTERINFO {
+ D3DKMT_HANDLE hAdapter;
+ KMTQUERYADAPTERINFOTYPE Type;
+ VOID* pPrivateDriverData;
+ UINT PrivateDriverDataSize;
+} D3DKMT_QUERYADAPTERINFO;
+
+#define D3DKMT_MAX_SEGMENT_COUNT 32
+
+typedef enum _D3DKMT_MEMORY_SEGMENT_GROUP {
+ D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL = 0,
+ D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL = 1
+} D3DKMT_MEMORY_SEGMENT_GROUP;
+
+typedef enum _D3DKMT_QUERYSTATISTICS_TYPE {
+ D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE = 17, // WDDM 3.1, Windows 11 2022 Update (version 22H2)
+} D3DKMT_QUERYSTATISTICS_TYPE;
+
+typedef struct _D3DKMT_ADAPTER_PERFDATA {
+ UINT32 PhysicalAdapterIndex; // in: The physical adapter index, in an LDA chain
+ D3DKMT_ALIGN64 ULONGLONG MemoryFrequency; // out: Clock frequency of the memory in hertz
+ D3DKMT_ALIGN64 ULONGLONG MaxMemoryFrequency; // out: Max memory clock frequency
+ D3DKMT_ALIGN64 ULONGLONG MaxMemoryFrequencyOC; // out: Clock frequency of the memory while overclocked in hertz.
+ D3DKMT_ALIGN64 ULONGLONG MemoryBandwidth; // out: Amount of memory transferred in bytes
+ D3DKMT_ALIGN64 ULONGLONG PCIEBandwidth; // out: Amount of memory transferred over PCI-E in bytes
+ ULONG FanRPM; // out: Fan rpm
+ ULONG Power; // out: Power draw of the adapter in tenths of a percentage
+ ULONG Temperature; // out: Temperature in deci-Celsius 1 = 0.1C
+ UCHAR PowerStateOverride; // out: Overrides dxgkrnls power view of linked adapters.
+} D3DKMT_ADAPTER_PERFDATA;
+
+typedef struct _D3DKMT_NODE_PERFDATA {
+ UINT32 NodeOrdinal; // in: Node ordinal of the requested engine.
+ UINT32 PhysicalAdapterIndex; // in: The physical adapter index, in an LDA chain
+ D3DKMT_ALIGN64 ULONGLONG Frequency; // out: Clock frequency of the engine in hertz
+ D3DKMT_ALIGN64 ULONGLONG MaxFrequency; // out: Max engine clock frequency
+ D3DKMT_ALIGN64 ULONGLONG MaxFrequencyOC; // out: Max engine over clock frequency
+ ULONG Voltage; // out: Voltage of the engine in milli volts mV
+ ULONG VoltageMax; // out: Max voltage levels in milli volts.
+ ULONG VoltageMaxOC; // out: Max voltage level while overclocked in milli volts.
+ // WDDM 2.5
+ D3DKMT_ALIGN64 ULONGLONG MaxTransitionLatency; // out: Max transition latency to change the frequency in 100 nanoseconds
+} D3DKMT_NODE_PERFDATA;
+
+typedef struct _D3DKMT_QUERYSTATISTICS_MEMORY_USAGE {
+ D3DKMT_ALIGN64 UINT64 AllocatedBytes;
+ D3DKMT_ALIGN64 UINT64 FreeBytes;
+ D3DKMT_ALIGN64 UINT64 ZeroBytes;
+ D3DKMT_ALIGN64 UINT64 ModifiedBytes;
+ D3DKMT_ALIGN64 UINT64 StandbyBytes;
+} D3DKMT_QUERYSTATISTICS_MEMORY_USAGE;
+
+typedef union _D3DKMT_QUERYSTATISTICS_RESULT {
+ D3DKMT_QUERYSTATISTICS_MEMORY_USAGE SegmentGroupUsageInformation;
+ uint8_t Padding[776];
+} D3DKMT_QUERYSTATISTICS_RESULT;
+
+typedef struct _D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT_GROUP_USAGE {
+ UINT16 PhysicalAdapterIndex;
+ UINT16 SegmentGroup; // D3DKMT_MEMORY_SEGMENT_GROUP
+} D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT_GROUP_USAGE;
+
+typedef struct _D3DKMT_QUERYSTATISTICS {
+ D3DKMT_QUERYSTATISTICS_TYPE Type; // in: type of data requested
+ LUID AdapterLuid; // in: adapter to get export / statistics from
+ HANDLE* hProcess; // in: process to get statistics for, if required for this query type
+ D3DKMT_QUERYSTATISTICS_RESULT QueryResult; // out: requested data
+
+ union {
+ D3DKMT_QUERYSTATISTICS_QUERY_SEGMENT_GROUP_USAGE QuerySegmentGroupUsage;
+ };
+} D3DKMT_QUERYSTATISTICS;
+static_assert(sizeof(D3DKMT_QUERYSTATISTICS) ==
+#if SIZE_MAX == UINT64_MAX
+ 0x328
+#else
+ 0x320
+#endif
+ ,
+ "D3DKMT_QUERYSTATISTICS structure size mismatch");
+
+#define DXGK_MAX_METADATA_NAME_LENGTH 32
+typedef enum {
+ DXGK_ENGINE_TYPE_OTHER,
+ DXGK_ENGINE_TYPE_3D,
+ DXGK_ENGINE_TYPE_VIDEO_DECODE,
+ DXGK_ENGINE_TYPE_VIDEO_ENCODE,
+ DXGK_ENGINE_TYPE_VIDEO_PROCESSING,
+ DXGK_ENGINE_TYPE_SCENE_ASSEMBLY,
+ DXGK_ENGINE_TYPE_COPY,
+ DXGK_ENGINE_TYPE_OVERLAY,
+ DXGK_ENGINE_TYPE_CRYPTO,
+ DXGK_ENGINE_TYPE_VIDEO_CODEC,
+ DXGK_ENGINE_TYPE_MAX
+} DXGK_ENGINE_TYPE;
+
+typedef struct _DXGK_NODEMETADATA_FLAGS {
+ union {
+ struct {
+ UINT ContextSchedulingSupported : 1; // WDDM 2.2
+ UINT RingBufferFenceRelease : 1; // WDDM 2.5
+ UINT SupportTrackedWorkload : 1;
+ UINT UserModeSubmission : 1;
+ UINT SupportBuildTestCommandBuffer : 1; // WDDM 3.2
+ UINT Reserved : 11;
+ UINT MaxInFlightHwQueueBuffers : 16;
+ };
+ UINT32 Value;
+ };
+} DXGK_NODEMETADATA_FLAGS;
+
+typedef struct _DXGK_NODEMETADATA {
+ DXGK_ENGINE_TYPE EngineType;
+ WCHAR FriendlyName[DXGK_MAX_METADATA_NAME_LENGTH];
+ DXGK_NODEMETADATA_FLAGS Flags; // WDDM 2.2
+ BOOLEAN GpuMmuSupported; // WDDM 2.0 ???
+ BOOLEAN IoMmuSupported;
+} FF_A_PACKED DXGK_NODEMETADATA;
+
+typedef struct _D3DKMT_NODEMETADATA {
+ UINT NodeOrdinalAndAdapterIndex; // WDDMv2: High word is physical adapter index, low word is node ordinal
+ DXGK_NODEMETADATA NodeData;
+} FF_A_PACKED D3DKMT_NODEMETADATA;
+static_assert(sizeof(D3DKMT_NODEMETADATA) == 0x4E, "D3DKMT_NODEMETADATA structure size mismatch");
+
+// Functions
+
+#if _WIN32
+
+EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTOpenAdapterFromLuid(_Inout_ CONST D3DKMT_OPENADAPTERFROMLUID*);
+EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryAdapterInfo(_Inout_ CONST D3DKMT_QUERYADAPTERINFO*);
+EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTCloseAdapter(_In_ CONST D3DKMT_CLOSEADAPTER*);
+EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTEnumAdapters(_Inout_ CONST D3DKMT_ENUMADAPTERS*);
+EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTEnumAdapters2(_Inout_ CONST D3DKMT_ENUMADAPTERS2*);
+EXTERN_C _Check_return_ NTSTATUS APIENTRY D3DKMTQueryStatistics(_In_ CONST D3DKMT_QUERYSTATISTICS*);
+
+#else
+
+ // Ref: https://github.com/microsoft/WSL2-Linux-Kernel/blob/linux-msft-wsl-6.6.y/include/uapi/misc/d3dkmthk.h
+ #define LX_DXOPENADAPTERFROMLUID _IOWR(0x47, 0x01, D3DKMT_OPENADAPTERFROMLUID)
+ #define LX_DXQUERYADAPTERINFO _IOWR(0x47, 0x09, D3DKMT_QUERYADAPTERINFO)
+ #define LX_DXENUMADAPTERS2 _IOWR(0x47, 0x14, D3DKMT_ENUMADAPTERS2)
+ #define LX_DXCLOSEADAPTER _IOWR(0x47, 0x15, D3DKMT_CLOSEADAPTER)
+ #define LX_DXQUERYSTATISTICS _IOWR(0x47, 0x43, D3DKMT_QUERYSTATISTICS)
+
+extern int dxgfd; // File descriptor for /dev/dxg, initialized in gpu_wsl.c
+
+static inline NTSTATUS D3DKMTOpenAdapterFromLuid(const D3DKMT_OPENADAPTERFROMLUID* params) {
+ return ioctl(dxgfd, LX_DXOPENADAPTERFROMLUID, params) < 0 ? -errno : 0;
+}
+
+static inline NTSTATUS D3DKMTQueryAdapterInfo(const D3DKMT_QUERYADAPTERINFO* params) {
+ return ioctl(dxgfd, LX_DXQUERYADAPTERINFO, params) < 0 ? -errno : 0;
+}
+
+static inline NTSTATUS D3DKMTCloseAdapter(const D3DKMT_CLOSEADAPTER* params) {
+ return ioctl(dxgfd, LX_DXCLOSEADAPTER, params) < 0 ? -errno : 0;
+}
+
+static inline NTSTATUS D3DKMTEnumAdapters2(D3DKMT_ENUMADAPTERS2* params) {
+ return ioctl(dxgfd, LX_DXENUMADAPTERS2, params) < 0 ? -errno : 0;
+}
+
+static inline NTSTATUS D3DKMTQueryStatistics(const D3DKMT_QUERYSTATISTICS* params) {
+ return ioctl(dxgfd, LX_DXQUERYSTATISTICS, params) < 0 ? -errno : 0;
+}
+
+#endif
diff --git a/src/detection/gpu/gpu.c b/src/detection/gpu/gpu.c
new file mode 100644
index 0000000..a287298
--- /dev/null
+++ b/src/detection/gpu/gpu.c
@@ -0,0 +1,208 @@
+#include "gpu.h"
+#include "common/debug.h"
+#include "detection/vulkan/vulkan.h"
+#include "detection/opencl/opencl.h"
+#include "detection/opengl/opengl.h"
+#include "modules/opengl/opengl.h"
+
+const char* FF_GPU_VENDOR_NAME_APPLE = "Apple";
+const char* FF_GPU_VENDOR_NAME_AMD = "AMD";
+const char* FF_GPU_VENDOR_NAME_INTEL = "Intel";
+const char* FF_GPU_VENDOR_NAME_NVIDIA = "NVIDIA";
+const char* FF_GPU_VENDOR_NAME_MTHREADS = "Moore Threads";
+const char* FF_GPU_VENDOR_NAME_QUALCOMM = "Qualcomm";
+const char* FF_GPU_VENDOR_NAME_MTK = "MTK";
+const char* FF_GPU_VENDOR_NAME_VMWARE = "VMware";
+const char* FF_GPU_VENDOR_NAME_PARALLELS = "Parallels";
+const char* FF_GPU_VENDOR_NAME_MICROSOFT = "Microsoft";
+const char* FF_GPU_VENDOR_NAME_REDHAT = "RedHat";
+const char* FF_GPU_VENDOR_NAME_ORACLE = "Oracle";
+const char* FF_GPU_VENDOR_NAME_BROADCOM = "Broadcom";
+const char* FF_GPU_VENDOR_NAME_LOONGSON = "Loongson";
+const char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO = "Jingjia Micro";
+const char* FF_GPU_VENDOR_NAME_HUAWEI = "Huawei";
+const char* FF_GPU_VENDOR_NAME_ZHAOXIN = "Zhaoxin";
+const char* FF_GPU_VENDOR_NAME_QEMU = "QEMU";
+
+const char* ffGPUGetVendorString(unsigned vendorId) {
+ // https://devicehunt.com/all-pci-vendors
+ switch (vendorId) {
+ case 0x106b:
+ return FF_GPU_VENDOR_NAME_APPLE;
+ case 0x1002:
+ case 0x1022:
+ case 0x1dd8:
+ return FF_GPU_VENDOR_NAME_AMD;
+ case 0x8086:
+ case 0x8087:
+ case 0x03e7:
+ return FF_GPU_VENDOR_NAME_INTEL;
+ case 0x0955:
+ case 0x10de:
+ case 0x12d2:
+ return FF_GPU_VENDOR_NAME_NVIDIA;
+ case 0x1ed5:
+ return FF_GPU_VENDOR_NAME_MTHREADS;
+ case 0x17cb:
+ case 0x5143:
+ return FF_GPU_VENDOR_NAME_QUALCOMM;
+ case 0x14c3:
+ return FF_GPU_VENDOR_NAME_MTK;
+ case 0x15ad:
+ return FF_GPU_VENDOR_NAME_VMWARE;
+ case 0x1af4:
+ return FF_GPU_VENDOR_NAME_REDHAT;
+ case 0x1ab8:
+ case 0x05404c42: // PD
+ return FF_GPU_VENDOR_NAME_PARALLELS;
+ case 0x1414:
+ return FF_GPU_VENDOR_NAME_MICROSOFT;
+ case 0x108e:
+ return FF_GPU_VENDOR_NAME_ORACLE;
+ case 0x182f:
+ case 0x14e4:
+ return FF_GPU_VENDOR_NAME_BROADCOM;
+ case 0x0014:
+ return FF_GPU_VENDOR_NAME_LOONGSON;
+ case 0x0731:
+ return FF_GPU_VENDOR_NAME_JINGJIA_MICRO;
+ case 0x19e5:
+ return FF_GPU_VENDOR_NAME_HUAWEI;
+ case 0x1d17:
+ return FF_GPU_VENDOR_NAME_ZHAOXIN;
+ case 0x1234: // https://admin.pci-ids.ucw.cz/read/PC/1234
+ return FF_GPU_VENDOR_NAME_QEMU;
+ default:
+ return NULL;
+ }
+}
+
+const char* detectByOpenGL(FFlist* gpus) {
+ FF_DEBUG("Starting OpenGL GPU detection fallback");
+
+ FFOpenGLResult result;
+ ffStrbufInit(&result.version);
+ ffStrbufInit(&result.renderer);
+ ffStrbufInit(&result.vendor);
+ ffStrbufInit(&result.slv);
+ ffStrbufInit(&result.library);
+
+ FF_A_CLEANUP(ffDestroyOpenGLOptions) FFOpenGLOptions options;
+ ffInitOpenGLOptions(&options);
+ const char* error = ffDetectOpenGL(&options, &result);
+ FF_DEBUG("OpenGL detection returns: %s", error ?: "success");
+
+ if (!error) {
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ ffStrbufInitMove(&gpu->vendor, &result.vendor);
+ ffStrbufInitMove(&gpu->name, &result.renderer);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInitF(&gpu->platformApi, "OpenGL %s", result.version.chars);
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->dedicated = gpu->shared = (FFGPUMemory) { 0, 0 };
+ gpu->deviceId = 0;
+
+ FF_DEBUG("OpenGL reported renderer='%s', vendor='%s', version='%s'",
+ gpu->name.chars,
+ gpu->vendor.chars,
+ result.version.chars);
+
+ if (ffStrbufContainS(&gpu->name, "Apple")) {
+ ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE);
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+ } else if (ffStrbufContainS(&gpu->name, "Intel")) {
+ ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_INTEL);
+ } else if (ffStrbufContainS(&gpu->name, "AMD") || ffStrbufContainS(&gpu->name, "ATI")) {
+ ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_AMD);
+ } else if (ffStrbufContainS(&gpu->name, "NVIDIA")) {
+ ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_NVIDIA);
+ } else if (ffStrbufContainS(&gpu->name, "MTT")) {
+ ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_MTHREADS);
+ }
+
+ FF_DEBUG("OpenGL fallback produced GPU: name='%s', vendor='%s', type=%u",
+ gpu->name.chars,
+ gpu->vendor.chars,
+ gpu->type);
+ }
+
+ ffStrbufDestroy(&result.version);
+ ffStrbufDestroy(&result.renderer);
+ ffStrbufDestroy(&result.vendor);
+ ffStrbufDestroy(&result.slv);
+ ffStrbufDestroy(&result.library);
+ return error;
+}
+
+const char* ffDetectGPU(const FFGPUOptions* options, FFlist* result) {
+ FF_DEBUG("Starting GPU detection with method=%d", (int) options->detectionMethod);
+
+ if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_PCI) {
+ FF_DEBUG("Trying PCI/native GPU detection");
+ const char* error = ffDetectGPUImpl(options, result);
+ if (!error && result->length > 0) {
+ FF_DEBUG("PCI/native GPU detection succeeded with %u GPU(s)", result->length);
+ return NULL;
+ }
+
+ FF_DEBUG("PCI/native GPU detection did not produce results (error=%s, gpuCount=%u)",
+ error ?: "none",
+ result->length);
+ }
+ if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_VULKAN) {
+ FF_DEBUG("Trying Vulkan GPU detection fallback");
+ FFVulkanResult* vulkan = ffDetectVulkan();
+ if (!vulkan->error && vulkan->gpus.length > 0) {
+ FF_DEBUG("Vulkan detection succeeded with %u GPU(s)", vulkan->gpus.length);
+ ffListDestroy(result);
+ ffListInitMove(result, &vulkan->gpus);
+
+#ifdef __ANDROID__
+ double ffGPUDetectTempFromTZ(void);
+ if (options->temp && result->length == 1) {
+ FF_DEBUG("Applying Android thermal-zone temperature to single Vulkan GPU");
+ FF_LIST_GET(FFGPUResult, *result, 0)->temperature = ffGPUDetectTempFromTZ();
+ }
+#endif
+
+ return NULL;
+ }
+
+ FF_DEBUG("Vulkan detection did not produce results (error=%s, gpuCount=%u)",
+ vulkan->error ?: "none",
+ vulkan->gpus.length);
+ }
+ if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENCL) {
+ FF_DEBUG("Trying OpenCL GPU detection fallback");
+ FFOpenCLResult* opencl = ffDetectOpenCL();
+ if (!opencl->error && opencl->gpus.length > 0) {
+ FF_DEBUG("OpenCL detection succeeded with %u GPU(s)", opencl->gpus.length);
+ ffListDestroy(result);
+ ffListInitMove(result, &opencl->gpus);
+ return NULL;
+ }
+
+ FF_DEBUG("OpenCL detection did not produce results (error=%s, gpuCount=%u)",
+ opencl->error ?: "none",
+ opencl->gpus.length);
+ }
+ if (options->detectionMethod <= FF_GPU_DETECTION_METHOD_OPENGL) {
+ FF_DEBUG("Trying OpenGL GPU detection fallback");
+ const char* error = detectByOpenGL(result);
+ if (error == NULL) {
+ FF_DEBUG("OpenGL fallback succeeded with %u GPU(s)", result->length);
+ return NULL;
+ }
+
+ FF_DEBUG("OpenGL fallback failed: %s", error);
+ }
+
+ FF_DEBUG("GPU detection failed in all enabled backends");
+ return "GPU detection failed";
+}
diff --git a/src/detection/gpu/gpu.h b/src/detection/gpu/gpu.h
new file mode 100644
index 0000000..10bd02b
--- /dev/null
+++ b/src/detection/gpu/gpu.h
@@ -0,0 +1,89 @@
+#pragma once
+
+#include "fastfetch.h"
+#include "modules/gpu/option.h"
+
+#define FF_GPU_TEMP_UNSET (-DBL_MAX)
+#define FF_GPU_CORE_COUNT_UNSET -1
+#define FF_GPU_VMEM_SIZE_UNSET ((uint64_t) -1)
+#define FF_GPU_FREQUENCY_UNSET 0
+#define FF_GPU_CORE_USAGE_UNSET (-DBL_MAX)
+#define FF_GPU_INDEX_UNSET ((uint32_t) -1)
+
+extern const char* FF_GPU_VENDOR_NAME_APPLE;
+extern const char* FF_GPU_VENDOR_NAME_AMD;
+extern const char* FF_GPU_VENDOR_NAME_INTEL;
+extern const char* FF_GPU_VENDOR_NAME_NVIDIA;
+extern const char* FF_GPU_VENDOR_NAME_MTHREADS;
+extern const char* FF_GPU_VENDOR_NAME_QUALCOMM;
+extern const char* FF_GPU_VENDOR_NAME_MTK;
+extern const char* FF_GPU_VENDOR_NAME_VMWARE;
+extern const char* FF_GPU_VENDOR_NAME_PARALLELS;
+extern const char* FF_GPU_VENDOR_NAME_MICROSOFT;
+extern const char* FF_GPU_VENDOR_NAME_REDHAT;
+extern const char* FF_GPU_VENDOR_NAME_ORACLE;
+extern const char* FF_GPU_VENDOR_NAME_BROADCOM;
+extern const char* FF_GPU_VENDOR_NAME_LOONGSON;
+extern const char* FF_GPU_VENDOR_NAME_JINGJIA_MICRO;
+extern const char* FF_GPU_VENDOR_NAME_HUAWEI;
+extern const char* FF_GPU_VENDOR_NAME_ZHAOXIN;
+extern const char* FF_GPU_VENDOR_NAME_QEMU;
+
+typedef struct FFGPUMemory {
+ uint64_t total;
+ uint64_t used;
+} FFGPUMemory;
+
+typedef struct FFGPUResult {
+ uint32_t index;
+ FFGPUType type;
+ FFstrbuf vendor;
+ FFstrbuf name;
+ FFstrbuf driver;
+ FFstrbuf platformApi;
+ FFstrbuf memoryType;
+ double temperature;
+ double coreUsage;
+ int32_t coreCount;
+ uint32_t frequency; // Maximum time clock frequency in MHz
+ FFGPUMemory dedicated;
+ FFGPUMemory shared;
+ uint64_t deviceId;
+} FFGPUResult;
+
+const char* ffDetectGPU(const FFGPUOptions* options, FFlist* result);
+const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus);
+
+const char* ffGPUGetVendorString(unsigned vendorId);
+
+typedef struct FFGpuDriverPciBusId {
+ uint32_t domain;
+ uint32_t bus;
+ uint32_t device;
+ uint32_t func;
+} FFGpuDriverPciBusId;
+
+#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__GNU__)
+void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu);
+void ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu);
+
+ #if FF_HAVE_DRM
+const char* ffDrmDetectRadeon(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath);
+const char* ffDrmDetectAmdgpu(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath);
+const char* ffDrmDetectI915(FFGPUResult* gpu, int fd);
+const char* ffDrmDetectXe(FFGPUResult* gpu, int fd);
+const char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd);
+const char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd);
+ #endif // FF_HAVE_DRM
+
+const char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId);
+#endif // defined(XXX)
+
+static inline uint64_t ffGPUPciAddr2Id(uint64_t domain, uint64_t bus, uint64_t device, uint64_t function) {
+ return (domain << 16) | (bus << 8) | (device << 3) | function;
+}
+
+static inline uint64_t ffGPUGeneral2Id(uint64_t originalId) {
+ // Note: originalId may already have the MSB set
+ return (1ULL << 63) | originalId;
+}
diff --git a/src/detection/gpu/gpu_amd.c b/src/detection/gpu/gpu_amd.c
new file mode 100644
index 0000000..f396def
--- /dev/null
+++ b/src/detection/gpu/gpu_amd.c
@@ -0,0 +1,460 @@
+#include "gpu_driver_specific.h"
+
+#include "adl.h"
+#include "common/library.h"
+#include "common/mallocHelper.h"
+#include "common/debug.h"
+
+// Helper function to convert ADL status code to string
+FF_A_UNUSED static const char* ffAdlStatusToString(int status) {
+ switch (status) {
+#define FF_ADL_STATUS_CASE(name) \
+ case name: \
+ return #name;
+ FF_ADL_STATUS_CASE(ADL_OK)
+ FF_ADL_STATUS_CASE(ADL_OK_WARNING)
+ FF_ADL_STATUS_CASE(ADL_OK_MODE_CHANGE)
+ FF_ADL_STATUS_CASE(ADL_OK_RESTART)
+ FF_ADL_STATUS_CASE(ADL_OK_WAIT)
+ FF_ADL_STATUS_CASE(ADL_ERR)
+ FF_ADL_STATUS_CASE(ADL_ERR_NOT_INIT)
+ FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM)
+ FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM_SIZE)
+ FF_ADL_STATUS_CASE(ADL_ERR_INVALID_ADL_IDX)
+ FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CONTROLLER_IDX)
+ FF_ADL_STATUS_CASE(ADL_ERR_INVALID_DIPLAY_IDX)
+ FF_ADL_STATUS_CASE(ADL_ERR_NOT_SUPPORTED)
+ FF_ADL_STATUS_CASE(ADL_ERR_NULL_POINTER)
+ FF_ADL_STATUS_CASE(ADL_ERR_DISABLED_ADAPTER)
+ FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CALLBACK)
+ FF_ADL_STATUS_CASE(ADL_ERR_RESOURCE_CONFLICT)
+ FF_ADL_STATUS_CASE(ADL_ERR_SET_INCOMPLETE)
+ FF_ADL_STATUS_CASE(ADL_ERR_NO_XDISPLAY)
+ FF_ADL_STATUS_CASE(ADL_ERR_CALL_TO_INCOMPATIABLE_DRIVER)
+ FF_ADL_STATUS_CASE(ADL_ERR_NO_ADMINISTRATOR_PRIVILEGES)
+ FF_ADL_STATUS_CASE(ADL_ERR_FEATURESYNC_NOT_STARTED)
+ FF_ADL_STATUS_CASE(ADL_ERR_INVALID_POWER_STATE)
+#undef FF_ADL_STATUS_CASE
+ default:
+ return "Unknown ADL error";
+ }
+}
+
+// Memory allocation function
+static void* __stdcall ffAdlMainMemoryAlloc(int iSize) {
+ return malloc((size_t) iSize);
+}
+
+struct FFAdlData {
+ FF_LIBRARY_SYMBOL(ADL2_Main_Control_Destroy)
+ FF_LIBRARY_SYMBOL(ADL2_Adapter_AdapterInfoX3_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Adapter_Graphic_Core_Info_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Adapter_MemoryInfo2_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Adapter_DedicatedVRAMUsage_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Adapter_ASICFamilyType_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Overdrive_Caps)
+ FF_LIBRARY_SYMBOL(ADL2_OverdriveN_CapabilitiesX2_Get)
+ FF_LIBRARY_SYMBOL(ADL2_OverdriveN_SystemClocksX2_Get)
+ FF_LIBRARY_SYMBOL(ADL2_OverdriveN_PerformanceStatus_Get)
+ FF_LIBRARY_SYMBOL(ADL2_OverdriveN_Temperature_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Overdrive8_Current_Setting_Get)
+ FF_LIBRARY_SYMBOL(ADL2_New_QueryPMLogData_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Overdrive6_CurrentStatus_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Overdrive6_Temperature_Get)
+ FF_LIBRARY_SYMBOL(ADL2_Overdrive6_StateInfo_Get)
+
+ bool inited;
+ ADL_CONTEXT_HANDLE apiHandle;
+} adlData;
+
+static void shutdownAdl() {
+ if (adlData.apiHandle) {
+ FF_DEBUG("Destroying ADL context");
+ adlData.ffADL2_Main_Control_Destroy(adlData.apiHandle);
+ adlData.apiHandle = NULL;
+ }
+}
+
+const char* ffDetectAmdGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) {
+ FF_DEBUG("Attempting to detect AMD GPU info using '%s'", soName);
+
+ if (!adlData.inited) {
+ adlData.inited = true;
+ FF_DEBUG("Initializing ADL library");
+ FF_LIBRARY_LOAD(atiadl, "dlopen atiadlxx failed", soName, 1);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(atiadl, ADL2_Main_Control_Create)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Main_Control_Destroy)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_AdapterInfoX3_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_Graphic_Core_Info_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_MemoryInfo2_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_DedicatedVRAMUsage_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_ASICFamilyType_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive_Caps)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_CapabilitiesX2_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_SystemClocksX2_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_PerformanceStatus_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive8_Current_Setting_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_New_QueryPMLogData_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_Temperature_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_CurrentStatus_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_Temperature_Get)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_StateInfo_Get)
+ FF_DEBUG("ADL library loaded");
+
+ int result = ffADL2_Main_Control_Create(ffAdlMainMemoryAlloc, 1 /*iEnumConnectedAdapters*/, &adlData.apiHandle);
+ FF_DEBUG("ADL2_Main_Control_Create returned %s (%d)", ffAdlStatusToString(result), result);
+ if (result != ADL_OK) {
+ return "ffADL2_Main_Control_Create() failed";
+ }
+
+ atexit(shutdownAdl);
+ atiadl = NULL; // don't close atiadl
+ FF_DEBUG("ADL initialization complete");
+ }
+
+ if (!adlData.apiHandle) {
+ FF_DEBUG("ADL context not initialized");
+ return "ffADL2_Main_Control_Create() failed";
+ }
+
+ FF_AUTO_FREE AdapterInfo* devices = NULL;
+ int numDevices = 0;
+ int adapterResult = adlData.ffADL2_Adapter_AdapterInfoX3_Get(adlData.apiHandle, -1, &numDevices, &devices);
+ FF_DEBUG("ADL2_Adapter_AdapterInfoX3_Get returned %s (%d)", ffAdlStatusToString(adapterResult), adapterResult);
+
+ if (adapterResult == ADL_OK) {
+ FF_DEBUG("found %d adapters", numDevices);
+ } else {
+ FF_DEBUG("ffADL2_Adapter_AdapterInfoX3_Get() failed");
+ return "ffADL2_Adapter_AdapterInfoX3_Get() failed";
+ }
+
+ const AdapterInfo* device = NULL;
+ for (int iDev = 0; iDev < numDevices; iDev++) {
+ if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) {
+ FF_DEBUG("Checking device %d: bus=%d, device=%d, func=%d against requested bus=%u, device=%u, func=%u",
+ iDev,
+ devices[iDev].iBusNumber,
+ devices[iDev].iDeviceNumber,
+ devices[iDev].iFunctionNumber,
+ cond->pciBusId.bus,
+ cond->pciBusId.device,
+ cond->pciBusId.func);
+
+ if (
+ cond->pciBusId.bus == (uint32_t) devices[iDev].iBusNumber &&
+ cond->pciBusId.device == (uint32_t) devices[iDev].iDeviceNumber &&
+ cond->pciBusId.func == (uint32_t) devices[iDev].iFunctionNumber) {
+ device = &devices[iDev];
+ FF_DEBUG("Found matching device: %s (index: %d)", device->strAdapterName, device->iAdapterIndex);
+ break;
+ }
+ }
+ }
+
+ if (!device) {
+ FF_DEBUG("Device not found");
+ return "Device not found";
+ }
+
+ if (result.coreCount) {
+ ADLGraphicCoreInfo coreInfo;
+ int status = adlData.ffADL2_Adapter_Graphic_Core_Info_Get(adlData.apiHandle, device->iAdapterIndex, &coreInfo);
+ FF_DEBUG("ADL2_Adapter_Graphic_Core_Info_Get returned %s (%d)", ffAdlStatusToString(status), status);
+
+ if (status == ADL_OK) {
+ FF_DEBUG("Core info - NumCUs: %d, NumPEsPerCU: %d", coreInfo.iNumCUs, coreInfo.iNumPEsPerCU);
+ *result.coreCount = (uint32_t) coreInfo.iNumCUs * (uint32_t) coreInfo.iNumPEsPerCU;
+ FF_DEBUG("Got core count: %u", *result.coreCount);
+ } else {
+ FF_DEBUG("Failed to get core count");
+ }
+ }
+
+ if (result.memory) {
+ int vramUsage = 0;
+ int status = adlData.ffADL2_Adapter_DedicatedVRAMUsage_Get(adlData.apiHandle, device->iAdapterIndex, &vramUsage);
+ FF_DEBUG("ADL2_Adapter_DedicatedVRAMUsage_Get returned %s (%d), usage: %d MB",
+ ffAdlStatusToString(status),
+ status,
+ vramUsage);
+
+ if (status == ADL_OK && vramUsage >= 0) {
+ result.memory->used = (uint64_t) vramUsage * 1024 * 1024;
+ FF_DEBUG("Dedicated VRAM usage: %llu bytes (%d MB)", result.memory->used, vramUsage);
+ } else {
+ FF_DEBUG("Failed to get dedicated VRAM usage");
+ }
+ }
+
+ if (result.memoryType) {
+ ADLMemoryInfo2 memoryInfo;
+ int status = adlData.ffADL2_Adapter_MemoryInfo2_Get(adlData.apiHandle, device->iAdapterIndex, &memoryInfo);
+ FF_DEBUG("ADL2_Adapter_MemoryInfo2_Get returned %s (%d)", ffAdlStatusToString(status), status);
+
+ if (status == ADL_OK) {
+ FF_DEBUG("Memory info - Type: %s, Size: %lld MB", memoryInfo.strMemoryType, memoryInfo.iMemorySize / 1024 / 1024);
+ ffStrbufSetS(result.memoryType, memoryInfo.strMemoryType);
+ FF_DEBUG("Got memory type: %s", memoryInfo.strMemoryType);
+ } else {
+ FF_DEBUG("Failed to get memory type");
+ }
+ }
+
+ if (result.type) {
+ int asicTypes = 0;
+ int valids = 0;
+ int status = adlData.ffADL2_Adapter_ASICFamilyType_Get(adlData.apiHandle, device->iAdapterIndex, &asicTypes, &valids);
+ FF_DEBUG("ADL2_Adapter_ASICFamilyType_Get returned %s (%d), asicTypes: 0x%x, valids: 0x%x",
+ ffAdlStatusToString(status),
+ status,
+ asicTypes,
+ valids);
+
+ if (status == ADL_OK) {
+ asicTypes &= valids; // This design is strange
+ *result.type = asicTypes & ADL_ASIC_INTEGRATED ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
+ FF_DEBUG("GPU type: %s (asicTypes: 0x%x, valids: 0x%x)",
+ *result.type == FF_GPU_TYPE_INTEGRATED ? "Integrated" : "Discrete",
+ asicTypes,
+ valids);
+ } else {
+ FF_DEBUG("Failed to get GPU type");
+ }
+ }
+
+ if (result.index) {
+ *result.index = (uint32_t) device->iAdapterIndex;
+ FF_DEBUG("Setting adapter index: %u", *result.index);
+ }
+
+ if (result.name) {
+ ffStrbufSetS(result.name, device->strAdapterName);
+ FF_DEBUG("Setting adapter name: %s; UDID: %s, Present: %d, Exist: %d", device->strAdapterName, device->strUDID, device->iPresent, device->iExist);
+ }
+
+ int odVersion = 0;
+
+ {
+ int odSupported = 0;
+ int odEnabled = 0;
+ int status = adlData.ffADL2_Overdrive_Caps(adlData.apiHandle, device->iAdapterIndex, &odSupported, &odEnabled, &odVersion);
+ FF_DEBUG("ADL2_Overdrive_Caps returned %s (%d); supported %d, enabled %d; version %d",
+ ffAdlStatusToString(status),
+ status,
+ odSupported,
+ odEnabled,
+ odVersion);
+ if (status != ADL_OK) {
+ FF_DEBUG("Overdrive not supported, results may be inaccurate");
+ // Note even if Overdrive is not supported, we can still get the OD version
+ }
+ }
+
+ if (odVersion == 8) {
+ FF_DEBUG("Using Overdrive8 API (odVersion=%d)", odVersion);
+
+ if (result.frequency) {
+ ADLOD8CurrentSetting currentSetting = { .count = OD8_COUNT };
+ int status = adlData.ffADL2_Overdrive8_Current_Setting_Get(adlData.apiHandle, device->iAdapterIndex, &currentSetting);
+ FF_DEBUG("ADL2_Overdrive8_Current_Setting_Get returned %s (%d)", ffAdlStatusToString(status), status);
+ if (status == ADL_OK) {
+ FF_DEBUG("OD8 Settings count: %d", currentSetting.count);
+
+ *result.frequency = (uint32_t) currentSetting.Od8SettingTable[OD8_GFXCLK_FMAX];
+ FF_DEBUG("Got max engine clock (OD8_GFXCLK_FMAX): %u MHz", *result.frequency);
+ } else {
+ FF_DEBUG("Failed to get max frequency information");
+ }
+ }
+
+ if (result.temp || result.coreUsage) {
+ ADLPMLogDataOutput pmLogDataOutput = {};
+ int status = adlData.ffADL2_New_QueryPMLogData_Get(adlData.apiHandle, device->iAdapterIndex, &pmLogDataOutput);
+ FF_DEBUG("ADL2_New_QueryPMLogData_Get returned %s (%d)", ffAdlStatusToString(status), status);
+ if (status == ADL_OK) {
+ if (result.temp) {
+ ADLSingleSensorData* sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_HOTSPOT];
+ FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_HOTSPOT, "ADL_PMLOG_TEMPERATURE_HOTSPOT", sensor->supported, sensor->value);
+ if (sensor->supported) {
+ *result.temp = sensor->value;
+ FF_DEBUG("Temperature: %.1f°C (HOTSPOT)", *result.temp);
+ } else {
+ sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_GFX];
+ FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_GFX, "ADL_PMLOG_TEMPERATURE_GFX", sensor->supported, sensor->value);
+ if (sensor->supported) {
+ *result.temp = sensor->value;
+ FF_DEBUG("Temperature: %.1f°C (GFX)", *result.temp);
+ } else {
+ sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_SOC];
+ FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_SOC, "ADL_PMLOG_TEMPERATURE_SOC", sensor->supported, sensor->value);
+ if (sensor->supported) {
+ *result.temp = sensor->value;
+ FF_DEBUG("Temperature: %.1f°C (SOC)", *result.temp);
+ } else {
+ FF_DEBUG("No supported temp sensor found, temp detection failed");
+ }
+ }
+ }
+ }
+ if (result.coreUsage) {
+ ADLSingleSensorData* activity = &pmLogDataOutput.sensors[ADL_PMLOG_INFO_ACTIVITY_GFX];
+ FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_INFO_ACTIVITY_GFX, "ADL_PMLOG_INFO_ACTIVITY_GFX", activity->supported, activity->value);
+ if (activity->supported) {
+ *result.coreUsage = activity->value;
+ FF_DEBUG("Core usage: %.1f%%", *result.coreUsage);
+ } else {
+ FF_DEBUG("Sensor %d not supported, GPU usage detection failed", ADL_PMLOG_INFO_ACTIVITY_GFX);
+ }
+ }
+ } else {
+ FF_DEBUG("Failed to get temperature / GPU activity");
+ }
+ }
+ } else if (odVersion == 7) {
+ FF_DEBUG("Using OverdriveN API (odVersion=%d)", odVersion);
+
+ if (result.frequency) {
+ // https://github.com/MaynardMiner/odvii/blob/master/OverdriveN.cpp#L176
+ ADLODNCapabilitiesX2 odCapabilities = {};
+ int status = adlData.ffADL2_OverdriveN_CapabilitiesX2_Get(adlData.apiHandle, device->iAdapterIndex, &odCapabilities);
+ FF_DEBUG("ADL2_OverdriveN_CapabilitiesX2_Get returned %s (%d)", ffAdlStatusToString(status), status);
+
+ if (status == ADL_OK) {
+ if (odCapabilities.iMaximumNumberOfPerformanceLevels == 0) {
+ FF_DEBUG("ADL2_OverdriveN_CapabilitiesX2_Get: no performance levels available");
+ } else {
+ FF_DEBUG("ODN Capabilities - MaxPerformanceLevels: %d, GPU Clock Range: [%d - %d]",
+ odCapabilities.iMaximumNumberOfPerformanceLevels,
+ odCapabilities.sEngineClockRange.iMin,
+ odCapabilities.sEngineClockRange.iMax);
+
+ size_t size = sizeof(ADLODNPerformanceLevelsX2) + sizeof(ADLODNPerformanceLevelX2) * ((unsigned) odCapabilities.iMaximumNumberOfPerformanceLevels - 1);
+ FF_AUTO_FREE ADLODNPerformanceLevelsX2* odPerfLevels = calloc(size, 1);
+ odPerfLevels->iSize = (int) size;
+ odPerfLevels->iNumberOfPerformanceLevels = odCapabilities.iMaximumNumberOfPerformanceLevels;
+ odPerfLevels->iMode = ODNControlType_Current;
+
+ int status = adlData.ffADL2_OverdriveN_SystemClocksX2_Get(adlData.apiHandle, device->iAdapterIndex, odPerfLevels);
+ FF_DEBUG("ADL2_OverdriveN_SystemClocksX2_Get returned %s (%d), levels: %d",
+ ffAdlStatusToString(status),
+ status,
+ odPerfLevels->iNumberOfPerformanceLevels);
+
+ if (status != ADL_OK) {
+ FF_DEBUG("Failed to get frequency information");
+ } else {
+ // lowest to highest
+ for (int i = odPerfLevels->iNumberOfPerformanceLevels - 1; i >= 0; i--) {
+ ADLODNPerformanceLevelX2* level = &odPerfLevels->aLevels[i];
+ FF_DEBUG("Performance level %d: enabled: %d, engine clock = %d", i, level->iEnabled, level->iClock);
+ if (level->iEnabled) {
+ *result.frequency = (uint32_t) level->iClock / 100; // in 10 kHz
+ FF_DEBUG("Got max engine clock: %u MHz", *result.frequency);
+ break;
+ }
+ }
+ }
+ }
+ } else {
+ FF_DEBUG("Failed to get frequency information");
+ }
+ }
+
+ if (result.coreUsage) {
+ ADLODNPerformanceStatus performanceStatus = {};
+ int status = adlData.ffADL2_OverdriveN_PerformanceStatus_Get(adlData.apiHandle, device->iAdapterIndex, &performanceStatus);
+ FF_DEBUG("ADL2_OverdriveN_PerformanceStatus_Get returned %s (%d)", ffAdlStatusToString(status), status);
+
+ if (status == ADL_OK) {
+ FF_DEBUG("Performance Status - Activity: %d%%, CoreClock: %dMHz, MemoryClock: %dMHz",
+ performanceStatus.iGPUActivityPercent,
+ performanceStatus.iCoreClock,
+ performanceStatus.iMemoryClock);
+
+ *result.coreUsage = performanceStatus.iGPUActivityPercent;
+ FF_DEBUG("Got GPU activity: %d%%", performanceStatus.iGPUActivityPercent);
+ } else {
+ FF_DEBUG("Failed to get GPU activity");
+ }
+ }
+
+ if (result.temp) {
+ int milliDegrees = 0;
+ int status = adlData.ffADL2_OverdriveN_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, 1, &milliDegrees);
+ FF_DEBUG("ADL2_OverdriveN_Temperature_Get returned %s (%d)", ffAdlStatusToString(status), status);
+
+ if (status == ADL_OK) {
+ *result.temp = milliDegrees / 1000.0;
+ FF_DEBUG("Temperature: %.1f°C (raw: %d milliC)", *result.temp, milliDegrees);
+ } else {
+ FF_DEBUG("Failed to get temperature");
+ }
+ }
+ } else if (odVersion == 6) {
+ FF_DEBUG("Using Overdrive6 API (odVersion=%d)", odVersion);
+
+ if (result.frequency) {
+ FF_AUTO_FREE ADLOD6StateInfo* stateInfo = calloc(sizeof(ADLOD6StateInfo) + sizeof(ADLOD6PerformanceLevel), 1);
+ stateInfo->iNumberOfPerformanceLevels = 2;
+
+ int status = adlData.ffADL2_Overdrive6_StateInfo_Get(adlData.apiHandle, device->iAdapterIndex, ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE, stateInfo);
+ FF_DEBUG("ADL2_Overdrive6_StateInfo_Get returned %s (%d), performance levels: %d",
+ ffAdlStatusToString(status),
+ status,
+ stateInfo->iNumberOfPerformanceLevels);
+
+ if (status == ADL_OK) {
+ // OD6 uses clock ranges instead of discrete performance levels.
+ // iNumberOfPerformanceLevels is always 2.
+ // The 1st level indicates the minimum clocks in the range.
+ // The 2nd level indicates the maximum clocks in the range.
+ if (stateInfo->iNumberOfPerformanceLevels != 2) {
+ FF_DEBUG("ADL2_Overdrive6_StateInfo_Get: unexpected number of performance levels: %d", stateInfo->iNumberOfPerformanceLevels);
+ } else {
+ FF_DEBUG("OD6 Settings - MinPerformanceLevels: %d, MaxPerformanceLevels: %d",
+ stateInfo->aLevels[0].iEngineClock,
+ stateInfo->aLevels[1].iEngineClock);
+ *result.frequency = (uint32_t) stateInfo->aLevels[1].iEngineClock / 100; // in 10 kHz
+ FF_DEBUG("Got max engine clock: %u MHz", *result.frequency);
+ }
+ } else {
+ FF_DEBUG("Failed to get frequency information");
+ }
+ }
+
+ if (result.coreUsage) {
+ ADLOD6CurrentStatus status = {};
+ int apiStatus = adlData.ffADL2_Overdrive6_CurrentStatus_Get(adlData.apiHandle, device->iAdapterIndex, &status);
+ FF_DEBUG("ADL2_Overdrive6_CurrentStatus_Get returned %s (%d)", ffAdlStatusToString(apiStatus), apiStatus);
+
+ if (apiStatus == ADL_OK) {
+ *result.coreUsage = status.iActivityPercent;
+ FF_DEBUG("Got GPU activity: %d%%", status.iActivityPercent);
+ } else {
+ FF_DEBUG("Failed to get GPU activity");
+ }
+ }
+
+ if (result.temp) {
+ int milliDegrees = 0;
+ int status = adlData.ffADL2_Overdrive6_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, &milliDegrees);
+ FF_DEBUG("ADL2_Overdrive6_Temperature_Get returned %s (%d), temperature: %d milliC",
+ ffAdlStatusToString(status),
+ status,
+ milliDegrees);
+
+ if (status == ADL_OK) {
+ *result.temp = milliDegrees / 1000.0;
+ FF_DEBUG("Temperature: %.1f°C", *result.temp);
+ } else {
+ FF_DEBUG("Failed to get temperature");
+ }
+ }
+ } else {
+ FF_DEBUG("Unknown Overdrive version: %d", odVersion);
+ return "Unknown Overdrive version";
+ }
+ FF_DEBUG("AMD GPU detection complete - returning success");
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_android.c b/src/detection/gpu/gpu_android.c
new file mode 100644
index 0000000..08fd48a
--- /dev/null
+++ b/src/detection/gpu/gpu_android.c
@@ -0,0 +1,55 @@
+#include "gpu.h"
+#include "common/io.h"
+#include "common/strutil.h"
+
+#include <fcntl.h>
+
+static double parseTZDir(int dfd, FFstrbuf* buffer) {
+ if (!ffReadFileBufferRelative(dfd, "type", buffer) || !ffStrbufStartsWithS(buffer, "gpu")) {
+ return FF_GPU_TEMP_UNSET;
+ }
+
+ if (!ffReadFileBufferRelative(dfd, "temp", buffer)) {
+ return FF_GPU_TEMP_UNSET;
+ }
+
+ double value = ffStrbufToDouble(buffer, FF_GPU_TEMP_UNSET); // millidegree Celsius
+ if (value == FF_GPU_TEMP_UNSET) {
+ return FF_GPU_TEMP_UNSET;
+ }
+
+ return value / 1000.;
+}
+
+double ffGPUDetectTempFromTZ(void) {
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir("/sys/class/thermal/");
+ if (dirp) {
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+ int dfd = dirfd(dirp);
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ if (!ffStrStartsWith(entry->d_name, "thermal_zone")) {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_FD int subfd = openat(dfd, entry->d_name, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
+ if (subfd < 0) {
+ continue;
+ }
+
+ double result = parseTZDir(subfd, &buffer);
+ if (result != FF_GPU_TEMP_UNSET) {
+ return result;
+ }
+ }
+ }
+ return FF_GPU_TEMP_UNSET;
+}
+
+const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
+ FF_UNUSED(options, gpus);
+ return "No permission. Fallbacks to Vulkan, OpenCL or OpenGL instead";
+}
diff --git a/src/detection/gpu/gpu_apple.c b/src/detection/gpu/gpu_apple.c
new file mode 100644
index 0000000..3b8de20
--- /dev/null
+++ b/src/detection/gpu/gpu_apple.c
@@ -0,0 +1,208 @@
+#include "gpu.h"
+#include "common/apple/cf_helpers.h"
+#include "common/apple/smc_temps.h"
+
+#include <IOKit/graphics/IOGraphicsLib.h>
+
+const char* ffGpuDetectMetal(FFlist* gpus);
+const char* ffGpuDetectDriverVersion(FFlist* gpus);
+
+static double detectGpuTemp(const FFstrbuf* gpuName) {
+ double result = 0;
+ const char* error = NULL;
+
+ if (ffStrbufStartsWithS(gpuName, "Apple M")) {
+ switch (strtol(gpuName->chars + strlen("Apple M"), NULL, 10)) {
+ case 0:
+ error = "Invalid Apple Silicon GPU";
+ break;
+ case 1:
+ error = ffDetectSmcTemps(FF_TEMP_GPU_M1X, &result);
+ break;
+ case 2:
+ error = ffDetectSmcTemps(FF_TEMP_GPU_M2X, &result);
+ break;
+ case 3:
+ error = ffDetectSmcTemps(FF_TEMP_GPU_M3X, &result);
+ break;
+ case 4:
+ error = ffDetectSmcTemps(FF_TEMP_GPU_M4X, &result);
+ break;
+ default:
+ error = "Unsupported Apple Silicon GPU";
+ break;
+ }
+ } else if (ffStrbufStartsWithS(gpuName, "Intel")) {
+ error = ffDetectSmcTemps(FF_TEMP_GPU_INTEL, &result);
+ } else if (ffStrbufStartsWithS(gpuName, "Radeon") || ffStrbufStartsWithS(gpuName, "AMD")) {
+ error = ffDetectSmcTemps(FF_TEMP_GPU_AMD, &result);
+ } else {
+ error = ffDetectSmcTemps(FF_TEMP_GPU_UNKNOWN, &result);
+ }
+
+ if (error) {
+ return FF_GPU_TEMP_UNSET;
+ }
+
+ return result;
+}
+
+#ifdef __aarch64__
+ #include "common/apple/cf_helpers.h"
+
+ #include <IOKit/IOKitLib.h>
+
+static const char* detectFrequency(FFGPUResult* gpu) {
+ // https://github.com/giampaolo/psutil/pull/2222/files
+
+ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t entryDevice = IOServiceGetMatchingService(MACH_PORT_NULL, IOServiceNameMatching("pmgr"));
+ if (!entryDevice) {
+ return "IOServiceGetMatchingServices() failed";
+ }
+
+ if (!IOObjectConformsTo(entryDevice, "AppleARMIODevice")) {
+ return "\"pmgr\" should conform to \"AppleARMIODevice\"";
+ }
+
+ FF_CFTYPE_AUTO_RELEASE CFDataRef freqProperty = (CFDataRef) IORegistryEntryCreateCFProperty(entryDevice, CFSTR("voltage-states9-sram"), kCFAllocatorDefault, kNilOptions);
+ if (!freqProperty || CFGetTypeID(freqProperty) != CFDataGetTypeID()) {
+ return "\"voltage-states9-sram\" in \"pmgr\" is not found";
+ }
+
+ // voltage-states9-sram stores supported <frequency / voltage> pairs of gpu from the lowest to the highest
+ CFIndex propLength = CFDataGetLength(freqProperty);
+ if (propLength == 0 || propLength % (CFIndex) sizeof(uint32_t) * 2 != 0) {
+ return "Invalid \"voltage-states9-sram\" length";
+ }
+
+ uint32_t* pStart = (uint32_t*) CFDataGetBytePtr(freqProperty);
+ uint32_t pMax = *pStart;
+ for (CFIndex i = 2; i < propLength / (CFIndex) sizeof(uint32_t) && pStart[i] > 0; i += 2 /* skip voltage */) {
+ pMax = pMax > pStart[i] ? pMax : pStart[i];
+ }
+
+ if (pMax > 0) {
+ // While this is not necessary for now (seems), we add this logic just in case. See cpu_apple.c
+ if (pMax > 100000000) { // Assume that pMax is in Hz
+ gpu->frequency = pMax / 1000 / 1000;
+ } else { // Assume that pMax is in kHz
+ gpu->frequency = pMax / 1000;
+ }
+ }
+
+ return NULL;
+}
+#endif
+
+const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
+ FF_IOOBJECT_AUTO_RELEASE io_iterator_t iterator = IO_OBJECT_NULL;
+ {
+ CFMutableDictionaryRef matches = IOServiceMatching(kIOAcceleratorClassName);
+ CFDictionaryAddValue(matches, CFSTR("IOMatchCategory"), CFSTR(kIOAcceleratorClassName));
+ if (IOServiceGetMatchingServices(MACH_PORT_NULL, matches, &iterator) != kIOReturnSuccess) {
+ return "IOServiceGetMatchingServices() failed";
+ }
+ }
+
+ io_registry_entry_t registryEntry;
+ while ((registryEntry = IOIteratorNext(iterator)) != IO_OBJECT_NULL) {
+ CFMutableDictionaryRef properties;
+ if (IORegistryEntryCreateCFProperties(registryEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
+ IOObjectRelease(registryEntry);
+ continue;
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ ffStrbufInit(&gpu->memoryType);
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+ IORegistryEntryGetRegistryEntryID(registryEntry, &gpu->deviceId);
+ ffStrbufInitStatic(&gpu->platformApi, "IOKit");
+
+ ffStrbufInit(&gpu->driver); // Ok for both Apple and Intel
+ ffCfDictGetString(properties, CFSTR("CFBundleIdentifier"), &gpu->driver);
+
+ if (ffCfDictGetInt(properties, CFSTR("gpu-core-count"), &gpu->coreCount) != NULL) { // For Apple
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ }
+
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ CFDictionaryRef perfStatistics = NULL;
+ uint64_t vramUsed = 0, vramTotal = 0;
+ if (ffCfDictGetDict(properties, CFSTR("PerformanceStatistics"), &perfStatistics) == NULL) {
+ int64_t utilization;
+ if (ffCfDictGetInt64(perfStatistics, CFSTR("Device Utilization %"), &utilization) == NULL) {
+ gpu->coreUsage = (double) utilization;
+ } else if (ffCfDictGetInt64(perfStatistics, CFSTR("GPU Core Utilization"), &utilization) == NULL) {
+ gpu->coreUsage = (double) utilization / 10000000.; // Nvidia?
+ }
+
+ if (ffCfDictGetInt64(perfStatistics, CFSTR("Alloc system memory"), (int64_t*) &vramTotal) == NULL) {
+ if (ffCfDictGetInt64(perfStatistics, CFSTR("In use system memory"), (int64_t*) &vramUsed) != NULL) {
+ vramTotal = 0;
+ }
+ } else if (ffCfDictGetInt64(perfStatistics, CFSTR("vramFreeBytes"), (int64_t*) &vramTotal) == NULL) {
+ if (ffCfDictGetInt64(perfStatistics, CFSTR("vramUsedBytes"), (int64_t*) &vramUsed) == NULL) {
+ vramTotal += vramUsed;
+ } else {
+ vramTotal = 0;
+ }
+ }
+ }
+
+ ffStrbufInit(&gpu->name);
+ // IOAccelerator returns model / vendor-id properties for Apple Silicon, but not for Intel Iris GPUs.
+ // Still needs testing for AMD's
+ if (ffCfDictGetString(properties, CFSTR("model"), &gpu->name) != NULL) {
+ CFRelease(properties);
+ properties = NULL;
+
+ FF_IOOBJECT_AUTO_RELEASE io_registry_entry_t parentEntry = 0;
+ if (IORegistryEntryGetParentEntry(registryEntry, kIOServicePlane, &parentEntry) != kIOReturnSuccess ||
+ IORegistryEntryCreateCFProperties(parentEntry, &properties, kCFAllocatorDefault, kNilOptions) != kIOReturnSuccess) {
+ IOObjectRelease(registryEntry);
+ continue;
+ }
+ ffCfDictGetString(properties, CFSTR("model"), &gpu->name);
+ }
+
+ ffStrbufInit(&gpu->vendor);
+ int vendorId;
+ if (ffCfDictGetInt(properties, CFSTR("vendor-id"), &vendorId) == NULL) {
+ const char* vendorStr = ffGPUGetVendorString((unsigned) vendorId);
+ ffStrbufAppendS(&gpu->vendor, vendorStr);
+ if (vendorStr == FF_GPU_VENDOR_NAME_APPLE || vendorStr == FF_GPU_VENDOR_NAME_INTEL) {
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+ } else if (vendorStr == FF_GPU_VENDOR_NAME_NVIDIA || vendorStr == FF_GPU_VENDOR_NAME_AMD) {
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ }
+
+#ifdef __aarch64__
+ if (vendorStr == FF_GPU_VENDOR_NAME_APPLE) {
+ detectFrequency(gpu);
+ }
+#endif
+
+ if (gpu->type == FF_GPU_TYPE_INTEGRATED) {
+ gpu->shared.total = vramTotal;
+ gpu->shared.used = vramUsed;
+ } else if (gpu->type == FF_GPU_TYPE_DISCRETE) {
+ gpu->dedicated.total = vramTotal;
+ gpu->dedicated.used = vramUsed;
+ }
+ }
+
+ gpu->temperature = options->temp ? detectGpuTemp(&gpu->name) : FF_GPU_TEMP_UNSET;
+
+ CFRelease(properties);
+ IOObjectRelease(registryEntry);
+ }
+
+ ffGpuDetectMetal(gpus);
+ if (instance.config.general.detectVersion) {
+ ffGpuDetectDriverVersion(gpus);
+ }
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_apple.m b/src/detection/gpu/gpu_apple.m
new file mode 100644
index 0000000..e206bdc
--- /dev/null
+++ b/src/detection/gpu/gpu_apple.m
@@ -0,0 +1,87 @@
+#include "gpu.h"
+
+#import <Metal/MTLDevice.h>
+#import <IOKit/kext/KextManager.h>
+
+#ifndef MAC_OS_VERSION_26_0
+ #define MTLGPUFamilyMetal4 ((MTLGPUFamily) 5002)
+#endif
+#ifndef MAC_OS_VERSION_13_0
+ #define MTLGPUFamilyMetal3 ((MTLGPUFamily) 5001)
+#endif
+#ifndef MAC_OS_X_VERSION_10_15
+ #define MTLFeatureSet_macOS_GPUFamily1_v4 ((MTLFeatureSet) 10004)
+ #define MTLFeatureSet_macOS_GPUFamily2_v1 ((MTLFeatureSet) 10005)
+#endif
+
+const char* ffGpuDetectDriverVersion(FFlist* gpus)
+{
+ if (@available(macOS 10.7, *))
+ {
+ NSMutableArray* arr = NSMutableArray.new;
+ FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
+ [arr addObject:@(x->driver.chars)];
+
+ NSDictionary* dict = CFBridgingRelease(KextManagerCopyLoadedKextInfo((__bridge CFArrayRef)arr, (__bridge CFArrayRef)@[@"CFBundleVersion"]));
+ FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
+ {
+ NSString* version = dict[@(x->driver.chars)][@"CFBundleVersion"];
+ if (version)
+ {
+ ffStrbufAppendC(&x->driver, ' ');
+ ffStrbufAppendS(&x->driver, version.UTF8String);
+ }
+ }
+ return NULL;
+ }
+ return "Unsupported macOS version";
+}
+
+const char* ffGpuDetectMetal(FFlist* gpus)
+{
+ if (@available(macOS 10.13, *))
+ {
+ for (id<MTLDevice> device in MTLCopyAllDevices())
+ {
+ FFGPUResult* gpu = NULL;
+ FF_LIST_FOR_EACH(FFGPUResult, x, *gpus)
+ {
+ if (x->deviceId == device.registryID)
+ {
+ gpu = x;
+ break;
+ }
+ }
+ if (!gpu) continue;
+
+ #ifndef MAC_OS_X_VERSION_10_15
+ if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily2_v1])
+ ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 2");
+ else if ([device supportsFeatureSet:MTLFeatureSet_macOS_GPUFamily1_v1])
+ ffStrbufSetStatic(&gpu->platformApi, "Metal Feature Set 1");
+ #else // MAC_OS_X_VERSION_10_15
+ #pragma clang diagnostic push
+ #pragma clang diagnostic ignored "-Wunguarded-availability-new"
+ if ([device supportsFamily:MTLGPUFamilyMetal4])
+ ffStrbufSetStatic(&gpu->platformApi, "Metal 4");
+ else if ([device supportsFamily:MTLGPUFamilyMetal3])
+ ffStrbufSetStatic(&gpu->platformApi, "Metal 3");
+ #pragma clang diagnostic pop
+ else if ([device supportsFamily:MTLGPUFamilyCommon3])
+ ffStrbufSetStatic(&gpu->platformApi, "Metal Common 3");
+ else if ([device supportsFamily:MTLGPUFamilyCommon2])
+ ffStrbufSetStatic(&gpu->platformApi, "Metal Common 2");
+ else if ([device supportsFamily:MTLGPUFamilyCommon1])
+ ffStrbufSetStatic(&gpu->platformApi, "Metal Common 1");
+
+ gpu->type = device.hasUnifiedMemory ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
+ gpu->index = (uint32_t) device.locationNumber;
+
+ if (device.hasUnifiedMemory && device.recommendedMaxWorkingSetSize > 0)
+ gpu->shared.total = device.recommendedMaxWorkingSetSize;
+ #endif
+ }
+ return NULL;
+ }
+ return "Metal API is not supported by this macOS version";
+}
diff --git a/src/detection/gpu/gpu_bsd.c b/src/detection/gpu/gpu_bsd.c
new file mode 100644
index 0000000..ae29f46
--- /dev/null
+++ b/src/detection/gpu/gpu_bsd.c
@@ -0,0 +1,230 @@
+#include "gpu_driver_specific.h"
+
+#include "common/io.h"
+#include "common/mallocHelper.h"
+
+#include <sys/pciio.h>
+#include <fcntl.h>
+#if __has_include(<dev/pci/pcireg.h>)
+ #include <dev/pci/pcireg.h> // FreeBSD
+#else
+ #include <bus/pci/pcireg.h> // DragonFly
+#endif
+
+static void fillGPUTypeGeneric(FFGPUResult* gpu) {
+ if (gpu->type == FF_GPU_TYPE_UNKNOWN) {
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA) {
+ if (ffStrbufStartsWithIgnCaseS(&gpu->name, "GeForce") ||
+ ffStrbufStartsWithIgnCaseS(&gpu->name, "Quadro") ||
+ ffStrbufStartsWithIgnCaseS(&gpu->name, "Tesla")) {
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS) {
+ if (ffStrbufStartsWithIgnCaseS(&gpu->name, "MTT ")) {
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) {
+ // 0000:00:02.0 is reserved for Intel integrated graphics
+ gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
+ }
+ }
+}
+
+#if FF_HAVE_DRM
+ #include "common/library.h"
+ #include "common/strutil.h"
+
+ #include <xf86drm.h>
+
+static const char* detectByDrm(const FFGPUOptions* options, FFlist* gpus) {
+ FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm" FF_LIBRARY_EXTENSION, 2)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmGetDevices)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, drmFreeDevices)
+
+ drmDevicePtr devices[64];
+ int nDevices = ffdrmGetDevices(devices, ARRAY_SIZE(devices));
+ if (nDevices < 0) {
+ return "drmGetDevices() failed";
+ }
+
+ for (int iDev = 0; iDev < nDevices; ++iDev) {
+ drmDevice* dev = devices[iDev];
+
+ if (!(dev->available_nodes & (1 << DRM_NODE_PRIMARY))) {
+ continue;
+ }
+
+ const char* path = dev->nodes[DRM_NODE_PRIMARY];
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInit(&gpu->vendor);
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInitS(&gpu->platformApi, path);
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = 0;
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ switch (dev->bustype) {
+ case DRM_BUS_PCI:
+ ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev->deviceinfo.pci->vendor_id));
+ gpu->deviceId = ffGPUPciAddr2Id(dev->businfo.pci->domain, dev->businfo.pci->bus, dev->businfo.pci->dev, dev->businfo.pci->func);
+ break;
+ case DRM_BUS_HOST1X:
+ ffStrbufSetS(&gpu->name, dev->deviceinfo.host1x->compatible[0]);
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+ break;
+ case DRM_BUS_PLATFORM:
+ ffStrbufSetS(&gpu->name, dev->deviceinfo.platform->compatible[0]);
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+ break;
+ case DRM_BUS_USB:
+ ffStrbufSetF(&gpu->name, "USB Device (%u-%u)", dev->deviceinfo.usb->vendor, dev->deviceinfo.usb->product);
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ break;
+ }
+
+ FF_AUTO_CLOSE_FD int fd = open(path, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ continue;
+ }
+
+ char driverName[64];
+ driverName[0] = '\0';
+ struct drm_version ver = {
+ .name = driverName,
+ .name_len = ARRAY_SIZE(driverName),
+ };
+ if (ioctl(fd, DRM_IOCTL_VERSION, &ver) == 0) {
+ driverName[ver.name_len] = '\0';
+ ffStrbufSetF(&gpu->driver, "%s %d.%d.%d", ver.name, ver.version_major, ver.version_minor, ver.version_patchlevel);
+ }
+
+ if (ffStrStartsWith(driverName, "i915")) {
+ ffDrmDetectI915(gpu, fd);
+ } else if (ffStrStartsWith(driverName, "amdgpu")) {
+ ffDrmDetectAmdgpu(options, gpu, dev->nodes[DRM_NODE_RENDER]);
+ } else if (ffStrStartsWith(driverName, "radeon")) {
+ ffDrmDetectRadeon(options, gpu, dev->nodes[DRM_NODE_RENDER]);
+ } else if (ffStrStartsWith(driverName, "xe")) {
+ ffDrmDetectXe(gpu, fd);
+ } else if (ffStrStartsWith(driverName, "asahi")) {
+ ffDrmDetectAsahi(gpu, fd);
+ } else if (ffStrStartsWith(driverName, "nouveau")) {
+ ffDrmDetectNouveau(gpu, fd);
+ } else if (dev->bustype == DRM_BUS_PCI) {
+ ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) {
+ .domain = (uint32_t) dev->businfo.pci->domain,
+ .bus = dev->businfo.pci->bus,
+ .device = dev->businfo.pci->dev,
+ .func = dev->businfo.pci->func,
+ });
+ }
+
+ if (gpu->name.length == 0) {
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ ffGPUQueryAmdGpuName(dev->deviceinfo.pci->device_id, dev->deviceinfo.pci->revision_id, gpu);
+ }
+ if (gpu->name.length == 0) {
+ ffGPUFillVendorAndName(0, dev->deviceinfo.pci->vendor_id, dev->deviceinfo.pci->device_id, gpu);
+ }
+ }
+
+ fillGPUTypeGeneric(gpu);
+ }
+
+ ffdrmFreeDevices(devices, nDevices);
+
+ return NULL;
+}
+#endif
+
+static const char* detectByPci(const FFGPUOptions* options, FFlist* gpus) {
+ FF_AUTO_CLOSE_FD int fd = open("/dev/pci", O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ return "open(\"/dev/pci\", O_RDONLY | O_CLOEXEC, 0) failed";
+ }
+
+ struct pci_conf confs[128];
+ struct pci_match_conf match = {
+ .pc_class = PCIC_DISPLAY,
+ .flags = PCI_GETCONF_MATCH_CLASS,
+ };
+ struct pci_conf_io pcio = {
+ .pat_buf_len = sizeof(match),
+ .num_patterns = 1,
+ .patterns = &match,
+ .match_buf_len = sizeof(confs),
+ .matches = confs,
+ };
+
+ if (ioctl(fd, PCIOCGETCONF, &pcio) < 0) {
+ return "ioctl(fd, PCIOCGETCONF, &pc) failed";
+ }
+
+ if (pcio.status == PCI_GETCONF_ERROR) {
+ return "ioctl(fd, PCIOCGETCONF, &pc) returned error";
+ }
+
+ for (uint32_t i = 0; i < pcio.num_matches; ++i) {
+ struct pci_conf* pc = &confs[i];
+
+ if (pc->pc_sel.pc_func > 0 && pc->pc_subclass == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) {
+ continue; // Likely an auxiliary display controller (#2034)
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(pc->pc_vendor));
+ ffStrbufInit(&gpu->name);
+ ffStrbufInitS(&gpu->driver, pc->pd_name);
+ ffStrbufInitStatic(&gpu->platformApi, "/dev/pci");
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = ffGPUPciAddr2Id(pc->pc_sel.pc_domain, pc->pc_sel.pc_bus, pc->pc_sel.pc_dev, pc->pc_sel.pc_func);
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) {
+ .domain = (uint32_t) pc->pc_sel.pc_domain,
+ .bus = pc->pc_sel.pc_bus,
+ .device = pc->pc_sel.pc_dev,
+ .func = pc->pc_sel.pc_func,
+ });
+
+ if (gpu->name.length == 0) {
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ ffGPUQueryAmdGpuName(pc->pc_device, pc->pc_revid, gpu);
+ }
+ if (gpu->name.length == 0) {
+ ffGPUFillVendorAndName(pc->pc_subclass, pc->pc_vendor, pc->pc_device, gpu);
+ }
+ }
+
+ fillGPUTypeGeneric(gpu);
+ }
+
+ return NULL;
+}
+
+const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
+#if FF_HAVE_DRM
+ if (options->detectionMethod == FF_GPU_DETECTION_METHOD_AUTO) {
+ detectByDrm(options, gpus);
+ if (gpus->length > 0) {
+ return NULL;
+ }
+ }
+#endif
+
+ return detectByPci(options, gpus);
+}
diff --git a/src/detection/gpu/gpu_driver_specific.h b/src/detection/gpu/gpu_driver_specific.h
new file mode 100644
index 0000000..3ddfb69
--- /dev/null
+++ b/src/detection/gpu/gpu_driver_specific.h
@@ -0,0 +1,90 @@
+#pragma once
+
+#include "gpu.h"
+
+typedef enum FF_A_PACKED FFGpuDriverConditionType {
+ FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID = 1 << 0,
+ FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID = 1 << 1,
+ FF_GPU_DRIVER_CONDITION_TYPE_LUID = 1 << 2,
+ FF_GPU_DRIVER_CONDITION_TYPE_FORCE_UNSIGNED = UINT8_MAX,
+} FFGpuDriverConditionType;
+
+typedef struct FFGpuDriverPciDeviceId {
+ uint32_t deviceId;
+ uint32_t vendorId;
+ uint32_t subSystemId;
+ uint32_t revId;
+} FFGpuDriverPciDeviceId;
+
+// Use pciBusId if not NULL; use pciDeviceId otherwise
+typedef struct FFGpuDriverCondition {
+ FFGpuDriverConditionType type;
+ FFGpuDriverPciBusId pciBusId;
+ FFGpuDriverPciDeviceId pciDeviceId;
+ uint64_t luid;
+} FFGpuDriverCondition;
+
+// detect x if not NULL
+typedef struct FFGpuDriverResult {
+ uint32_t* index;
+ double* temp;
+ FFGPUMemory* memory;
+ FFstrbuf* memoryType;
+ FFGPUMemory* sharedMemory;
+ uint32_t* coreCount;
+ double* coreUsage;
+ FFGPUType* type;
+ uint32_t* frequency;
+ FFstrbuf* name;
+} FFGpuDriverResult;
+
+const char* ffDetectNvidiaGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);
+const char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);
+const char* ffDetectAmdGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);
+const char* ffDetectMthreadsGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName);
+
+#ifndef FF_GPU_DRIVER_DLLNAME_PATH_PREFIX
+ #define FF_GPU_DRIVER_DLLNAME_PATH_PREFIX
+#endif
+
+FF_A_UNUSED static inline bool getDriverSpecificDetectionFn(const char* vendor, __typeof__(&ffDetectNvidiaGpuInfo)* pDetectFn, const char** pDllName) {
+ if (vendor == FF_GPU_VENDOR_NAME_NVIDIA) {
+ *pDetectFn = ffDetectNvidiaGpuInfo;
+#ifdef _WIN32
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "nvml.dll";
+#else
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "libnvidia-ml.so";
+#endif
+ } else if (vendor == FF_GPU_VENDOR_NAME_MTHREADS) {
+ *pDetectFn = ffDetectMthreadsGpuInfo;
+#ifdef _WIN32
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "mtml.dll";
+#else
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "libmtml.so";
+#endif
+ }
+#ifdef _WIN32
+ else if (vendor == FF_GPU_VENDOR_NAME_INTEL) {
+ *pDetectFn = ffDetectIntelGpuInfo;
+ #ifdef _WIN64
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "ControlLib.dll";
+ #else
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "ControlLib32.dll";
+ #endif
+ } else if (vendor == FF_GPU_VENDOR_NAME_AMD) {
+ *pDetectFn = ffDetectAmdGpuInfo;
+ #ifdef _WIN64
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "atiadlxx.dll";
+ #else
+ *pDllName = FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "atiadlxy.dll";
+ #endif
+ }
+#endif
+ else {
+ *pDetectFn = NULL;
+ *pDllName = NULL;
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/detection/gpu/gpu_drm.c b/src/detection/gpu/gpu_drm.c
new file mode 100644
index 0000000..3a7d381
--- /dev/null
+++ b/src/detection/gpu/gpu_drm.c
@@ -0,0 +1,380 @@
+#include "gpu.h"
+
+#if FF_HAVE_DRM
+ #include <drm.h>
+ #include <fcntl.h>
+ #include <sys/ioctl.h>
+
+ #include "common/io.h"
+ #include "common/library.h"
+ #include "common/mallocHelper.h"
+ #include "common/strutil.h"
+
+ #include "intel_drm.h"
+ #include "asahi_drm.h"
+ #include <radeon_drm.h>
+ #include <nouveau_drm.h>
+
+const char* ffDrmDetectRadeon(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath) {
+ FF_AUTO_CLOSE_FD int fd = open(renderPath, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ return "Failed to open DRM render device";
+ }
+
+ uint32_t value;
+
+ // https://github.com/torvalds/linux/blob/fb4d33ab452ea254e2c319bac5703d1b56d895bf/drivers/gpu/drm/radeon/radeon_kms.c#L231
+
+ if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {
+ .request = RADEON_INFO_ACTIVE_CU_COUNT,
+ .value = (uintptr_t) &value,
+ }) >= 0) {
+ gpu->coreCount = (int32_t) value;
+ }
+
+ if (options->temp) {
+ if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {
+ .request = RADEON_INFO_CURRENT_GPU_TEMP, // millidegrees C
+ .value = (uintptr_t) &value,
+ }) >= 0 &&
+ value != 0) { // 0 means unavailable
+ gpu->temperature = (double) value / 1000.0;
+ }
+ }
+
+ if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {
+ .request = RADEON_INFO_MAX_SCLK, // MHz
+ .value = (uintptr_t) &value,
+ }) >= 0) {
+ gpu->frequency = (uint32_t) (value / 1000u);
+ }
+
+ if (options->driverSpecific) {
+ struct drm_radeon_gem_info gemInfo;
+ if (ioctl(fd, DRM_IOCTL_RADEON_GEM_INFO, &gemInfo) >= 0) {
+ // vram_usage can be bigger than vram_usage, so we use vram_size here
+ gpu->dedicated.total = gemInfo.vram_size;
+ gpu->shared.total = gemInfo.gart_size;
+
+ uint64_t memSize;
+ if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {
+ .request = RADEON_INFO_VRAM_USAGE, // uint64_t
+ .value = (uintptr_t) &memSize,
+ }) >= 0) {
+ gpu->dedicated.used = memSize;
+ }
+
+ if (ioctl(fd, DRM_IOCTL_RADEON_INFO, &(struct drm_radeon_info) {
+ .request = RADEON_INFO_GTT_USAGE, // uint64_t
+ .value = (uintptr_t) &memSize,
+ }) >= 0) {
+ gpu->shared.used = memSize;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+ #ifdef FF_HAVE_DRM_AMDGPU
+ #include <amdgpu.h>
+ #include <amdgpu_drm.h>
+
+const char* ffDrmDetectAmdgpu(const FFGPUOptions* options, FFGPUResult* gpu, const char* renderPath) {
+ #if FF_HAVE_DRM_AMDGPU
+ FF_LIBRARY_LOAD_MESSAGE(libdrm, "libdrm_amdgpu" FF_LIBRARY_EXTENSION, 1)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_device_initialize)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_get_marketing_name)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_gpu_info)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_sensor_info)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_query_heap_info)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libdrm, amdgpu_device_deinitialize)
+
+ FF_AUTO_CLOSE_FD int fd = open(renderPath, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ return "Failed to open DRM render device";
+ }
+
+ amdgpu_device_handle handle;
+ uint32_t majorVersion, minorVersion;
+ if (ffamdgpu_device_initialize(fd, &majorVersion, &minorVersion, &handle) < 0) {
+ return "Failed to initialize AMDGPU device";
+ }
+
+ uint32_t value;
+
+ if (options->temp) {
+ if (ffamdgpu_query_sensor_info(handle, AMDGPU_INFO_SENSOR_GPU_TEMP, sizeof(value), &value) >= 0) {
+ gpu->temperature = value / 1000.;
+ }
+ }
+
+ ffStrbufSetS(&gpu->name, ffamdgpu_get_marketing_name(handle));
+
+ struct amdgpu_gpu_info gpuInfo;
+ if (ffamdgpu_query_gpu_info(handle, &gpuInfo) >= 0) {
+ gpu->coreCount = (int32_t) gpuInfo.cu_active_number;
+ gpu->frequency = (uint32_t) (gpuInfo.max_engine_clk / 1000u);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->type = gpuInfo.ids_flags & AMDGPU_IDS_FLAGS_FUSION ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
+ #define FF_VRAM_CASE(name, value) \
+ case value /* AMDGPU_VRAM_TYPE_ ## name */: \
+ ffStrbufSetStatic(&gpu->memoryType, #name); \
+ break
+ switch (gpuInfo.vram_type) {
+ FF_VRAM_CASE(UNKNOWN, 0);
+ FF_VRAM_CASE(GDDR1, 1);
+ FF_VRAM_CASE(DDR2, 2);
+ FF_VRAM_CASE(GDDR3, 3);
+ FF_VRAM_CASE(GDDR4, 4);
+ FF_VRAM_CASE(GDDR5, 5);
+ FF_VRAM_CASE(HBM, 6);
+ FF_VRAM_CASE(DDR3, 7);
+ FF_VRAM_CASE(DDR4, 8);
+ FF_VRAM_CASE(GDDR6, 9);
+ FF_VRAM_CASE(DDR5, 10);
+ FF_VRAM_CASE(LPDDR4, 11);
+ FF_VRAM_CASE(LPDDR5, 12);
+ default:
+ ffStrbufAppendF(&gpu->memoryType, "Unknown (%u)", gpuInfo.vram_type);
+ break;
+ }
+
+ struct amdgpu_heap_info heapInfo;
+ if (ffamdgpu_query_heap_info(handle, AMDGPU_GEM_DOMAIN_VRAM, 0, &heapInfo) >= 0) {
+ gpu->dedicated.total = heapInfo.heap_size;
+ gpu->dedicated.used = heapInfo.heap_usage;
+ }
+ if (ffamdgpu_query_heap_info(handle, AMDGPU_GEM_DOMAIN_GTT, 0, &heapInfo) >= 0) {
+ gpu->shared.total = heapInfo.heap_size;
+ gpu->shared.used = heapInfo.heap_usage;
+ }
+ }
+
+ if (ffamdgpu_query_sensor_info(handle, AMDGPU_INFO_SENSOR_GPU_LOAD, sizeof(value), &value) >= 0) {
+ gpu->coreUsage = value;
+ }
+
+ ffamdgpu_device_deinitialize(handle);
+
+ return NULL;
+ #else
+ FF_UNUSED(options, gpu, renderPath);
+ return "Fastfetch is compiled without libdrm support";
+ #endif
+}
+ #endif
+
+const char* ffDrmDetectI915(FFGPUResult* gpu, int fd) {
+ {
+ int value;
+ drm_i915_getparam_t getparam = { .param = I915_PARAM_EU_TOTAL, .value = &value };
+ if (ioctl(fd, DRM_IOCTL_I915_GETPARAM, &getparam) >= 0) {
+ gpu->coreCount = value;
+ }
+ }
+ {
+ struct drm_i915_query_item queryItem = {
+ .query_id = DRM_I915_QUERY_MEMORY_REGIONS,
+ };
+ struct drm_i915_query query = {
+ .items_ptr = (uintptr_t) &queryItem,
+ .num_items = 1,
+ };
+ if (ioctl(fd, DRM_IOCTL_I915_QUERY, &query) >= 0 && queryItem.length > 0) { // #2259
+ FF_AUTO_FREE uint8_t* buffer = calloc(1, (size_t) queryItem.length);
+ queryItem.data_ptr = (uintptr_t) buffer;
+ if (ioctl(fd, DRM_IOCTL_I915_QUERY, &query) >= 0) {
+ gpu->dedicated.total = gpu->shared.total = gpu->dedicated.used = gpu->shared.used = 0;
+ struct drm_i915_query_memory_regions* regionInfo = (void*) buffer;
+ for (uint32_t i = 0; i < regionInfo->num_regions; i++) {
+ struct drm_i915_memory_region_info* region = regionInfo->regions + i;
+ switch (region->region.memory_class) {
+ case I915_MEMORY_CLASS_SYSTEM:
+ gpu->shared.total += region->probed_size;
+ gpu->shared.used += region->probed_size - region->unallocated_size;
+ break;
+ case I915_MEMORY_CLASS_DEVICE:
+ gpu->dedicated.total += region->probed_size;
+ gpu->dedicated.used += region->probed_size - region->unallocated_size;
+ break;
+ }
+ }
+ }
+ }
+ }
+ return NULL;
+}
+
+static inline int popcountBytes(uint8_t* bytes, uint32_t length) {
+ int count = 0;
+ while (length >= 8) {
+ count += __builtin_popcountll(*(uint64_t*) bytes);
+ bytes += 8;
+ length -= 8;
+ }
+ if (length >= 4) {
+ count += __builtin_popcountl(*(uint32_t*) bytes);
+ bytes += 4;
+ length -= 4;
+ }
+ if (length >= 2) {
+ count += __builtin_popcountl(*(uint16_t*) bytes);
+ bytes += 2;
+ length -= 2;
+ }
+ if (length) {
+ count += __builtin_popcountl(*(uint8_t*) bytes);
+ }
+ return count;
+}
+
+const char* ffDrmDetectXe(FFGPUResult* gpu, int fd) {
+ bool flag = false;
+ {
+ struct drm_xe_device_query query = {
+ .query = DRM_XE_DEVICE_QUERY_GT_TOPOLOGY,
+ };
+ if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) {
+ FF_AUTO_FREE uint8_t* buffer = malloc(query.size);
+ query.data = (uintptr_t) buffer;
+ if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) {
+ int dssCount = 0, euPerDssCount = 0;
+ for (struct drm_xe_query_topology_mask* topo = (void*) buffer;
+ (uint8_t*) topo < buffer + query.size;
+ topo = (void*) (topo->mask + topo->num_bytes)) {
+ switch (topo->type) {
+ case DRM_XE_TOPO_DSS_COMPUTE:
+ case DRM_XE_TOPO_DSS_GEOMETRY:
+ dssCount += popcountBytes(topo->mask, topo->num_bytes);
+ break;
+ case DRM_XE_TOPO_EU_PER_DSS:
+ euPerDssCount += popcountBytes(topo->mask, topo->num_bytes);
+ break;
+ }
+ }
+ gpu->coreCount = dssCount * euPerDssCount;
+ flag = true;
+ }
+ }
+ }
+
+ {
+ struct drm_xe_device_query query = {
+ .query = DRM_XE_DEVICE_QUERY_MEM_REGIONS,
+ };
+ if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) {
+ FF_AUTO_FREE uint8_t* buffer = malloc(query.size);
+ query.data = (uintptr_t) buffer;
+ if (ioctl(fd, DRM_IOCTL_XE_DEVICE_QUERY, &query) >= 0) {
+ gpu->dedicated.total = gpu->shared.total = gpu->dedicated.used = gpu->shared.used = 0;
+ struct drm_xe_query_mem_regions* regionInfo = (void*) buffer;
+ for (uint32_t i = 0; i < regionInfo->num_mem_regions; i++) {
+ struct drm_xe_mem_region* region = regionInfo->mem_regions + i;
+ switch (region->mem_class) {
+ case DRM_XE_MEM_REGION_CLASS_SYSMEM:
+ gpu->shared.total += region->total_size;
+ gpu->shared.used += region->used;
+ break;
+ case DRM_XE_MEM_REGION_CLASS_VRAM:
+ gpu->dedicated.total += region->total_size;
+ gpu->dedicated.used += region->used;
+ break;
+ }
+ }
+ flag = true;
+ }
+ }
+ }
+ return flag ? NULL : "Failed to query Xe GPU information";
+}
+
+const char* ffDrmDetectAsahi(FFGPUResult* gpu, int fd) {
+ struct drm_asahi_params_global paramsGlobal = {};
+ if (ioctl(fd, DRM_IOCTL_ASAHI_GET_PARAMS, &(struct drm_asahi_get_params) {
+ .param_group = DRM_ASAHI_GET_PARAMS,
+ .pointer = (uintptr_t) &paramsGlobal,
+ .size = sizeof(paramsGlobal),
+ }) >= 0) {
+ // They removed `unstable_uabi_version` from the struct. Hopefully they won't introduce new ABI changes.
+ gpu->coreCount = (int32_t) (paramsGlobal.num_clusters_total * paramsGlobal.num_cores_per_cluster);
+ gpu->frequency = paramsGlobal.max_frequency_khz / 1000;
+ gpu->deviceId = ffGPUGeneral2Id(paramsGlobal.chip_id);
+
+ if (!gpu->name.length) {
+ const char* variant = " Unknown";
+ switch (paramsGlobal.gpu_variant) {
+ case 'G':
+ variant = "";
+ break;
+ case 'S':
+ variant = " Pro";
+ break;
+ case 'C':
+ variant = " Max";
+ break;
+ case 'D':
+ variant = " Ultra";
+ break;
+ }
+ ffStrbufSetF(&gpu->name, "Apple M%d%s (G%d%c %02X)", paramsGlobal.gpu_generation - 12, variant, paramsGlobal.gpu_generation, paramsGlobal.gpu_variant, paramsGlobal.gpu_revision + 0xA0);
+ }
+
+ return NULL;
+ }
+
+ return "Failed to query Asahi GPU information";
+}
+
+ #ifndef DRM_IOCTL_NOUVEAU_GETPARAM
+ #define DRM_IOCTL_NOUVEAU_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_NOUVEAU_GETPARAM, struct drm_nouveau_getparam)
+ #endif
+
+const char* ffDrmDetectNouveau(FFGPUResult* gpu, int fd) {
+ struct drm_nouveau_getparam getparam = {};
+
+ getparam.param = NOUVEAU_GETPARAM_FB_SIZE;
+ if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0) {
+ gpu->dedicated.total = getparam.value;
+ }
+
+ getparam.param = NOUVEAU_GETPARAM_AGP_SIZE;
+ if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0) {
+ gpu->shared.total = getparam.value;
+ }
+
+ getparam.param = NOUVEAU_GETPARAM_GRAPH_UNITS;
+ if (ioctl(fd, DRM_IOCTL_NOUVEAU_GETPARAM, &getparam) == 0 && getparam.value < INT32_MAX) {
+ gpu->coreCount = (int32_t) getparam.value;
+ }
+
+ return NULL;
+}
+
+#endif // FF_HAVE_DRM
+
+#include "gpu_driver_specific.h"
+
+const char* ffGPUDetectDriverSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFGpuDriverPciBusId pciBusId) {
+ __typeof__(&ffDetectNvidiaGpuInfo) detectFn;
+ const char* soName;
+ if (getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &soName) && (options->temp || options->driverSpecific)) {
+ return detectFn(&(FFGpuDriverCondition) {
+ .type = FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID,
+ .pciBusId = pciBusId,
+ },
+ (FFGpuDriverResult) {
+ .index = &gpu->index,
+ .temp = options->temp ? &gpu->temperature : NULL,
+ .memory = options->driverSpecific ? &gpu->dedicated : NULL,
+ .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL,
+ .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL,
+ .type = &gpu->type,
+ .frequency = options->driverSpecific ? &gpu->frequency : NULL,
+ .name = &gpu->name,
+ },
+ soName);
+ }
+
+ return "No driver-specific detection function found for the GPU vendor";
+}
diff --git a/src/detection/gpu/gpu_gnu.c b/src/detection/gpu/gpu_gnu.c
new file mode 100644
index 0000000..27c774b
--- /dev/null
+++ b/src/detection/gpu/gpu_gnu.c
@@ -0,0 +1,146 @@
+#include "gpu.h"
+#include "common/io.h"
+
+#include <hurd.h>
+#include <hurd/pci.h>
+#include <hurd/paths.h>
+
+enum {
+ PCI_VENDOR_ID = 0x00,
+ PCI_DEVICE_ID = 0x02,
+ PCI_REVISION_ID = 0x08,
+ PCI_CLASS_PROG = 0x09,
+ PCI_SUBCLASS = 0x0a,
+ PCI_CLASS_DEVICE = 0x0b,
+ PCI_CONF_SIZE = 0x40,
+};
+
+const char* ffDetectGPUImpl(FF_A_UNUSED const FFGPUOptions* options, FFlist* gpus) {
+ int dDomainFd = open(_SERVERS_BUS "/pci/0000", O_RDONLY | O_CLOEXEC);
+ if (dDomainFd < 0) {
+ return "open(_SERVERS_BUS \"/pci/0000\") failed";
+ }
+
+ FF_AUTO_CLOSE_DIR DIR* dirDomain = fdopendir(dDomainFd);
+ if (dirDomain == NULL) {
+ return "fdopendir(domain) failed";
+ }
+
+ struct dirent* busEntry;
+ while ((busEntry = readdir(dirDomain)) != NULL) {
+ if (busEntry->d_type != DT_DIR || busEntry->d_name[0] == '.') {
+ continue;
+ }
+
+ char* endptr;
+ uint16_t pciBus = (uint16_t) strtoul(busEntry->d_name, &endptr, 16);
+ if (*endptr != '\0') {
+ continue;
+ }
+
+ int dBusFd = openat(dDomainFd, busEntry->d_name, O_RDONLY | O_CLOEXEC);
+ if (dBusFd < 0) {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_DIR DIR* dirBus = fdopendir(dBusFd);
+ if (dirBus == NULL) {
+ continue;
+ }
+
+ struct dirent* devEntry;
+ while ((devEntry = readdir(dirBus)) != NULL) {
+ if (devEntry->d_type != DT_DIR || devEntry->d_name[0] == '.') {
+ continue;
+ }
+
+ uint8_t pciDev = (uint8_t) strtoul(devEntry->d_name, &endptr, 16);
+ if (*endptr != '\0') {
+ continue;
+ }
+
+ int dDevFd = openat(dBusFd, devEntry->d_name, O_RDONLY | O_CLOEXEC);
+ if (dDevFd < 0) {
+ continue;
+ }
+
+ FF_AUTO_CLOSE_DIR DIR* dirDev = fdopendir(dDevFd);
+ if (dirDev == NULL) {
+ continue;
+ }
+
+ struct dirent* funcEntry;
+ while ((funcEntry = readdir(dirDev)) != NULL) {
+ if (funcEntry->d_type != DT_DIR || funcEntry->d_name[0] == '.') {
+ continue;
+ }
+
+ uint8_t pciFunc = (uint8_t) strtoul(funcEntry->d_name, &endptr, 16);
+ if (*endptr != '\0') {
+ continue;
+ }
+
+ char subpath[PATH_MAX];
+ snprintf(subpath, ARRAY_SIZE(subpath), "%s/%s/%s/%s/config", _SERVERS_BUS "/pci/0000", busEntry->d_name, devEntry->d_name, funcEntry->d_name);
+
+ mach_port_t devicePort = file_name_lookup(subpath, 0, 0);
+ if (devicePort == MACH_PORT_NULL) {
+ continue;
+ }
+
+ mach_msg_type_number_t nread = 0;
+
+ uint8_t data[PCI_CONF_SIZE];
+ data_t pData = (data_t) data;
+ kern_return_t kr = pci_conf_read(devicePort, 0, &pData, &nread, PCI_CONF_SIZE);
+ mach_port_deallocate(mach_task_self(), devicePort);
+ if (kr != KERN_SUCCESS || nread < PCI_CONF_SIZE) {
+ continue;
+ }
+
+ if (pData != (data_t) data) {
+ memcpy(data, pData, PCI_CONF_SIZE);
+ vm_deallocate(mach_task_self(), (vm_address_t) pData, nread);
+ }
+
+ uint8_t classBase = data[PCI_CLASS_DEVICE];
+ if (classBase != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) {
+ continue;
+ }
+
+ uint8_t classSub = data[PCI_SUBCLASS];
+ if (pciFunc > 0 && classSub == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) { // Likely an auxiliary display controller (#2034)
+ continue;
+ }
+
+ uint8_t revision = data[PCI_REVISION_ID];
+ uint16_t vendorId = data[PCI_VENDOR_ID] | (data[PCI_VENDOR_ID + 1] << 8);
+ uint16_t deviceId = data[PCI_DEVICE_ID] | (data[PCI_DEVICE_ID + 1] << 8);
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(vendorId));
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInitStatic(&gpu->platformApi, "/servers/bus/pci");
+ ffStrbufInit(&gpu->memoryType);
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = ffGPUPciAddr2Id(0, pciBus, pciDev, pciFunc);
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ ffGPUQueryAmdGpuName(deviceId, revision, gpu);
+ }
+
+ if (gpu->name.length == 0) {
+ ffGPUFillVendorAndName(classSub, vendorId, deviceId, gpu);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_haiku.c b/src/detection/gpu/gpu_haiku.c
new file mode 100644
index 0000000..f75f039
--- /dev/null
+++ b/src/detection/gpu/gpu_haiku.c
@@ -0,0 +1,51 @@
+#include "gpu.h"
+#include "common/io.h"
+
+#include <private/drivers/poke.h>
+
+const char* ffDetectGPUImpl(FF_A_UNUSED const FFGPUOptions* options, FFlist* gpus) {
+ FF_AUTO_CLOSE_FD int pokefd = open(POKE_DEVICE_FULLNAME, O_RDWR | O_CLOEXEC);
+ if (pokefd < 0) {
+ return "open(POKE_DEVICE_FULLNAME) failed";
+ }
+
+ pci_info dev;
+ pci_info_args cmd = {
+ .signature = POKE_SIGNATURE,
+ .info = &dev,
+ };
+
+ for (cmd.index = 0; ioctl(pokefd, POKE_GET_NTH_PCI_INFO, &cmd, sizeof(cmd)) == B_OK && cmd.status == B_OK; ++cmd.index) {
+ if (dev.class_base != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) {
+ continue;
+ }
+
+ if (dev.function > 0 && dev.class_sub == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) {
+ continue; // Likely an auxiliary display controller (#2034)
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(dev.vendor_id));
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInitStatic(&gpu->platformApi, POKE_DEVICE_FULLNAME);
+ ffStrbufInit(&gpu->memoryType);
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = ffGPUPciAddr2Id(0, dev.bus, dev.device, dev.function);
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ ffGPUQueryAmdGpuName(dev.device_id, dev.revision, gpu);
+ }
+
+ if (gpu->name.length == 0) {
+ ffGPUFillVendorAndName(dev.class_sub, dev.vendor_id, dev.device_id, gpu);
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_intel.c b/src/detection/gpu/gpu_intel.c
new file mode 100644
index 0000000..7d94c80
--- /dev/null
+++ b/src/detection/gpu/gpu_intel.c
@@ -0,0 +1,231 @@
+#include "gpu_driver_specific.h"
+
+#include "common/library.h"
+#include "common/mallocHelper.h"
+#include "igcl.h"
+
+struct FFIgclData {
+ FF_LIBRARY_SYMBOL(ctlClose)
+
+ FF_LIBRARY_SYMBOL(ctlEnumerateDevices)
+ FF_LIBRARY_SYMBOL(ctlGetDeviceProperties)
+ FF_LIBRARY_SYMBOL(ctlEnumTemperatureSensors)
+ FF_LIBRARY_SYMBOL(ctlTemperatureGetProperties)
+ FF_LIBRARY_SYMBOL(ctlEnumMemoryModules)
+ FF_LIBRARY_SYMBOL(ctlMemoryGetProperties)
+ FF_LIBRARY_SYMBOL(ctlMemoryGetState)
+ FF_LIBRARY_SYMBOL(ctlEnumFrequencyDomains)
+ FF_LIBRARY_SYMBOL(ctlFrequencyGetProperties)
+
+ bool inited;
+ ctl_api_handle_t apiHandle;
+} igclData;
+
+static void shutdownIgcl() {
+ if (igclData.apiHandle) {
+ igclData.ffctlClose(igclData.apiHandle);
+ igclData.apiHandle = NULL;
+ }
+}
+
+const char* ffDetectIntelGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) {
+ if (!igclData.inited) {
+ igclData.inited = true;
+ FF_LIBRARY_LOAD(libigcl, "dlopen igcl (ControlLib) failed", soName, 1);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libigcl, ctlInit)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlClose)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumerateDevices)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlGetDeviceProperties)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumTemperatureSensors)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlTemperatureGetProperties)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumMemoryModules)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetProperties)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlMemoryGetState)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlEnumFrequencyDomains)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libigcl, igclData, ctlFrequencyGetProperties)
+
+ if (ffctlInit(&(ctl_init_args_t) {
+ .AppVersion = CTL_IMPL_VERSION,
+ .flags = CTL_INIT_FLAG_USE_LEVEL_ZERO,
+ .Size = sizeof(ctl_init_args_t),
+ .Version = 0,
+ },
+ &igclData.apiHandle) != CTL_RESULT_SUCCESS) {
+ return "loading igcl library failed";
+ }
+ atexit(shutdownIgcl);
+ libigcl = NULL; // don't close igcl
+ }
+
+ if (!igclData.apiHandle) {
+ return "loading igcl library failed";
+ }
+
+ uint32_t deviceCount = 0;
+ if (igclData.ffctlEnumerateDevices(igclData.apiHandle, &deviceCount, NULL)) {
+ return "ctlEnumerateDevices(NULL) failed";
+ }
+ if (deviceCount == 0) {
+ return "No Intel graphics adapter found";
+ }
+
+ FF_AUTO_FREE ctl_device_adapter_handle_t* devices = malloc(deviceCount * sizeof(*devices));
+ if (igclData.ffctlEnumerateDevices(igclData.apiHandle, &deviceCount, devices)) {
+ return "ctlEnumerateDevices(devices) failed";
+ }
+
+ ctl_device_adapter_handle_t device = NULL;
+
+ uint64_t /* LUID */ deviceId = 0;
+ ctl_device_adapter_properties_t properties = {
+ .Size = sizeof(properties),
+ .pDeviceID = &deviceId,
+ .device_id_size = sizeof(deviceId),
+ .Version = 2,
+ };
+ for (uint32_t iDev = 0; iDev < deviceCount; iDev++) {
+ if (igclData.ffctlGetDeviceProperties(devices[iDev], &properties) != CTL_RESULT_SUCCESS) {
+ continue;
+ }
+
+ if (properties.device_type != CTL_DEVICE_TYPE_GRAPHICS) {
+ continue;
+ }
+
+ if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) {
+ if (cond->pciBusId.bus == properties.adapter_bdf.bus &&
+ cond->pciBusId.device == properties.adapter_bdf.device &&
+ cond->pciBusId.func == properties.adapter_bdf.function) {
+ device = devices[iDev];
+ break;
+ }
+ } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_LUID) {
+ if (cond->luid == deviceId) {
+ device = devices[iDev];
+ break;
+ }
+ } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID) {
+ if (
+ cond->pciDeviceId.deviceId == properties.pci_device_id &&
+ cond->pciDeviceId.vendorId == properties.pci_vendor_id &&
+ cond->pciDeviceId.subSystemId == (uint32_t) ((properties.pci_subsys_id << 16u) | properties.pci_subsys_vendor_id) &&
+ cond->pciDeviceId.revId == properties.rev_id) {
+ device = devices[iDev];
+ break;
+ }
+ }
+ }
+
+ if (!device) {
+ return "Device not found";
+ }
+
+ if (result.coreCount) {
+ *result.coreCount = properties.num_slices * properties.num_sub_slices_per_slice * properties.num_eus_per_sub_slice;
+ }
+
+ if (result.memory) {
+ ctl_mem_handle_t memoryModules[16];
+ uint32_t memoryCount = ARRAY_SIZE(memoryModules);
+ if (igclData.ffctlEnumMemoryModules(device, &memoryCount, memoryModules) == CTL_RESULT_SUCCESS && memoryCount > 0) {
+ result.memory->used = 0;
+ result.memory->total = 0;
+ for (uint32_t iMem = 0; iMem < memoryCount; iMem++) {
+ ctl_mem_properties_t memoryProperties = {
+ .Size = sizeof(memoryProperties),
+ .Version = 0,
+ };
+ if (igclData.ffctlMemoryGetProperties(memoryModules[iMem], &memoryProperties) == CTL_RESULT_SUCCESS) {
+ if (memoryProperties.location == CTL_MEM_LOC_DEVICE && result.memoryType) {
+ switch (memoryProperties.type) {
+#define FF_ICTL_MEM_TYPE_CASE(type) \
+ case CTL_MEM_TYPE_##type: \
+ ffStrbufSetStatic(result.memoryType, #type); \
+ break
+ FF_ICTL_MEM_TYPE_CASE(HBM);
+ FF_ICTL_MEM_TYPE_CASE(DDR);
+ FF_ICTL_MEM_TYPE_CASE(DDR3);
+ FF_ICTL_MEM_TYPE_CASE(DDR4);
+ FF_ICTL_MEM_TYPE_CASE(DDR5);
+ FF_ICTL_MEM_TYPE_CASE(LPDDR);
+ FF_ICTL_MEM_TYPE_CASE(LPDDR3);
+ FF_ICTL_MEM_TYPE_CASE(LPDDR4);
+ FF_ICTL_MEM_TYPE_CASE(LPDDR5);
+ FF_ICTL_MEM_TYPE_CASE(GDDR4);
+ FF_ICTL_MEM_TYPE_CASE(GDDR5);
+ FF_ICTL_MEM_TYPE_CASE(GDDR5X);
+ FF_ICTL_MEM_TYPE_CASE(GDDR6);
+ FF_ICTL_MEM_TYPE_CASE(GDDR6X);
+ FF_ICTL_MEM_TYPE_CASE(GDDR7);
+#undef FF_ICTL_MEM_TYPE_CASE
+ default:
+ ffStrbufSetF(result.memoryType, "Unknown (%u)", memoryProperties.type);
+ break;
+ }
+ }
+
+ ctl_mem_state_t memoryState = {
+ .Size = sizeof(ctl_mem_state_t),
+ .Version = 0,
+ };
+ if (igclData.ffctlMemoryGetState(memoryModules[iMem], &memoryState) == CTL_RESULT_SUCCESS) {
+ if (memoryProperties.location == CTL_MEM_LOC_DEVICE) {
+ result.memory->total += memoryState.size;
+ result.memory->used += memoryState.size - memoryState.free;
+ } else if (result.sharedMemory && memoryProperties.location == CTL_MEM_LOC_SYSTEM) {
+ result.sharedMemory->total += memoryState.size;
+ result.sharedMemory->used += memoryState.size - memoryState.free;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (result.type) {
+ *result.type = properties.graphics_adapter_properties & CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED
+ ? FF_GPU_TYPE_INTEGRATED
+ : FF_GPU_TYPE_DISCRETE;
+ }
+
+ if (result.temp) {
+ ctl_temp_handle_t sensors[16];
+ uint32_t sensorCount = ARRAY_SIZE(sensors);
+ if (igclData.ffctlEnumTemperatureSensors(device, &sensorCount, sensors) == CTL_RESULT_SUCCESS && sensorCount > 0) {
+ for (uint32_t iSensor = 0; iSensor < sensorCount; iSensor++) {
+ ctl_temp_properties_t props = { .Size = sizeof(props) };
+ // The official sample code does not set Version
+ // https://github.com/intel/drivers.gpu.control-library/blob/1bbacbf3814f2fd0d2b930cdf42fad83f3628db9/Samples/Telemetry_Samples/Sample_TelemetryAPP.cpp#L256
+ if (igclData.ffctlTemperatureGetProperties(sensors[iSensor], &props) == CTL_RESULT_SUCCESS) {
+ if (props.type == CTL_TEMP_SENSORS_GPU) {
+ *result.temp = props.maxTemperature;
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (result.frequency) {
+ ctl_freq_handle_t domains[16];
+ uint32_t domainCount = ARRAY_SIZE(domains);
+ if (igclData.ffctlEnumFrequencyDomains(device, &domainCount, domains) == CTL_RESULT_SUCCESS && domainCount > 0) {
+ double maxValue = 0;
+ ctl_freq_properties_t props = { .Size = sizeof(props), .Version = 0 };
+ for (uint32_t iDomain = 0; iDomain < domainCount; iDomain++) {
+ if (igclData.ffctlFrequencyGetProperties(domains[iDomain], &props) == CTL_RESULT_SUCCESS) {
+ if (props.type == CTL_FREQ_DOMAIN_GPU && props.max > maxValue) {
+ maxValue = props.max;
+ }
+ }
+ }
+ *result.frequency = (uint32_t) (maxValue + 0.5);
+ }
+ }
+
+ if (result.name) {
+ ffStrbufSetS(result.name, properties.name);
+ }
+
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_linux.c b/src/detection/gpu/gpu_linux.c
new file mode 100644
index 0000000..3aaa09c
--- /dev/null
+++ b/src/detection/gpu/gpu_linux.c
@@ -0,0 +1,663 @@
+#include "detection/gpu/gpu.h"
+#include "detection/vulkan/vulkan.h"
+#include "detection/cpu/cpu.h"
+#include "detection/gpu/gpu_driver_specific.h"
+#include "common/io.h"
+#include "common/library.h"
+#include "common/FFstrbuf.h"
+#include "common/strutil.h"
+#include "common/mallocHelper.h"
+#include "modules/gpu/option.h"
+
+#include <inttypes.h>
+#include <stdint.h>
+
+#ifdef FF_HAVE_DRM_AMDGPU
+ #include <amdgpu.h>
+ #include <amdgpu_drm.h>
+ #include <fcntl.h>
+#endif
+
+#ifdef FF_HAVE_DRM
+ #include "intel_drm.h"
+ #include <fcntl.h>
+ #include <sys/ioctl.h>
+#endif
+
+#if defined(FF_HAVE_DRM) && defined(__aarch64__)
+ // https://github.com/alyssarosenzweig/linux/blob/agx-uapi-v7/include/uapi/drm/asahi_drm.h
+ // Found in kernel-headers-6.14.4-400.asahi.fc42.aarch64
+ #if __has_include(<drm/asahi_drm.h>)
+ #include <drm/asahi_drm.h>
+ #else
+ #include "asahi_drm.h"
+ #endif
+ #define FF_HAVE_DRM_ASAHI 1
+#endif
+
+static bool pciDetectDriver(FFstrbuf* result, FFstrbuf* pciDir, FFstrbuf* buffer, FF_A_UNUSED const char* drmKey) {
+ uint32_t pciDirLength = pciDir->length;
+ ffStrbufAppendS(pciDir, "/driver");
+ char pathBuf[PATH_MAX];
+ ssize_t resultLength = readlink(pciDir->chars, pathBuf, ARRAY_SIZE(pathBuf));
+ if (resultLength <= 0) {
+ return false;
+ }
+
+ const char* slash = memrchr(pathBuf, '/', (size_t) resultLength);
+ if (slash) {
+ slash++;
+ ffStrbufSetNS(result, (uint32_t) (resultLength - (slash - pathBuf)), slash);
+ }
+
+ if (ffStrbufEqualS(result, "nvidia")) {
+ if (ffReadFileBuffer("/proc/driver/nvidia/version", buffer)) {
+ if (ffStrbufContainS(buffer, " Open ")) {
+ ffStrbufAppendS(result, " (open source)");
+ } else {
+ ffStrbufAppendS(result, " (proprietary)");
+ }
+ }
+ }
+
+ if (instance.config.general.detectVersion) {
+ ffStrbufAppendS(pciDir, "/module/version");
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ ffStrbufTrimRightSpace(buffer);
+ ffStrbufAppendC(result, ' ');
+ ffStrbufAppend(result, buffer);
+ } else if (ffStrbufEqualS(result, "zx")) {
+ ffStrbufSubstrBefore(pciDir, pciDirLength);
+ ffStrbufAppendS(pciDir, "/zx_info/driver_version");
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ ffStrbufTrimRightSpace(buffer);
+ ffStrbufAppendC(result, ' ');
+ ffStrbufAppend(result, buffer);
+ }
+ }
+ }
+
+ return true;
+}
+
+FF_A_UNUSED static const char* drmFindRenderFromCard(const char* drmCardKey, FFstrbuf* result) {
+ char path[PATH_MAX];
+ sprintf(path, "/sys/class/drm/%s/device/drm", drmCardKey);
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(path);
+ if (!dirp) {
+ return "Failed to open `/sys/class/drm/{drmCardKey}/device/drm`";
+ }
+
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (ffStrStartsWith(entry->d_name, "render")) {
+ ffStrbufSetS(result, "/dev/dri/");
+ ffStrbufAppendS(result, entry->d_name);
+ return NULL;
+ }
+ }
+ return "Failed to find render device";
+}
+
+static const char* drmDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) {
+#if FF_HAVE_DRM
+ const char* error = drmFindRenderFromCard(drmKey, buffer);
+ if (error) {
+ return error;
+ }
+ if (ffStrbufEqualS(&gpu->driver, "radeon")) {
+ return ffDrmDetectRadeon(options, gpu, buffer->chars);
+ } else {
+ #if FF_HAVE_DRM_AMDGPU
+ return ffDrmDetectAmdgpu(options, gpu, buffer->chars);
+ #else
+ FF_UNUSED(options, gpu, drmKey, buffer);
+ return "Fastfetch is not compiled with libdrm_amdgpu support";
+ #endif
+ }
+#else
+ FF_UNUSED(options, gpu, drmKey, buffer);
+ return "Fastfetch is not compiled with drm support";
+#endif
+}
+
+static void pciDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) {
+ // https://www.kernel.org/doc/html/v5.10/gpu/amdgpu.html#mem-info-vis-vram-total
+ const uint32_t pciDirLen = pciDir->length;
+
+ ffStrbufAppendS(pciDir, "/hwmon/");
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);
+ if (!dirp) {
+ return;
+ }
+
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ break;
+ }
+ if (!entry) {
+ return;
+ }
+ ffStrbufAppendS(pciDir, entry->d_name);
+ ffStrbufAppendC(pciDir, '/');
+
+ const uint32_t hwmonLen = pciDir->length;
+ uint64_t value = 0;
+ if (options->temp) {
+ ffStrbufAppendS(pciDir, "temp1_input"); // The on die GPU temperature in millidegrees Celsius
+ if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
+ gpu->temperature = (double) value / 1000;
+ }
+ }
+
+ if (ffStrbufEqualS(&gpu->driver, "amdgpu")) // Ancient radeon drivers don't have these files
+ {
+ ffStrbufSubstrBefore(pciDir, hwmonLen);
+ ffStrbufAppendS(pciDir, "in1_input"); // Northbridge voltage in millivolts (APUs only)
+ if (ffPathExists(pciDir->chars, FF_PATHTYPE_ANY)) {
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+ } else {
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ }
+
+ if (options->driverSpecific) {
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+ ffStrbufAppendS(pciDir, "/mem_info_vis_vram_total");
+ if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
+ if (gpu->type == FF_GPU_TYPE_DISCRETE) {
+ gpu->dedicated.total = value;
+ } else {
+ gpu->shared.total = value;
+ }
+
+ ffStrbufSubstrBefore(pciDir, pciDir->length - (uint32_t) strlen("/mem_info_vis_vram_total"));
+ ffStrbufAppendS(pciDir, "/mem_info_vis_vram_used");
+ if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
+ if (gpu->type == FF_GPU_TYPE_DISCRETE) {
+ gpu->dedicated.used = value;
+ } else {
+ gpu->shared.used = value;
+ }
+ }
+ }
+
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+ ffStrbufAppendS(pciDir, "/gpu_busy_percent");
+ if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
+ gpu->coreUsage = (double) value;
+ }
+ }
+ }
+}
+
+static void pciDetectIntelSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer, const char* drmKey) {
+ // Works for Intel GPUs
+ // https://patchwork.kernel.org/project/intel-gfx/patch/1422039866-11572-3-git-send-email-ville.syrjala@linux.intel.com/
+
+ // 0000:00:02.0 is reserved for Intel integrated graphics
+ gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
+
+ if (!drmKey) {
+ return;
+ }
+
+ const uint32_t pciDirLen = pciDir->length;
+
+ bool isXE = ffStrbufEqualS(&gpu->driver, "xe");
+ if (isXE) {
+ ffStrbufAppendS(pciDir, "/tile0/gt0/freq0/max_freq");
+ } else {
+ ffStrbufAppendF(pciDir, "/drm/%s/gt_max_freq_mhz", drmKey);
+ }
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, 0);
+ }
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+
+ if (options->temp) {
+ ffStrbufAppendS(pciDir, "/hwmon/");
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);
+ if (dirp) {
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+
+ ffStrbufSubstrBefore(pciDir, pciDirLen + strlen("/hwmon/"));
+ ffStrbufAppendS(pciDir, entry->d_name);
+ // https://github.com/Syllo/nvtop/blob/73291884d926445e499d6b9b71cb7a9bdbc7c393/src/extract_gpuinfo_intel.c#L279-L281
+ ffStrbufAppendS(pciDir, isXE ? "/temp2_input" : "/temp1_input");
+
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ uint64_t value = ffStrbufToUInt(buffer, 0);
+ if (value > 0) {
+ gpu->temperature = (double) value / 1000;
+ break;
+ }
+ }
+ }
+ }
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+ }
+}
+
+static const char* drmDetectIntelSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) {
+#if FF_HAVE_DRM
+ ffStrbufSetS(buffer, "/dev/dri/");
+ ffStrbufAppendS(buffer, drmKey);
+ FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ return "Failed to open drm device";
+ }
+
+ if (ffStrbufEqualS(&gpu->driver, "xe")) {
+ return ffDrmDetectXe(gpu, fd);
+ } else if (ffStrbufEqualS(&gpu->driver, "i915")) {
+ return ffDrmDetectI915(gpu, fd);
+ }
+ return "Unknown Intel GPU driver";
+#else
+ FF_UNUSED(gpu, drmKey, buffer);
+ return "Fastfetch is not compiled with drm support";
+#endif
+}
+
+static const char* pciDetectTempGeneral(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) {
+ if (options->temp) {
+ const uint32_t pciDirLen = pciDir->length;
+ ffStrbufAppendS(pciDir, "/hwmon/");
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);
+ if (dirp) {
+ struct dirent* entry;
+ while ((entry = readdir(dirp))) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+ ffStrbufAppendS(pciDir, entry->d_name);
+ ffStrbufAppendS(pciDir, "/temp1_input");
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ uint64_t value = ffStrbufToUInt(buffer, 0);
+ if (value > 0) {
+ gpu->temperature = (double) value / 1000.0;
+ }
+ }
+ break;
+ }
+ }
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+ }
+ return NULL;
+}
+
+static const char* drmDetectNouveauSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) {
+#if FF_HAVE_DRM
+ ffStrbufSetS(buffer, "/dev/dri/");
+ ffStrbufAppendS(buffer, drmKey);
+ FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);
+ if (fd < 0) {
+ return "Failed to open drm device";
+ }
+
+ return ffDrmDetectNouveau(gpu, fd);
+#else
+ FF_UNUSED(gpu, drmKey, buffer);
+ return "Fastfetch is not compiled with drm support";
+#endif
+}
+
+static const char* pciDetectZxSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) {
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+
+ const uint32_t pciDirLen = pciDir->length;
+ ffStrbufAppendS(pciDir, "/zx_info/eclk");
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, FF_GPU_FREQUENCY_UNSET);
+ }
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+
+ if (options->driverSpecific) {
+ ffStrbufAppendS(pciDir, "/zx_info/engine_3d_usage");
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ gpu->coreUsage = ffStrbufToDouble(buffer, FF_GPU_CORE_USAGE_UNSET);
+ }
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+
+ ffStrbufAppendS(pciDir, "/zx_info/fb_size");
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ gpu->shared.total = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET);
+ }
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+
+ if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) {
+ gpu->shared.total *= 1024 * 1024;
+
+ ffStrbufAppendS(pciDir, "/zx_info/free_fb_mem");
+ if (ffReadFileBuffer(pciDir->chars, buffer)) {
+ gpu->shared.used = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET);
+ }
+ ffStrbufSubstrBefore(pciDir, pciDirLen);
+
+ if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) {
+ gpu->shared.used *= 1024 * 1024;
+ gpu->shared.used = gpu->shared.total - gpu->shared.used;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf* buffer, FFstrbuf* deviceDir, const char* drmKey) {
+ const uint32_t drmDirPathLength = deviceDir->length;
+ uint32_t vendorId, deviceId, subVendorId, subDeviceId;
+ uint8_t classId, subclassId;
+ if (sscanf(buffer->chars + strlen("pci:"), "v%8" SCNx32 "d%8" SCNx32 "sv%8" SCNx32 "sd%8" SCNx32 "bc%2" SCNx8 "sc%2" SCNx8, &vendorId, &deviceId, &subVendorId, &subDeviceId, &classId, &subclassId) != 6) {
+ return "Failed to parse pci modalias";
+ }
+
+ if (classId != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) {
+ return "Not a GPU device";
+ }
+
+ char pciPath[PATH_MAX];
+ const char* pPciPath = NULL;
+ if (drmKey) {
+ ssize_t pathLength = readlink(deviceDir->chars, pciPath, ARRAY_SIZE(pciPath) - 1);
+ if (pathLength <= 0) {
+ return "Unable to get PCI device path";
+ }
+ pciPath[pathLength] = '\0';
+ pPciPath = strrchr(pciPath, '/');
+ if (__builtin_expect(pPciPath != NULL, true)) {
+ pPciPath++;
+ } else {
+ pPciPath = pciPath;
+ }
+ } else {
+ pPciPath = memrchr(deviceDir->chars, '/', deviceDir->length);
+ assert(pPciPath);
+ pPciPath++;
+ }
+
+ uint32_t pciDomain, pciBus, pciDevice, pciFunc;
+ if (sscanf(pPciPath, "%" SCNx32 ":%" SCNx32 ":%" SCNx32 ".%" SCNx32, &pciDomain, &pciBus, &pciDevice, &pciFunc) != 4) {
+ return "Invalid PCI device path";
+ }
+
+ if (pciFunc > 0 && subclassId == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) {
+ return "Likely an auxiliary display controller"; // #2034
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString((uint16_t) vendorId));
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInit(&gpu->platformApi);
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = ffGPUPciAddr2Id(pciDomain, pciBus, pciDevice, pciFunc);
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ char drmKeyBuffer[8];
+ if (!drmKey) {
+ ffStrbufAppendS(deviceDir, "/drm");
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(deviceDir->chars);
+ if (dirp) {
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (ffStrStartsWith(entry->d_name, "card")) {
+ ffStrCopy(drmKeyBuffer, entry->d_name, ARRAY_SIZE(drmKeyBuffer));
+ drmKey = drmKeyBuffer;
+ break;
+ }
+ }
+ }
+ ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
+ }
+
+ if (drmKey) {
+ ffStrbufSetF(&gpu->platformApi, "DRM (%s)", drmKey);
+ }
+
+ pciDetectDriver(&gpu->driver, deviceDir, buffer, drmKey);
+ ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
+
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ bool ok = false;
+ if (drmKey && options->driverSpecific) {
+ ok = drmDetectAmdSpecific(options, gpu, drmKey, buffer) == NULL;
+ }
+
+ if (!ok) {
+ pciDetectAmdSpecific(options, gpu, deviceDir, buffer);
+ ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
+
+ ffStrbufAppendS(deviceDir, "/revision");
+ if (ffReadFileBuffer(deviceDir->chars, buffer)) {
+ char* pend;
+ uint64_t revision = strtoul(buffer->chars, &pend, 16);
+ if (pend != buffer->chars) {
+ ffGPUQueryAmdGpuName((uint16_t) deviceId, (uint8_t) revision, gpu);
+ }
+ }
+ ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
+ }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) {
+ pciDetectIntelSpecific(options, gpu, deviceDir, buffer, drmKey);
+ ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
+ if (options->driverSpecific && drmKey) {
+ drmDetectIntelSpecific(gpu, drmKey, buffer);
+ }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA && ffStrbufEqualS(&gpu->driver, "nouveau")) {
+ pciDetectTempGeneral(options, gpu, deviceDir, buffer);
+ if (options->driverSpecific && drmKey) {
+ drmDetectNouveauSpecific(gpu, drmKey, buffer);
+ }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_ZHAOXIN && ffStrbufStartsWithS(&gpu->driver, "zx")) {
+ pciDetectTempGeneral(options, gpu, deviceDir, buffer);
+ pciDetectZxSpecific(options, gpu, deviceDir, buffer);
+ } else {
+ ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId) {
+ .domain = pciDomain,
+ .bus = pciBus,
+ .device = pciDevice,
+ .func = pciFunc,
+ });
+ }
+
+ if (gpu->name.length == 0) {
+ ffGPUFillVendorAndName(subclassId, (uint16_t) vendorId, (uint16_t) deviceId, gpu);
+ }
+
+ if (gpu->type == FF_GPU_TYPE_UNKNOWN) {
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA) {
+ if (ffStrbufStartsWithIgnCaseS(&gpu->name, "GeForce") ||
+ ffStrbufStartsWithIgnCaseS(&gpu->name, "Quadro") ||
+ ffStrbufStartsWithIgnCaseS(&gpu->name, "Tesla")) {
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS) {
+ if (ffStrbufStartsWithIgnCaseS(&gpu->name, "MTT ")) {
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+#if __aarch64__
+
+FF_A_UNUSED static const char* drmDetectAsahiSpecific(FFGPUResult* gpu, const char* name, FF_A_UNUSED FFstrbuf* buffer, FF_A_UNUSED const char* drmKey) {
+ if (sscanf(name, "agx-t%lu", &gpu->deviceId) == 1) {
+ ffStrbufSetStatic(&gpu->name, ffCPUAppleCodeToName((uint32_t) gpu->deviceId));
+ }
+ ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE);
+
+ #if FF_HAVE_DRM_ASAHI
+ ffStrbufSetS(buffer, "/dev/dri/");
+ ffStrbufAppendS(buffer, drmKey);
+ FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);
+ if (fd >= 0) {
+ return ffDrmDetectAsahi(gpu, fd);
+ }
+ #endif
+
+ return NULL;
+}
+#endif
+
+static const char* detectOf(FFlist* gpus, FFstrbuf* buffer, FFstrbuf* drmDir, const char* drmKey) {
+ char compatible[256]; // vendor,model-name
+ if (sscanf(buffer->chars + strlen("of:"), "NgpuT%*[^C]C%255[^C]", compatible) != 1) {
+ return "Failed to parse of modalias or not a GPU device";
+ }
+
+ char* name = strchr(compatible, ',');
+ if (name) {
+ *name = '\0';
+ ++name;
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->deviceId = 0;
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->vendor);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInit(&gpu->memoryType);
+ ffStrbufInitF(&gpu->platformApi, "DRM (%s)", drmKey);
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ pciDetectDriver(&gpu->driver, drmDir, buffer, drmKey);
+
+#ifdef __aarch64__
+ if (ffStrbufEqualS(&gpu->driver, "asahi")) {
+ drmDetectAsahiSpecific(gpu, name, buffer, drmKey);
+ }
+#endif
+
+ if (!gpu->name.length) {
+ ffStrbufSetS(&gpu->name, name ?: compatible);
+ ffStrbufTrimRightSpace(&gpu->name);
+ }
+ if (!gpu->vendor.length && name) {
+ if (ffStrEquals(compatible, "brcm")) {
+ ffStrbufSetStatic(&gpu->vendor, "Broadcom"); // Raspberry Pi
+ } else {
+ ffStrbufSetS(&gpu->vendor, compatible);
+ gpu->vendor.chars[0] = (char) toupper(compatible[0]);
+ }
+ }
+
+ return NULL;
+}
+
+static const char* drmDetectGPUs(const FFGPUOptions* options, FFlist* gpus) {
+ FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);
+ ffStrbufAppendS(&drmDir, "/sys/class/drm/");
+ const uint32_t drmDirLength = drmDir.length;
+
+ FF_AUTO_CLOSE_DIR DIR* dir = opendir(drmDir.chars);
+ if (dir == NULL) {
+ return "Failed to open `/sys/class/drm/`";
+ }
+
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+
+ struct dirent* entry;
+ while ((entry = readdir(dir)) != NULL) {
+ if (!ffStrStartsWith(entry->d_name, "card") ||
+ strchr(entry->d_name + 4, '-') != NULL) {
+ continue;
+ }
+
+ ffStrbufAppendS(&drmDir, entry->d_name);
+
+ ffStrbufAppendS(&drmDir, "/device/modalias");
+ if (!ffReadFileBuffer(drmDir.chars, &buffer)) {
+ continue;
+ }
+ ffStrbufSubstrBefore(&drmDir, drmDir.length - (uint32_t) strlen("/modalias"));
+
+ if (ffStrbufStartsWithS(&buffer, "pci:")) {
+ detectPci(options, gpus, &buffer, &drmDir, entry->d_name);
+ } else if (ffStrbufStartsWithS(&buffer, "of:")) { // Open Firmware
+ detectOf(gpus, &buffer, &drmDir, entry->d_name);
+ }
+
+ ffStrbufSubstrBefore(&drmDir, drmDirLength);
+ }
+
+ return NULL;
+}
+
+static const char* pciDetectGPUs(const FFGPUOptions* options, FFlist* gpus) {
+ // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci
+ const char* pciDirPath = "/sys/bus/pci/devices/";
+
+ FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDirPath);
+ if (dirp == NULL) {
+ return "Failed to open `/sys/bus/pci/devices/`";
+ }
+
+ FF_STRBUF_AUTO_DESTROY pciDir = ffStrbufCreateA(64);
+ ffStrbufAppendS(&pciDir, pciDirPath);
+
+ const uint32_t pciBaseDirLength = pciDir.length;
+
+ FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
+
+ struct dirent* entry;
+ while ((entry = readdir(dirp)) != NULL) {
+ if (entry->d_name[0] == '.') {
+ continue;
+ }
+
+ ffStrbufSubstrBefore(&pciDir, pciBaseDirLength);
+ ffStrbufAppendS(&pciDir, entry->d_name);
+ const uint32_t pciDevDirLength = pciDir.length;
+
+ ffStrbufAppendS(&pciDir, "/modalias");
+ if (!ffReadFileBuffer(pciDir.chars, &buffer)) {
+ continue;
+ }
+ ffStrbufSubstrBefore(&pciDir, pciDevDirLength);
+ assert(ffStrbufStartsWithS(&buffer, "pci:"));
+
+ detectPci(options, gpus, &buffer, &pciDir, NULL);
+ ffStrbufSubstrBefore(&pciDir, pciBaseDirLength);
+ }
+
+ return NULL;
+}
+
+const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
+#if __x86_64__ || __aarch64__
+ const char* ffGPUDetectWsl2(const FFGPUOptions* options, FFlist* gpus);
+ if (ffGPUDetectWsl2(options, gpus) == NULL) {
+ return NULL;
+ }
+#endif
+
+ if (options->detectionMethod == FF_GPU_DETECTION_METHOD_AUTO) {
+ if (drmDetectGPUs(options, gpus) == NULL && gpus->length > 0) {
+ return NULL;
+ }
+ }
+ return pciDetectGPUs(options, gpus);
+}
diff --git a/src/detection/gpu/gpu_mthreads.c b/src/detection/gpu/gpu_mthreads.c
new file mode 100644
index 0000000..0b639f5
--- /dev/null
+++ b/src/detection/gpu/gpu_mthreads.c
@@ -0,0 +1,201 @@
+#include "gpu_driver_specific.h"
+
+#include "common/library.h"
+#include "mtml.h"
+
+struct FFMtmlData {
+ FF_LIBRARY_SYMBOL(mtmlDeviceCountGpuCores)
+ FF_LIBRARY_SYMBOL(mtmlDeviceGetBrand)
+ FF_LIBRARY_SYMBOL(mtmlDeviceGetIndex)
+ FF_LIBRARY_SYMBOL(mtmlDeviceGetName)
+ FF_LIBRARY_SYMBOL(mtmlDeviceGetPciInfo)
+ FF_LIBRARY_SYMBOL(mtmlDeviceGetUUID)
+ FF_LIBRARY_SYMBOL(mtmlDeviceInitGpu)
+ FF_LIBRARY_SYMBOL(mtmlDeviceInitMemory)
+ FF_LIBRARY_SYMBOL(mtmlGpuGetMaxClock)
+ FF_LIBRARY_SYMBOL(mtmlGpuGetTemperature)
+ FF_LIBRARY_SYMBOL(mtmlGpuGetUtilization)
+ FF_LIBRARY_SYMBOL(mtmlLibraryCountDevice)
+ FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByIndex)
+ FF_LIBRARY_SYMBOL(mtmlLibraryInitDeviceByPciSbdf)
+ FF_LIBRARY_SYMBOL(mtmlLibraryInitSystem)
+ FF_LIBRARY_SYMBOL(mtmlMemoryGetTotal)
+ FF_LIBRARY_SYMBOL(mtmlMemoryGetUsed)
+ FF_LIBRARY_SYMBOL(mtmlMemoryGetUtilization)
+ FF_LIBRARY_SYMBOL(mtmlLibraryShutDown)
+
+ bool inited;
+ MtmlLibrary* lib;
+ MtmlSystem* sys;
+} mtmlData;
+
+FF_A_UNUSED static void shutdownMtml(void) {
+ mtmlData.ffmtmlLibraryShutDown(mtmlData.lib);
+}
+
+const char* ffDetectMthreadsGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) {
+#ifndef FF_DISABLE_DLOPEN
+
+ if (!mtmlData.inited) {
+ mtmlData.inited = true;
+ FF_LIBRARY_LOAD(libmtml, "dlopen mtml failed", soName, 1);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libmtml, mtmlLibraryInit)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceCountGpuCores)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetBrand)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetIndex)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetName)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetPciInfo)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceGetUUID)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitGpu)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlDeviceInitMemory)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetMaxClock)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetTemperature)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlGpuGetUtilization)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryCountDevice)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByIndex)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitDeviceByPciSbdf)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryInitSystem)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetTotal)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUsed)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlMemoryGetUtilization)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libmtml, mtmlData, mtmlLibraryShutDown)
+
+ if (ffmtmlLibraryInit(&mtmlData.lib) != MTML_SUCCESS) {
+ mtmlData.ffmtmlLibraryInitSystem = NULL;
+ return "mtmlLibraryInit failed";
+ }
+ if (mtmlData.ffmtmlLibraryInitSystem(mtmlData.lib, &mtmlData.sys) != MTML_SUCCESS) {
+ mtmlData.ffmtmlLibraryShutDown(mtmlData.lib);
+ mtmlData.ffmtmlLibraryInitSystem = NULL;
+ return "mtmlLibraryInitSystem failed";
+ }
+ atexit(shutdownMtml);
+ libmtml = NULL; // don't close mtml
+ }
+
+ if (mtmlData.ffmtmlLibraryInitSystem == NULL) {
+ return "loading mtml library failed";
+ }
+
+ MtmlDevice* device = NULL;
+ if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) {
+ char pciBusIdStr[32];
+ snprintf(pciBusIdStr, ARRAY_SIZE(pciBusIdStr) - 1, "%04x:%02x:%02x.%d", cond->pciBusId.domain, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func);
+
+ MtmlReturn ret = mtmlData.ffmtmlLibraryInitDeviceByPciSbdf(mtmlData.lib, pciBusIdStr, &device);
+ if (ret != MTML_SUCCESS) {
+ return "mtmlLibraryInitDeviceByPciSbdf() failed";
+ }
+ } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID) {
+ uint32_t count;
+ if (mtmlData.ffmtmlLibraryCountDevice(mtmlData.lib, &count) != MTML_SUCCESS) {
+ return "mtmlLibraryCountDevice() failed";
+ }
+
+ for (uint32_t i = 0; i < count; i++, device = NULL) {
+ if (mtmlData.ffmtmlLibraryInitDeviceByIndex(mtmlData.lib, i, &device) != MTML_SUCCESS) {
+ continue;
+ }
+
+ MtmlPciInfo pciInfo;
+ if (mtmlData.ffmtmlDeviceGetPciInfo(device, &pciInfo) != MTML_SUCCESS) {
+ continue;
+ }
+
+ if (pciInfo.pciDeviceId != ((cond->pciDeviceId.deviceId << 16u) | cond->pciDeviceId.vendorId) ||
+ pciInfo.pciSubsystemId != cond->pciDeviceId.subSystemId) {
+ continue;
+ }
+
+ break;
+ }
+ if (!device) {
+ return "Device not found";
+ }
+ } else {
+ return "Unknown condition type";
+ }
+
+ MtmlBrandType brand;
+ if (mtmlData.ffmtmlDeviceGetBrand(device, &brand) == MTML_SUCCESS) {
+ switch (brand) {
+ case MTML_BRAND_MTT:
+ *result.type = FF_GPU_TYPE_DISCRETE;
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (result.index) {
+ unsigned int value;
+ if (mtmlData.ffmtmlDeviceGetIndex(device, &value) == MTML_SUCCESS) {
+ *result.index = value;
+ }
+ }
+
+ if (result.temp) {
+ MtmlGpu* gpu = NULL;
+ if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS) {
+ uint32_t value;
+ if (mtmlData.ffmtmlGpuGetTemperature(gpu, &value) == MTML_SUCCESS) {
+ *result.temp = value;
+ }
+ }
+ }
+
+ if (result.memory) {
+ MtmlMemory* mem = NULL;
+ if (mtmlData.ffmtmlDeviceInitMemory(device, &mem) == MTML_SUCCESS) {
+ unsigned long long total;
+ if (mtmlData.ffmtmlMemoryGetTotal(mem, &total) == MTML_SUCCESS) {
+ result.memory->total = total;
+ }
+
+ unsigned long long used;
+ if (mtmlData.ffmtmlMemoryGetUsed(mem, &used) == MTML_SUCCESS) {
+ result.memory->used = used;
+ }
+ }
+ }
+
+ if (result.coreCount) {
+ mtmlData.ffmtmlDeviceCountGpuCores(device, result.coreCount);
+ }
+
+ if (result.frequency) {
+ MtmlGpu* gpu = NULL;
+ if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS) {
+ uint32_t clockMHz;
+ if (mtmlData.ffmtmlGpuGetMaxClock(gpu, &clockMHz) == MTML_SUCCESS) {
+ *result.frequency = clockMHz;
+ }
+ }
+ }
+
+ if (result.coreUsage) {
+ MtmlGpu* gpu = NULL;
+ if (mtmlData.ffmtmlDeviceInitGpu(device, &gpu) == MTML_SUCCESS) {
+ unsigned int utilization;
+ if (mtmlData.ffmtmlGpuGetUtilization(gpu, &utilization) == MTML_SUCCESS) {
+ *result.coreUsage = utilization;
+ }
+ }
+ }
+
+ if (result.name) {
+ char name[MTML_DEVICE_NAME_BUFFER_SIZE];
+ if (mtmlData.ffmtmlDeviceGetName(device, name, ARRAY_SIZE(name)) == MTML_SUCCESS) {
+ ffStrbufSetS(result.name, name);
+ }
+ }
+
+ return NULL;
+
+#else
+
+ FF_UNUSED(cond, result, soName);
+ return "dlopen is disabled";
+
+#endif
+}
diff --git a/src/detection/gpu/gpu_nbsd.c b/src/detection/gpu/gpu_nbsd.c
new file mode 100644
index 0000000..4f5dab3
--- /dev/null
+++ b/src/detection/gpu/gpu_nbsd.c
@@ -0,0 +1,120 @@
+#include "gpu.h"
+#include "common/io.h"
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcidevs.h>
+#include <dev/pci/pciio.h>
+
+static inline int pciReadConf(int fd, uint32_t bus, uint32_t device, uint32_t func, uint32_t reg, uint32_t* result) {
+ struct pciio_bdf_cfgreg bdfr = {
+ .bus = bus,
+ .device = device,
+ .function = func,
+ .cfgreg = {
+ .reg = reg,
+ },
+ };
+
+ if (ioctl(fd, PCI_IOC_BDF_CFGREAD, &bdfr) == -1) {
+ return -1;
+ }
+
+ *result = bdfr.cfgreg.val;
+ return 0;
+}
+
+const char* ffDetectGPUImpl(FF_A_UNUSED const FFGPUOptions* options, FFlist* gpus) {
+ char pciDevPath[] = "/dev/pciXXX";
+
+ for (uint32_t idev = 0; idev <= 255; idev++) {
+ snprintf(pciDevPath + strlen("/dev/pci"), 4, "%u", idev);
+
+ FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC);
+ if (pcifd < 0) {
+ if (errno == ENOENT) {
+ break; // No more /dev/pciN devices
+ }
+ return "open(\"/dev/pciN\", O_RDONLY | O_CLOEXEC) failed";
+ }
+
+ struct pciio_businfo businfo;
+ if (ioctl(pcifd, PCI_IOC_BUSINFO, &businfo) != 0) {
+ continue;
+ }
+
+ uint32_t bus = businfo.busno;
+ for (uint32_t dev = 0; dev < businfo.maxdevs; dev++) {
+ uint32_t maxfuncs = 0;
+ for (uint32_t func = 0; func <= maxfuncs; func++) {
+ uint32_t pciid, pciclass;
+ if (pciReadConf(pcifd, bus, dev, func, PCI_ID_REG, &pciid) != 0) {
+ continue;
+ }
+
+ if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0) {
+ continue;
+ }
+
+ if (pciReadConf(pcifd, bus, dev, func, PCI_CLASS_REG, &pciclass) != 0) {
+ continue;
+ }
+
+ if (func == 0) {
+ // For some reason, pciReadConf returns success even for non-existing devices.
+ // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out.
+ uint32_t bhlcr;
+ if (pciReadConf(pcifd, bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0) {
+ continue;
+ }
+
+ if (PCI_HDRTYPE_MULTIFN(bhlcr)) {
+ maxfuncs = 7;
+ }
+ }
+
+ if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY) {
+ continue;
+ }
+
+ if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC) {
+ continue; // Likely an auxiliary display controller (#2034)
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid)));
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInitS(&gpu->platformApi, pciDevPath);
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func);
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu);
+ }
+ if (gpu->name.length == 0) {
+ ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu);
+ }
+
+ struct pciio_drvname drvname = {
+ .device = dev,
+ .function = func,
+ };
+ if (ioctl(pcifd, PCI_IOC_DRVNAME, &drvname) == 0) {
+ ffStrbufInitS(&gpu->driver, drvname.name);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_nosupport.c b/src/detection/gpu/gpu_nosupport.c
new file mode 100644
index 0000000..95fb2f3
--- /dev/null
+++ b/src/detection/gpu/gpu_nosupport.c
@@ -0,0 +1,6 @@
+#include "gpu.h"
+
+const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
+ FF_UNUSED(options, gpus);
+ return "Not supported on this platform";
+}
diff --git a/src/detection/gpu/gpu_nvidia.c b/src/detection/gpu/gpu_nvidia.c
new file mode 100644
index 0000000..85776dd
--- /dev/null
+++ b/src/detection/gpu/gpu_nvidia.c
@@ -0,0 +1,299 @@
+#include "gpu_driver_specific.h"
+
+#include "common/library.h"
+#include "nvml.h"
+
+struct FFNvmlData {
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetCount_v2)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetHandleByIndex_v2)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetHandleByPciBusId_v2)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetPciInfo_v3)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetTemperature)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetMemoryInfo_v2)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetMemoryInfo)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetNumGpuCores)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetMaxClockInfo)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetUtilizationRates)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetBrand)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetIndex)
+ FF_LIBRARY_SYMBOL(nvmlDeviceGetName)
+
+ bool inited;
+} nvmlData;
+
+#if defined(_WIN32) && !defined(FF_DISABLE_DLOPEN)
+
+ #include "nvapi.h"
+
+struct FFNvapiData {
+ FF_LIBRARY_SYMBOL(nvapi_Unload)
+ FF_LIBRARY_SYMBOL(nvapi_EnumPhysicalGPUs)
+ FF_LIBRARY_SYMBOL(nvapi_GPU_GetRamType)
+ FF_LIBRARY_SYMBOL(nvapi_GPU_GetGPUType)
+
+ bool inited;
+} nvapiData;
+
+static const char* detectMoreByNvapi(FFGpuDriverResult* result) {
+ if (!nvapiData.inited) {
+ nvapiData.inited = true;
+
+ FF_LIBRARY_LOAD_MESSAGE(libnvapi,
+ #ifdef _WIN64
+ "nvapi64.dll"
+ #else
+ "nvapi.dll"
+ #endif
+ ,
+ 1);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvapi, nvapi_QueryInterface)
+ #define FF_NVAPI_INTERFACE(iName, iOffset) \
+ __typeof__(&iName) ff##iName = ffnvapi_QueryInterface(iOffset); \
+ if (ff##iName == NULL) return "nvapi_QueryInterface " #iName " failed";
+
+ FF_NVAPI_INTERFACE(nvapi_Initialize, NVAPI_INTERFACE_OFFSET_INITIALIZE)
+ FF_NVAPI_INTERFACE(nvapi_Unload, NVAPI_INTERFACE_OFFSET_UNLOAD)
+ FF_NVAPI_INTERFACE(nvapi_EnumPhysicalGPUs, NVAPI_INTERFACE_OFFSET_ENUM_PHYSICAL_GPUS)
+ FF_NVAPI_INTERFACE(nvapi_GPU_GetRamType, NVAPI_INTERFACE_OFFSET_GPU_GET_RAM_TYPE)
+ FF_NVAPI_INTERFACE(nvapi_GPU_GetGPUType, NVAPI_INTERFACE_OFFSET_GPU_GET_GPU_TYPE)
+ #undef FF_NVAPI_INTERFACE
+
+ if (ffnvapi_Initialize() < 0) {
+ return "NvAPI_Initialize() failed";
+ }
+
+ nvapiData.ffnvapi_EnumPhysicalGPUs = ffnvapi_EnumPhysicalGPUs;
+ nvapiData.ffnvapi_GPU_GetRamType = ffnvapi_GPU_GetRamType;
+ nvapiData.ffnvapi_GPU_GetGPUType = ffnvapi_GPU_GetGPUType;
+ nvapiData.ffnvapi_Unload = ffnvapi_Unload;
+
+ atexit((void*) ffnvapi_Unload);
+ libnvapi = NULL; // don't close nvapi
+ }
+
+ if (nvapiData.ffnvapi_EnumPhysicalGPUs == NULL) {
+ return "loading nvapi library failed";
+ }
+
+ NvPhysicalGpuHandle handles[32];
+ int gpuCount = 0;
+
+ if (nvapiData.ffnvapi_EnumPhysicalGPUs(handles, &gpuCount) < 0) {
+ return "NvAPI_EnumPhysicalGPUs() failed";
+ }
+
+ uint32_t gpuIndex = *result->index;
+
+ if (gpuIndex >= (uint32_t) gpuCount) {
+ return "GPU index out of range";
+ }
+
+ // Not very sure. Need to check in multi-GPU system
+ NvPhysicalGpuHandle gpuHandle = handles[gpuIndex];
+
+ NvApiGPUMemoryType memType;
+ if (result->memoryType && nvapiData.ffnvapi_GPU_GetRamType(gpuHandle, &memType) == 0) {
+ switch (memType) {
+ #define FF_NVAPI_MEMORY_TYPE(type) \
+ case NVAPI_GPU_MEMORY_TYPE_##type: \
+ ffStrbufSetStatic(result->memoryType, #type); \
+ break;
+ FF_NVAPI_MEMORY_TYPE(UNKNOWN)
+ FF_NVAPI_MEMORY_TYPE(SDRAM)
+ FF_NVAPI_MEMORY_TYPE(DDR1)
+ FF_NVAPI_MEMORY_TYPE(DDR2)
+ FF_NVAPI_MEMORY_TYPE(GDDR2)
+ FF_NVAPI_MEMORY_TYPE(GDDR3)
+ FF_NVAPI_MEMORY_TYPE(GDDR4)
+ FF_NVAPI_MEMORY_TYPE(DDR3)
+ FF_NVAPI_MEMORY_TYPE(GDDR5)
+ FF_NVAPI_MEMORY_TYPE(LPDDR2)
+ FF_NVAPI_MEMORY_TYPE(GDDR5X)
+ FF_NVAPI_MEMORY_TYPE(LPDDR3)
+ FF_NVAPI_MEMORY_TYPE(LPDDR4)
+ FF_NVAPI_MEMORY_TYPE(LPDDR5)
+ FF_NVAPI_MEMORY_TYPE(GDDR6)
+ FF_NVAPI_MEMORY_TYPE(GDDR6X)
+ FF_NVAPI_MEMORY_TYPE(GDDR7)
+ #undef FF_NVAPI_MEMORY_TYPE
+ default:
+ ffStrbufSetF(result->memoryType, "Unknown (%d)", memType);
+ break;
+ }
+ }
+
+ NvApiGPUType gpuType;
+ if (result->type && nvapiData.ffnvapi_GPU_GetGPUType(gpuHandle, &gpuType) == 0) {
+ switch (gpuType) {
+ case NV_SYSTEM_TYPE_IGPU:
+ *result->type = FF_GPU_TYPE_INTEGRATED;
+ break;
+ case NV_SYSTEM_TYPE_DGPU:
+ *result->type = FF_GPU_TYPE_DISCRETE;
+ break;
+ default:
+ *result->type = FF_GPU_TYPE_UNKNOWN;
+ break;
+ }
+ }
+
+ return NULL;
+}
+
+#endif
+
+const char* ffDetectNvidiaGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) {
+#ifndef FF_DISABLE_DLOPEN
+
+ if (!nvmlData.inited) {
+ nvmlData.inited = true;
+ FF_LIBRARY_LOAD(libnvml, "dlopen nvml failed", soName, 1);
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvml, nvmlInit_v2)
+ FF_LIBRARY_LOAD_SYMBOL_MESSAGE(libnvml, nvmlShutdown)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetCount_v2)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetHandleByIndex_v2)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetHandleByPciBusId_v2)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetPciInfo_v3)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetTemperature)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMemoryInfo_v2)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMemoryInfo)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetNumGpuCores)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetMaxClockInfo)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetUtilizationRates)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetBrand)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetIndex)
+ FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(libnvml, nvmlData, nvmlDeviceGetName)
+
+ if (ffnvmlInit_v2() != NVML_SUCCESS) {
+ nvmlData.ffnvmlDeviceGetNumGpuCores = NULL;
+ return "nvmlInit_v2() failed";
+ }
+ atexit((void*) ffnvmlShutdown);
+ libnvml = NULL; // don't close nvml
+ }
+
+ if (nvmlData.ffnvmlDeviceGetNumGpuCores == NULL) {
+ return "loading nvml library failed";
+ }
+
+ nvmlDevice_t device = NULL;
+ if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) {
+ char pciBusIdStr[32];
+ snprintf(pciBusIdStr, ARRAY_SIZE(pciBusIdStr), "%04x:%02x:%02x.%d", cond->pciBusId.domain, cond->pciBusId.bus, cond->pciBusId.device, cond->pciBusId.func);
+
+ nvmlReturn_t ret = nvmlData.ffnvmlDeviceGetHandleByPciBusId_v2(pciBusIdStr, &device);
+ if (ret != NVML_SUCCESS) {
+ return "nvmlDeviceGetHandleByPciBusId_v2() failed";
+ }
+ } else if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID) {
+ uint32_t count;
+ if (nvmlData.ffnvmlDeviceGetCount_v2(&count) != NVML_SUCCESS) {
+ return "nvmlDeviceGetCount_v2() failed";
+ }
+
+ for (uint32_t i = 0; i < count; i++, device = NULL) {
+ if (nvmlData.ffnvmlDeviceGetHandleByIndex_v2(i, &device) != NVML_SUCCESS) {
+ continue;
+ }
+
+ nvmlPciInfo_t pciInfo;
+ if (nvmlData.ffnvmlDeviceGetPciInfo_v3(device, &pciInfo) != NVML_SUCCESS) {
+ continue;
+ }
+
+ if (pciInfo.pciDeviceId != ((cond->pciDeviceId.deviceId << 16u) | cond->pciDeviceId.vendorId) ||
+ pciInfo.pciSubSystemId != cond->pciDeviceId.subSystemId) {
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (!device) {
+ return "Device not found";
+ }
+
+ if (result.type) {
+ nvmlBrandType_t brand;
+ if (nvmlData.ffnvmlDeviceGetBrand(device, &brand) == NVML_SUCCESS) {
+ switch (brand) {
+ case NVML_BRAND_NVIDIA_RTX:
+ case NVML_BRAND_QUADRO_RTX:
+ case NVML_BRAND_GEFORCE:
+ case NVML_BRAND_TITAN:
+ case NVML_BRAND_TESLA:
+ case NVML_BRAND_QUADRO:
+ *result.type = FF_GPU_TYPE_DISCRETE;
+ break;
+ default:
+ break;
+ }
+ }
+ }
+
+ if (result.index) {
+ unsigned int value;
+ if (nvmlData.ffnvmlDeviceGetIndex(device, &value) == NVML_SUCCESS) {
+ *result.index = value;
+ #ifdef _WIN32
+ // Don't bother loading nvapi for GPU type detection only
+ if (result.memoryType) {
+ detectMoreByNvapi(&result);
+ }
+ #endif
+ }
+ }
+
+ if (result.temp) {
+ uint32_t value;
+ if (nvmlData.ffnvmlDeviceGetTemperature(device, NVML_TEMPERATURE_GPU, &value) == NVML_SUCCESS) {
+ *result.temp = value;
+ }
+ }
+
+ if (result.memory) {
+ nvmlMemory_v2_t memory = { .version = nvmlMemory_v2 };
+ if (nvmlData.ffnvmlDeviceGetMemoryInfo_v2(device, &memory) == NVML_SUCCESS) {
+ result.memory->total = memory.used + memory.free;
+ result.memory->used = memory.used;
+ } else {
+ nvmlMemory_t memory_v1;
+ if (nvmlData.ffnvmlDeviceGetMemoryInfo(device, &memory_v1) == NVML_SUCCESS) {
+ result.memory->total = memory_v1.total;
+ result.memory->used = memory_v1.used;
+ }
+ }
+ }
+
+ if (result.coreCount) {
+ nvmlData.ffnvmlDeviceGetNumGpuCores(device, result.coreCount);
+ }
+
+ if (result.frequency) {
+ nvmlData.ffnvmlDeviceGetMaxClockInfo(device, NVML_CLOCK_GRAPHICS, result.frequency);
+ }
+
+ if (result.coreUsage) {
+ nvmlUtilization_t utilization;
+ if (nvmlData.ffnvmlDeviceGetUtilizationRates(device, &utilization) == NVML_SUCCESS) {
+ *result.coreUsage = utilization.gpu;
+ }
+ }
+
+ if (result.name) {
+ char name[NVML_DEVICE_NAME_V2_BUFFER_SIZE];
+ if (nvmlData.ffnvmlDeviceGetName(device, name, ARRAY_SIZE(name)) == NVML_SUCCESS) {
+ ffStrbufSetS(result.name, name);
+ }
+ }
+
+ return NULL;
+
+#else
+
+ FF_UNUSED(cond, result, soName);
+ return "dlopen is disabled";
+
+#endif
+}
diff --git a/src/detection/gpu/gpu_obsd.c b/src/detection/gpu/gpu_obsd.c
new file mode 100644
index 0000000..dabaded
--- /dev/null
+++ b/src/detection/gpu/gpu_obsd.c
@@ -0,0 +1,101 @@
+#include "gpu.h"
+#include "common/io.h"
+
+#include <sys/param.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcidevs.h>
+#include <sys/pciio.h>
+
+static inline int pciReadConf(int fd, uint8_t bus, uint8_t device, uint8_t func, uint8_t reg, uint32_t* result) {
+ struct pci_io bdfr = {
+ .pi_sel = {
+ .pc_bus = bus,
+ .pc_dev = device,
+ .pc_func = func,
+ },
+ .pi_reg = reg,
+ .pi_width = 4,
+ };
+
+ if (ioctl(fd, PCIOCREAD, &bdfr) == -1) {
+ return -1;
+ }
+
+ *result = bdfr.pi_data;
+ return 0;
+}
+
+const char* ffDetectGPUImpl(FF_A_UNUSED const FFGPUOptions* options, FFlist* gpus) {
+ char pciDevPath[] = "/dev/pci0";
+ FF_AUTO_CLOSE_FD int pcifd = open(pciDevPath, O_RDONLY | O_CLOEXEC);
+ if (pcifd < 0) {
+ return "open(\"/dev/pci0\", O_RDONLY | O_CLOEXEC) failed";
+ }
+
+ for (uint16_t bus = 0; bus <= 255; bus++) {
+ for (uint8_t dev = 0; dev < 32; dev++) {
+ uint8_t maxfuncs = 0;
+ for (uint8_t func = 0; func <= maxfuncs; func++) {
+ uint32_t pciid, pciclass;
+ if (pciReadConf(pcifd, (uint8_t) bus, dev, func, PCI_ID_REG, &pciid) != 0) {
+ continue;
+ }
+
+ if (PCI_VENDOR(pciid) == PCI_VENDOR_INVALID || PCI_VENDOR(pciid) == 0) {
+ continue;
+ }
+
+ if (pciReadConf(pcifd, (uint8_t) bus, dev, func, PCI_CLASS_REG, &pciclass) != 0) {
+ continue;
+ }
+
+ if (func == 0) {
+ // For some reason, pciReadConf returns success even for non-existing devices.
+ // So we need to check for `PCI_VENDOR(pciid) == PCI_VENDOR_INVALID` above to filter them out.
+ uint32_t bhlcr;
+ if (pciReadConf(pcifd, (uint8_t) bus, dev, 0, PCI_BHLC_REG, &bhlcr) != 0) {
+ continue;
+ }
+
+ if (PCI_HDRTYPE_MULTIFN(bhlcr)) {
+ maxfuncs = 7;
+ }
+ }
+
+ if (PCI_CLASS(pciclass) != PCI_CLASS_DISPLAY) {
+ continue;
+ }
+
+ if (func > 0 && PCI_SUBCLASS(pciclass) == PCI_SUBCLASS_DISPLAY_MISC) {
+ continue; // Likely an auxiliary display controller (#2034)
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString(PCI_VENDOR(pciid)));
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInitS(&gpu->platformApi, "/dev/pci0");
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = ffGPUPciAddr2Id(0, bus, dev, func);
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ ffGPUQueryAmdGpuName(PCI_PRODUCT(pciid), PCI_REVISION(pciid), gpu);
+ }
+ if (gpu->name.length == 0) {
+ ffGPUFillVendorAndName(PCI_SUBCLASS(pciclass), PCI_VENDOR(pciid), PCI_PRODUCT(pciid), gpu);
+ }
+ }
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_pci.c b/src/detection/gpu/gpu_pci.c
new file mode 100644
index 0000000..63cd877
--- /dev/null
+++ b/src/detection/gpu/gpu_pci.c
@@ -0,0 +1,251 @@
+#include "gpu.h"
+#include "common/io.h"
+#include "common/properties.h"
+#include "common/memrchr.h"
+
+#include <stdlib.h>
+#ifdef __FreeBSD__
+ #include <paths.h>
+ #ifndef _PATH_LOCALBASE
+ #define _PATH_LOCALBASE "/usr/local"
+ #endif
+#elif __OpenBSD__
+ #define _PATH_LOCALBASE "/usr/local"
+#elif __NetBSD__
+ #define _PATH_LOCALBASE "/usr/pkg"
+#endif
+
+#if FF_HAVE_EMBEDDED_PCIIDS
+ #include "fastfetch_pciids.c.inc"
+#endif
+#if FF_HAVE_EMBEDDED_AMDGPUIDS
+ #include "fastfetch_amdgpuids.c.inc"
+#endif
+
+#define FF_STR_INDIR(x) #x
+#define FF_STR(x) FF_STR_INDIR(x)
+
+static const FFstrbuf* loadPciIds() {
+ static FFstrbuf pciids;
+
+ if (pciids.chars) {
+ return &pciids;
+ }
+ ffStrbufInit(&pciids);
+
+#ifdef FF_CUSTOM_PCI_IDS_PATH
+
+ ffReadFileBuffer(FF_STR(FF_CUSTOM_PCI_IDS_PATH), &pciids);
+
+#else // FF_CUSTOM_PCI_IDS_PATH
+
+ #if __linux__
+ ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/share/hwdata/pci.ids", &pciids);
+ if (pciids.length == 0) {
+ ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/share/misc/pci.ids", &pciids); // debian?
+ if (pciids.length == 0) {
+ ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/local/share/hwdata/pci.ids", &pciids);
+ }
+ }
+ #elif __OpenBSD__ || __FreeBSD__ || __NetBSD__
+ ffReadFileBuffer(_PATH_LOCALBASE "/share/hwdata/pci.ids", &pciids);
+ if (pciids.length == 0) {
+ ffReadFileBuffer(_PATH_LOCALBASE "/share/pciids/pci.ids", &pciids);
+ }
+ #elif __sun
+ ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids", &pciids);
+ #elif __HAIKU__
+ ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/system/data/hwdata/pci.ids", &pciids);
+ #endif
+
+#endif // FF_CUSTOM_PCI_IDS_PATH
+
+ return &pciids;
+}
+
+static void parsePciIdsFile(const FFstrbuf* content, uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
+ if (content->length) {
+ char buffer[32];
+
+ // Search for vendor
+ uint32_t len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), "\n%04x ", vendor);
+ char* start = (char*) memmem(content->chars, content->length, buffer, len);
+ char* end = content->chars + content->length;
+ if (start) {
+ start += len;
+ end = memchr(start, '\n', (uint32_t) (end - start));
+ if (!end) {
+ end = content->chars + content->length;
+ }
+ if (!gpu->vendor.length) {
+ ffStrbufSetNS(&gpu->vendor, (uint32_t) (end - start), start);
+ }
+
+ start = end; // point to '\n' of vendor
+ end = start + 1; // point to start of devices
+ // find the start of next vendor
+ while (end[0] == '\t' || end[0] == '#') {
+ end = strchr(end, '\n');
+ if (!end) {
+ end = content->chars + content->length;
+ break;
+ } else {
+ end++;
+ }
+ }
+
+ // Search for device
+ len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), "\n\t%04x ", device);
+ start = memmem(start, (size_t) (end - start), buffer, len);
+ if (start) {
+ start += len;
+ end = memchr(start, '\n', (uint32_t) (end - start));
+ if (!end) {
+ end = content->chars + content->length;
+ }
+
+ char* closingBracket = end - 1;
+ if (*closingBracket == ']') {
+ char* openingBracket = memrchr(start, '[', (size_t) (closingBracket - start));
+ if (openingBracket) {
+ openingBracket++;
+ ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);
+ }
+ }
+ if (!gpu->name.length) {
+ ffStrbufSetNS(&gpu->name, (uint32_t) (end - start), start);
+ }
+ }
+ }
+ }
+
+ if (!gpu->name.length) {
+ const char* subclassStr;
+ switch (subclass) {
+ case 0 /*PCI_CLASS_DISPLAY_VGA*/:
+ subclassStr = " (VGA compatible)";
+ break;
+ case 1 /*PCI_CLASS_DISPLAY_XGA*/:
+ subclassStr = " (XGA compatible)";
+ break;
+ case 2 /*PCI_CLASS_DISPLAY_3D*/:
+ subclassStr = " (3D)";
+ break;
+ default:
+ subclassStr = "";
+ break;
+ }
+
+ ffStrbufSetF(&gpu->name, "%s Device %04X%s", gpu->vendor.length ? gpu->vendor.chars : "Unknown", device, subclassStr);
+ }
+}
+
+#if FF_HAVE_EMBEDDED_PCIIDS
+static inline int pciDeviceCmp(const uint16_t* key, const FFPciDevice* element) {
+ return (int) *key - (int) element->id;
+}
+
+static bool loadPciidsInc(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
+ for (const FFPciVendor* pvendor = ffPciVendors; pvendor->name; pvendor++) {
+ if (pvendor->id != vendor) {
+ continue;
+ }
+
+ if (!gpu->vendor.length) {
+ ffStrbufSetS(&gpu->vendor, pvendor->name);
+ }
+
+ const FFPciDevice* pdevice = (const FFPciDevice*) bsearch(&device, pvendor->devices, pvendor->nDevices, sizeof(*pdevice), (void*) pciDeviceCmp);
+
+ if (pdevice) {
+ uint32_t nameLen = (uint32_t) strlen(pdevice->name);
+ const char* closingBracket = pdevice->name + nameLen - 1;
+ if (*closingBracket == ']') {
+ const char* openingBracket = memrchr(pdevice->name, '[', nameLen - 1);
+ if (openingBracket) {
+ openingBracket++;
+ ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);
+ }
+ }
+ if (!gpu->name.length) {
+ ffStrbufSetNS(&gpu->name, nameLen, pdevice->name);
+ }
+ return true;
+ }
+
+ if (!gpu->name.length) {
+ const char* subclassStr;
+ switch (subclass) {
+ case 0 /*PCI_CLASS_DISPLAY_VGA*/:
+ subclassStr = " (VGA compatible)";
+ break;
+ case 1 /*PCI_CLASS_DISPLAY_XGA*/:
+ subclassStr = " (XGA compatible)";
+ break;
+ case 2 /*PCI_CLASS_DISPLAY_3D*/:
+ subclassStr = " (3D)";
+ break;
+ default:
+ subclassStr = "";
+ break;
+ }
+
+ ffStrbufSetF(&gpu->name, "%s Device %04X%s", gpu->vendor.length ? gpu->vendor.chars : "Unknown", device, subclassStr);
+ }
+ return true;
+ }
+ return false;
+}
+#endif
+
+void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
+ if (vendor == 0x1234 && device == 0x1111 && subclass == 0) { // Not exist in pci.ids
+ ffStrbufSetStatic(&gpu->name, "Virtual Video Controller");
+ return;
+ }
+
+#if FF_HAVE_EMBEDDED_PCIIDS
+ bool ok = loadPciidsInc(subclass, vendor, device, gpu);
+ if (ok) {
+ return;
+ }
+#endif
+ return parsePciIdsFile(loadPciIds(), subclass, vendor, device, gpu);
+}
+
+#if FF_HAVE_EMBEDDED_AMDGPUIDS
+static inline int amdGpuCmp(const uint32_t* key, const FFArmGpuProduct* element) {
+ // Maximum value of *key is 0x00FFFFFF. `(int) *key` should never overflow
+ return (int) *key - (int) element->id;
+}
+
+static bool loadAmdGpuIdsInc(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu) {
+ uint32_t key = (deviceId << 8u) | revision;
+ FFArmGpuProduct* product = bsearch(&key, ffAmdGpuProducts, ARRAY_SIZE(ffAmdGpuProducts), sizeof(*ffAmdGpuProducts), (void*) amdGpuCmp);
+ if (product) {
+ ffStrbufSetS(&gpu->name, product->name);
+ return true;
+ }
+ return false;
+}
+#endif
+
+static void parseAmdGpuIdsFile(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu) {
+ char query[32];
+ snprintf(query, ARRAY_SIZE(query), "%X,\t%X,", (unsigned) deviceId, (unsigned) revision);
+#ifdef FF_CUSTOM_AMDGPU_IDS_PATH
+ ffParsePropFile(FF_STR(FF_CUSTOM_AMDGPU_IDS_PATH), query, &gpu->name);
+#else
+ ffParsePropFileData("libdrm/amdgpu.ids", query, &gpu->name);
+#endif
+}
+
+void ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu) {
+#if FF_HAVE_EMBEDDED_AMDGPUIDS
+ bool ok = loadAmdGpuIdsInc(deviceId, revisionId, gpu);
+ if (ok) {
+ return;
+ }
+#endif
+ return parseAmdGpuIdsFile(deviceId, revisionId, gpu);
+}
diff --git a/src/detection/gpu/gpu_sunos.c b/src/detection/gpu/gpu_sunos.c
new file mode 100644
index 0000000..13d4998
--- /dev/null
+++ b/src/detection/gpu/gpu_sunos.c
@@ -0,0 +1,54 @@
+#include "gpu.h"
+#include "common/strutil.h"
+
+#include <libdevinfo.h>
+
+static int walkDevTree(di_node_t node, FF_A_UNUSED di_minor_t minor, FFlist* gpus) {
+ int* vendorId;
+ int* deviceId;
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "vendor-id", &vendorId) > 0 && di_prop_lookup_ints(DDI_DEV_T_ANY, node, "device-id", &deviceId) > 0) {
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInitS(&gpu->vendor, ffGPUGetVendorString((uint16_t) *vendorId));
+ ffStrbufInit(&gpu->name);
+ ffStrbufInitS(&gpu->driver, di_driver_name(node));
+ ffStrbufInitStatic(&gpu->platformApi, "libdevinfo");
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->type = FF_GPU_TYPE_UNKNOWN;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = strtoul(di_bus_addr(node), NULL, 16);
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
+ int* revId;
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "revision-id", &revId) > 0) {
+ ffGPUQueryAmdGpuName((uint16_t) *deviceId, (uint8_t) *revId, gpu);
+ }
+ }
+
+ if (gpu->name.length == 0) {
+ uint8_t subclass = 0; // assume VGA
+ int* classCode;
+ if (di_prop_lookup_ints(DDI_DEV_T_ANY, node, "class-code", &classCode) > 0) {
+ subclass = (uint8_t) (*classCode & 0xFFFF);
+ }
+ ffGPUFillVendorAndName(subclass, (uint16_t) *vendorId, (uint16_t) *deviceId, gpu);
+ }
+ }
+
+ return DI_WALK_CONTINUE;
+}
+
+const char* ffDetectGPUImpl(FF_A_UNUSED const FFGPUOptions* options, FFlist* gpus) {
+ di_node_t rootNode = di_init("/", DINFOCPYALL);
+ if (rootNode == DI_NODE_NIL) {
+ return "di_init() failed";
+ }
+ di_walk_minor(rootNode, DDI_NT_DISPLAY, DI_WALK_CLDFIRST, gpus, (void*) walkDevTree);
+ di_fini(rootNode);
+
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_windows.c b/src/detection/gpu/gpu_windows.c
new file mode 100644
index 0000000..796c1c5
--- /dev/null
+++ b/src/detection/gpu/gpu_windows.c
@@ -0,0 +1,605 @@
+#include "detection/gpu/gpu.h"
+#if __linux__
+ #define FF_GPU_DRIVER_DLLNAME_PATH_PREFIX "/usr/lib/wsl/lib/"
+#endif
+#include "detection/gpu/gpu_driver_specific.h"
+#include "common/debug.h"
+
+#include <inttypes.h>
+#include "d3dkmthk.h"
+
+#if _WIN32
+ #include "common/windows/unicode.h"
+ #include "common/windows/registry.h"
+
+ #if FF_WIN81_COMPAT
+ #include "common/mallocHelper.h"
+ #include <windows.h>
+ #include <cfgmgr32.h>
+ #include <devguid.h>
+
+ #define GUID_DEVCLASS_DISPLAY_STRING L"{4d36e968-e325-11ce-bfc1-08002be10318}" // Found in <devguid.h>
+
+static bool queryDeviceIdsFallback(D3DKMT_ADAPTERADDRESS adapterAddress, D3DKMT_DEVICE_IDS* outDeviceIds) {
+ FF_DEBUG("KMTQAITYPE_PHYSICALADAPTERDEVICEIDS failed. Attempting queryDeviceIdsFallback: bus=%u device=%u function=%u",
+ adapterAddress.BusNumber,
+ adapterAddress.DeviceNumber,
+ adapterAddress.FunctionNumber);
+
+ if (adapterAddress.BusNumber == -1u) {
+ FF_DEBUG("Invalid adapter address, cannot query device IDs");
+ return false;
+ }
+
+ static FFlist deviceIdsCache;
+ static bool initialized;
+ typedef struct {
+ D3DKMT_DEVICE_IDS deviceIds;
+ D3DKMT_ADAPTERADDRESS adapterAddress;
+ } CacheEntry;
+
+ if (!initialized) {
+ initialized = true;
+ ffListInit(&deviceIdsCache);
+
+ ULONG devIdListSize = 0;
+ if (CM_Get_Device_ID_List_SizeW(&devIdListSize, GUID_DEVCLASS_DISPLAY_STRING, CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT) != CR_SUCCESS || devIdListSize <= 1) {
+ FF_DEBUG("No display devices found, list size: %lu", devIdListSize);
+ return false;
+ }
+
+ FF_DEBUG("Found device ID list size: %lu", devIdListSize);
+
+ FF_AUTO_FREE DEVINSTID_W devIdList = malloc(devIdListSize * sizeof(*devIdList));
+
+ if (CM_Get_Device_ID_ListW(GUID_DEVCLASS_DISPLAY_STRING, devIdList, devIdListSize, CM_GETIDLIST_FILTER_CLASS | CM_GETIDLIST_FILTER_PRESENT) != CR_SUCCESS) {
+ FF_DEBUG("CM_Get_Device_ID_ListW failed");
+ return false;
+ }
+
+ for (wchar_t* devId = devIdList; *devId; devId += wcslen(devId) + 1) {
+ FF_DEBUG("Processing device ID: %ls", devId);
+
+ DEVINST devInst = 0;
+
+ if (CM_Locate_DevNodeW(&devInst, devId, CM_LOCATE_DEVNODE_NORMAL) != CR_SUCCESS) {
+ FF_DEBUG("Failed to get device instance ID or locate device node");
+ continue;
+ }
+ FF_DEBUG("Device instance ID: %lu", devInst);
+
+ for (wchar_t* p = devId; *p; p++) {
+ if (*p >= L'a' && *p <= L'z') {
+ *p -= L'a' - L'A';
+ }
+ }
+
+ if (wcsncmp(devId, L"PCI\\", 4) != 0) {
+ FF_DEBUG("Skipping non-PCI device ID: %ls", devId);
+ continue;
+ }
+
+ uint32_t pciBus = 0;
+
+ ULONG pciBufLen = sizeof(pciBus);
+ if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_BUSNUMBER, NULL, &pciBus, &pciBufLen, 0) == CR_SUCCESS) {
+ uint32_t pciAddr = 0;
+ pciBufLen = sizeof(pciAddr);
+ if (CM_Get_DevNode_Registry_PropertyW(devInst, CM_DRP_ADDRESS, NULL, &pciAddr, &pciBufLen, 0) == CR_SUCCESS) {
+ CacheEntry* entry = FF_LIST_ADD(CacheEntry, deviceIdsCache);
+
+ entry->deviceIds = (D3DKMT_DEVICE_IDS) {};
+ // L"PCI\\VEN_10DE&DEV_2782&SUBSYS_513417AA&REV_A1\\4&3674a6b9&0&0008"
+ if (swscanf(devId + 4, L"VEN_%x&DEV_%x&SUBSYS_%4x%4x&REV_%x", &entry->deviceIds.VendorID, &entry->deviceIds.DeviceID, &entry->deviceIds.SubSystemID, &entry->deviceIds.SubVendorID, &entry->deviceIds.RevisionID) >= 2) {
+ FF_DEBUG("Parsed PCI IDs - Vendor: 0x%04x, Device: 0x%04x, SubVendor: 0x%04x, SubSystem: 0x%04x, Rev: 0x%04x", entry->deviceIds.VendorID, entry->deviceIds.DeviceID, entry->deviceIds.SubVendorID, entry->deviceIds.SubSystemID, entry->deviceIds.RevisionID);
+ // I thought it was DXGKMDT_OPM_BUS_TYPE_PCI, but it turns out to be false
+ // Who TF knows what 1 actually means. It's just reported by most graphic cards
+ // And yeah, DXGKMDT_OPM_BUS_TYPE_PCIEXPRESS (3) exists
+ entry->deviceIds.BusType = 1;
+ } else {
+ FF_DEBUG("Failed to parse PCI IDs from device ID string");
+ deviceIdsCache.length--; // remove the cache entry since it's not valid
+ continue;
+ }
+
+ entry->adapterAddress = (D3DKMT_ADAPTERADDRESS) {
+ .BusNumber = pciBus,
+ .DeviceNumber = (pciAddr >> 16) & 0xFFFF,
+ .FunctionNumber = pciAddr & 0xFFFF,
+ };
+ FF_DEBUG("Cached device IDs for PCI bus %u: vendor=0x%04x device=0x%04x", pciBus, entry->deviceIds.VendorID, entry->deviceIds.DeviceID);
+ } else {
+ FF_DEBUG("Failed to get PCI address");
+ }
+ } else {
+ FF_DEBUG("Failed to get PCI bus number");
+ }
+ }
+ }
+
+ FF_LIST_FOR_EACH (CacheEntry, entry, deviceIdsCache) {
+ if (memcmp(&entry->adapterAddress, &adapterAddress, sizeof(adapterAddress)) == 0) {
+ FF_DEBUG("Cache hit for adapter address: bus=%u device=%u function=%u", adapterAddress.BusNumber, adapterAddress.DeviceNumber, adapterAddress.FunctionNumber);
+ *outDeviceIds = entry->deviceIds;
+ return true;
+ }
+ }
+
+ FF_DEBUG("Cache miss for adapter address: bus=%u device=%u function=%u", adapterAddress.BusNumber, adapterAddress.DeviceNumber, adapterAddress.FunctionNumber);
+ return false;
+}
+ #endif // FF_WIN81_COMPAT
+
+static bool queryVendorNameViaRegistry(FFstrbuf* vendor, D3DKMT_HANDLE hAdapter) {
+ // `KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID` reports the GUID value used by the adapter's registry key (DirectX and Video)
+
+ GUID guid;
+ NTSTATUS status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = hAdapter,
+ .Type = KMTQAITYPE_QUERY_ADAPTER_UNIQUE_GUID,
+ .pPrivateDriverData = &guid,
+ .PrivateDriverDataSize = sizeof(guid),
+ });
+ if (!NT_SUCCESS(status)) {
+ FF_DEBUG("Failed to query adapter unique GUID: %s", ffDebugNtStatus(status));
+ return false;
+ }
+
+ wchar_t path[PATH_MAX];
+ swprintf(path, ARRAY_SIZE(path), L"SYSTEM\\CurrentControlSet\\Control\\Video\\{%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X}\\0000", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1], guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
+
+ FF_DEBUG("Querying registry: HKEY_LOCAL_MACHINE\\%ls\\ProviderName", path);
+ FF_AUTO_CLOSE_FD HANDLE key = NULL;
+ if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, path, &key, NULL)) {
+ return false;
+ }
+
+ return ffRegReadStrbuf(key, L"ProviderName", vendor, NULL);
+}
+
+#else
+ #include <unistd.h>
+ #include <fcntl.h>
+ #include <sys/ioctl.h>
+ #include <uchar.h>
+
+int dxgfd = -2;
+
+static void ffStrbufSetWS(FFstrbuf* strbuf, const char16_t* str) {
+ ffStrbufClear(strbuf);
+
+ mbstate_t state = {};
+ while (*str) {
+ char buf[5];
+ size_t len = c16rtomb(buf, *str, &state);
+ if (len == (size_t) -1) {
+ ffStrbufAppendS(strbuf, "�"); // U+FFFD REPLACEMENT CHARACTER
+ } else if (len > 0) {
+ ffStrbufAppendNS(strbuf, (uint32_t) len, buf);
+ }
+ str++;
+ }
+}
+
+static void closeDxgfd(void) {
+ if (dxgfd >= 0) {
+ close(dxgfd);
+ dxgfd = 0;
+ FF_DEBUG("Closed /dev/dxg file descriptor");
+ }
+}
+
+FF_A_UNUSED static inline const char* ffDebugNtStatus(NTSTATUS status) {
+ return status < 0 ? strerror(-status) : "Success";
+}
+#endif
+
+const char*
+#if _WIN32
+ffDetectGPUImpl
+#else
+ffGPUDetectWsl2
+#endif
+ (const FFGPUOptions* options, FFlist* gpus) {
+#if __linux__
+ if (dxgfd == -2) {
+ dxgfd = open("/dev/dxg", O_RDWR); // Windows DXCore/D3DKMT adapter driver for WSL
+ if (dxgfd < 0) {
+ if (errno == ENOENT) {
+ FF_DEBUG("/dev/dxg is not available, WSL DXCore GPU driver not detected");
+ return "No DXCore GPU driver detected (no /dev/dxg)";
+ } else {
+ FF_DEBUG("Failed to open /dev/dxg: %s", strerror(errno));
+ return "Failed to open /dev/dxg";
+ }
+ }
+ FF_DEBUG("Opened /dev/dxg successfully");
+ atexit(closeDxgfd);
+ }
+ if (dxgfd < 0) {
+ return "Failed to open /dev/dxg";
+ }
+#endif
+
+#if FF_WIN81_COMPAT
+ D3DKMT_ENUMADAPTERS enumAdapters = {};
+ D3DKMT_ADAPTERINFO* const adapters = enumAdapters.Adapters;
+ NTSTATUS status = D3DKMTEnumAdapters(&enumAdapters);
+#else
+ D3DKMT_ADAPTERINFO adapters[64];
+ D3DKMT_ENUMADAPTERS2 enumAdapters = {
+ .NumAdapters = ARRAY_SIZE(adapters),
+ .pAdapters = adapters,
+ };
+ NTSTATUS status = D3DKMTEnumAdapters2(&enumAdapters);
+#endif
+ if (!NT_SUCCESS(status)) {
+ FF_DEBUG("D3DKMTEnumAdapters(2) failed: %s", ffDebugNtStatus(status));
+ return "Failed to enumerate adapters with D3DKMTEnumAdapters2";
+ }
+
+ FF_DEBUG("D3DKMTEnumAdapters(2) succeeded, adapter count: %" PRIu32, (uint32_t) enumAdapters.NumAdapters);
+
+ for (uint32_t i = 0; i < enumAdapters.NumAdapters; i++) {
+ const D3DKMT_ADAPTERINFO* adapter = &adapters[i];
+ FF_DEBUG("Processing adapter #%u", i);
+
+ D3DKMT_ADAPTERTYPE adapterType;
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_ADAPTERTYPE,
+ .pPrivateDriverData = &adapterType,
+ .PrivateDriverDataSize = sizeof(adapterType),
+ });
+ if (!NT_SUCCESS(status)) {
+ FF_DEBUG("KMTQAITYPE_ADAPTERTYPE query failed for adapter #%u: %s", i, ffDebugNtStatus(status));
+ continue;
+ }
+ if (adapterType.SoftwareDevice) {
+ FF_DEBUG("Skipping software adapter #%u", i);
+ goto close_adapter;
+ }
+
+ FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
+ ffStrbufInit(&gpu->vendor);
+ ffStrbufInit(&gpu->name);
+ ffStrbufInit(&gpu->driver);
+ ffStrbufInit(&gpu->platformApi);
+ ffStrbufInit(&gpu->memoryType);
+ gpu->index = FF_GPU_INDEX_UNSET;
+ gpu->temperature = FF_GPU_TEMP_UNSET;
+ gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
+ gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
+ gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
+ gpu->deviceId = 0;
+ gpu->frequency = FF_GPU_FREQUENCY_UNSET;
+ gpu->type = adapterType.HybridIntegrated
+ ? FF_GPU_TYPE_INTEGRATED
+ : adapterType.HybridDiscrete
+ ? FF_GPU_TYPE_DISCRETE
+ : FF_GPU_TYPE_UNKNOWN;
+
+ D3DKMT_DRIVERVERSION wddmVersion = KMT_DRIVERVERSION_WDDM_2_0;
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_DRIVERVERSION,
+ .pPrivateDriverData = &wddmVersion,
+ .PrivateDriverDataSize = sizeof(wddmVersion),
+ });
+ if (NT_SUCCESS(status)) {
+ ffStrbufSetF(&gpu->platformApi, "WDDM %u.%u", (uint32_t) wddmVersion / 1000, ((uint32_t) wddmVersion % 1000) / 100);
+ FF_DEBUG("Adapter #%u WDDM version: %u", i, (uint32_t) wddmVersion);
+ } else {
+ ffStrbufSetStatic(&gpu->platformApi, "WDDM");
+ FF_DEBUG("KMTQAITYPE_DRIVERVERSION query failed for adapter #%u", i);
+ }
+
+ D3DKMT_ADAPTERADDRESS adapterAddress = {};
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_ADAPTERADDRESS,
+ .pPrivateDriverData = &adapterAddress,
+ .PrivateDriverDataSize = sizeof(adapterAddress),
+ });
+ if (NT_SUCCESS(status) && adapterAddress.FunctionNumber != 0xFFFF /* non-PCI device */) {
+ gpu->deviceId = ffGPUPciAddr2Id(0, adapterAddress.BusNumber, adapterAddress.DeviceNumber, adapterAddress.FunctionNumber);
+ FF_DEBUG("Adapter #%u PCI address: bus=%u device=%u function=%u",
+ i,
+ adapterAddress.BusNumber,
+ adapterAddress.DeviceNumber,
+ adapterAddress.FunctionNumber);
+ } else {
+ adapterAddress.BusNumber = -1u;
+ gpu->deviceId = ffGPUGeneral2Id(((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart);
+ FF_DEBUG("KMTQAITYPE_ADAPTERADDRESS query failed for adapter #%u, fallback to LUID-based deviceId: %s",
+ i,
+ ffDebugNtStatus(status));
+ }
+
+ D3DKMT_QUERY_DEVICE_IDS deviceIds = { .PhysicalAdapterIndex = 0 };
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_PHYSICALADAPTERDEVICEIDS,
+ .pPrivateDriverData = &deviceIds,
+ .PrivateDriverDataSize = sizeof(deviceIds),
+ });
+ if (NT_SUCCESS(status)
+#if FF_WIN81_COMPAT
+ || queryDeviceIdsFallback(adapterAddress, &deviceIds.DeviceIds)
+#endif
+ ) {
+ ffStrbufSetStatic(&gpu->vendor, ffGPUGetVendorString(deviceIds.DeviceIds.VendorID));
+ FF_DEBUG("Adapter #%u vendor/device IDs: vendor=0x%04x device=0x%04x",
+ i,
+ deviceIds.DeviceIds.VendorID,
+ deviceIds.DeviceIds.DeviceID);
+ } else {
+ deviceIds.DeviceIds.VendorID = -1u;
+ FF_DEBUG("KMTQAITYPE_PHYSICALADAPTERDEVICEIDS query failed for adapter #%u: %s", i, ffDebugNtStatus(status));
+ }
+
+ D3DKMT_UMD_DRIVER_VERSION umdDriverVersion;
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_UMD_DRIVER_VERSION,
+ .pPrivateDriverData = &umdDriverVersion,
+ .PrivateDriverDataSize = sizeof(umdDriverVersion),
+ });
+ if (NT_SUCCESS(status)) {
+ ffStrbufSetF(&gpu->driver,
+ "%u.%u.%u.%u",
+ (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 48ul & 0xFFFF),
+ (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 32ul & 0xFFFF),
+ (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 16ul & 0xFFFF),
+ (uint32_t) (umdDriverVersion.DriverVersion.QuadPart >> 0ul & 0xFFFF));
+ FF_DEBUG("Adapter #%u UMD driver version: %08" PRIX64, i, (uint64_t) umdDriverVersion.DriverVersion.QuadPart);
+ } else {
+ FF_DEBUG("KMTQAITYPE_UMD_DRIVER_VERSION query failed for adapter #%u: %s", i, ffDebugNtStatus(status));
+ }
+
+ __typeof__(&ffDetectNvidiaGpuInfo) detectFn;
+ const char* dllName;
+ if (options->driverSpecific && getDriverSpecificDetectionFn(gpu->vendor.chars, &detectFn, &dllName)) {
+ FF_DEBUG("Calling driver-specific detection function for vendor: %s, DLL: %s", gpu->vendor.chars, dllName);
+ FF_A_UNUSED const char* error = detectFn(
+ &(FFGpuDriverCondition) {
+ .type = FF_GPU_DRIVER_CONDITION_TYPE_LUID |
+ (deviceIds.DeviceIds.VendorID != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_DEVICE_ID : 0) |
+ (adapterAddress.BusNumber != -1u ? FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID : 0),
+ .pciDeviceId = {
+ .deviceId = deviceIds.DeviceIds.DeviceID,
+ .vendorId = deviceIds.DeviceIds.VendorID,
+ .subSystemId = deviceIds.DeviceIds.SubSystemID,
+ .revId = deviceIds.DeviceIds.RevisionID,
+ },
+ .pciBusId = {
+ .domain = 0,
+ .bus = adapterAddress.BusNumber,
+ .device = adapterAddress.DeviceNumber,
+ .func = adapterAddress.FunctionNumber,
+ },
+ .luid = ((uint64_t) adapter->AdapterLuid.HighPart << 32) | (uint64_t) adapter->AdapterLuid.LowPart,
+ },
+ (FFGpuDriverResult) {
+ .index = &gpu->index,
+ .temp = options->temp ? &gpu->temperature : NULL,
+ .memory = options->driverSpecific ? &gpu->dedicated : NULL,
+ .sharedMemory = options->driverSpecific ? &gpu->shared : NULL,
+ .memoryType = options->driverSpecific ? &gpu->memoryType : NULL,
+ .coreCount = options->driverSpecific ? (uint32_t*) &gpu->coreCount : NULL,
+ .coreUsage = options->driverSpecific ? &gpu->coreUsage : NULL,
+ .type = &gpu->type,
+ .name = &gpu->name,
+ .frequency = options->driverSpecific ? &gpu->frequency : NULL,
+ },
+ dllName);
+ FF_DEBUG("Driver-specific detection completed: %s", error ?: "Success");
+ } else if (options->driverSpecific) {
+ FF_DEBUG("No driver-specific detection function found for vendor: %s", gpu->vendor.chars);
+ }
+
+#if _WIN32
+ // Put this after the driver-specific detection, as `getDriverSpecificDetectionFn` never succeeds
+ if (gpu->vendor.length == 0 && wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) {
+ // For non-PCI devices
+ FF_DEBUG("Attempting to query vendor name via registry for adapter #%u", i);
+ queryVendorNameViaRegistry(&gpu->vendor, adapter->hAdapter);
+ }
+#endif
+
+ if (gpu->name.length == 0) {
+ D3DKMT_ADAPTERREGISTRYINFO registryInfo;
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_ADAPTERREGISTRYINFO,
+ .pPrivateDriverData = &registryInfo,
+ .PrivateDriverDataSize = sizeof(registryInfo),
+ });
+ if (NT_SUCCESS(status)) {
+ ffStrbufSetWS(&gpu->name, registryInfo.AdapterString);
+ FF_DEBUG("Adapter #%u adapter string: %s", i, gpu->name.chars);
+ } else {
+ FF_DEBUG("KMTQAITYPE_ADAPTERREGISTRYINFO query failed for adapter #%u: %s", i, ffDebugNtStatus(status));
+ }
+ }
+
+ if (gpu->dedicated.total == FF_GPU_VMEM_SIZE_UNSET && gpu->shared.total == FF_GPU_VMEM_SIZE_UNSET) {
+ if (wddmVersion >= KMT_DRIVERVERSION_WDDM_3_1 && options->driverSpecific) {
+ // Supports memory usage query; requires Windows 11 (22H2) or later
+ D3DKMT_QUERYSTATISTICS queryStatistics = {
+ .Type = D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE,
+ .AdapterLuid = adapter->AdapterLuid,
+ .QuerySegmentGroupUsage = {
+ .PhysicalAdapterIndex = 0,
+ .SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_LOCAL,
+ },
+ };
+ status = D3DKMTQueryStatistics(&queryStatistics);
+ if (NT_SUCCESS(status)) {
+ D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation;
+ uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes;
+ uint64_t total = used + info->FreeBytes + info->ZeroBytes;
+ gpu->dedicated.used = used;
+ gpu->dedicated.total = total;
+ FF_DEBUG("Adapter #%u local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total);
+ } else {
+ FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (LOCAL) failed for adapter #%u: %s",
+ i,
+ ffDebugNtStatus(status));
+ }
+
+ queryStatistics.QuerySegmentGroupUsage.SegmentGroup = D3DKMT_MEMORY_SEGMENT_GROUP_NON_LOCAL;
+ status = D3DKMTQueryStatistics(&queryStatistics);
+ if (NT_SUCCESS(status)) {
+ D3DKMT_QUERYSTATISTICS_MEMORY_USAGE* info = &queryStatistics.QueryResult.SegmentGroupUsageInformation;
+ uint64_t used = info->AllocatedBytes + info->ModifiedBytes + info->StandbyBytes;
+ uint64_t total = used + info->FreeBytes + info->ZeroBytes;
+ gpu->shared.used = used;
+ gpu->shared.total = total;
+ FF_DEBUG("Adapter #%u non-local memory usage: used=%" PRIu64 " total=%" PRIu64, i, used, total);
+ } else {
+ FF_DEBUG("D3DKMT_QUERYSTATISTICS_SEGMENT_GROUP_USAGE (NON_LOCAL) failed for adapter #%u: %s",
+ i,
+ ffDebugNtStatus(status));
+ }
+ } else {
+ // Supports basic segment (total) size query
+ D3DKMT_SEGMENTSIZEINFO segmentSizeInfo = {};
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_GETSEGMENTSIZE,
+ .pPrivateDriverData = &segmentSizeInfo,
+ .PrivateDriverDataSize = sizeof(segmentSizeInfo),
+ });
+ if (NT_SUCCESS(status)) {
+ FF_DEBUG("Adapter #%u segment size - DedicatedVideoMemorySize: %" PRIu64
+ ", DedicatedSystemMemorySize: %" PRIu64 ", SharedSystemMemorySize: %" PRIu64,
+ i,
+ (uint64_t) segmentSizeInfo.DedicatedVideoMemorySize,
+ (uint64_t) segmentSizeInfo.DedicatedSystemMemorySize,
+ (uint64_t) segmentSizeInfo.SharedSystemMemorySize);
+ gpu->dedicated.total = segmentSizeInfo.DedicatedVideoMemorySize;
+ gpu->shared.total = segmentSizeInfo.DedicatedSystemMemorySize + segmentSizeInfo.SharedSystemMemorySize;
+ } else {
+ FF_DEBUG("Failed to query segment size information for adapter #%u: %s", i, ffDebugNtStatus(status));
+ }
+ }
+ }
+
+ if (wddmVersion >= KMT_DRIVERVERSION_WDDM_2_4) {
+ if (gpu->frequency == FF_GPU_FREQUENCY_UNSET) {
+ for (uint32_t nodeIdx = 0;; nodeIdx++) {
+ D3DKMT_NODEMETADATA nodeMetadata = {
+ .NodeOrdinalAndAdapterIndex = (0 << 16) | nodeIdx,
+ };
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_NODEMETADATA,
+ .pPrivateDriverData = &nodeMetadata,
+ .PrivateDriverDataSize = sizeof(nodeMetadata),
+ });
+ if (!NT_SUCCESS(status)) { break; }
+
+ if (nodeMetadata.NodeData.EngineType != DXGK_ENGINE_TYPE_3D) { continue; }
+
+ D3DKMT_NODE_PERFDATA nodePerfData = {
+ .NodeOrdinal = nodeIdx,
+ .PhysicalAdapterIndex = 0,
+ };
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_NODEPERFDATA,
+ .pPrivateDriverData = &nodePerfData,
+ .PrivateDriverDataSize = sizeof(nodePerfData),
+ });
+ if (NT_SUCCESS(status)) {
+ if (nodePerfData.MaxFrequency != 0) {
+ gpu->frequency = (uint32_t) (nodePerfData.MaxFrequency / 1000 / 1000);
+ FF_DEBUG("Adapter #%u max graphics frequency: %u MHz", i, gpu->frequency);
+ } else {
+ FF_DEBUG("Adapter #%u does not report max graphics frequency", i);
+ }
+ break;
+ } else {
+ FF_DEBUG("Failed to query node performance data for adapter #%u node #%u: %s",
+ i,
+ nodeIdx,
+ ffDebugNtStatus(status));
+ }
+ }
+ }
+
+ if (options->temp && gpu->temperature == FF_GPU_TEMP_UNSET) {
+ D3DKMT_ADAPTER_PERFDATA adapterPerfData = {
+ .PhysicalAdapterIndex = 0,
+ };
+ status = D3DKMTQueryAdapterInfo(&(D3DKMT_QUERYADAPTERINFO) {
+ .hAdapter = adapter->hAdapter,
+ .Type = KMTQAITYPE_ADAPTERPERFDATA,
+ .pPrivateDriverData = &adapterPerfData,
+ .PrivateDriverDataSize = sizeof(adapterPerfData),
+ });
+ if (NT_SUCCESS(status)) {
+ if (adapterPerfData.Temperature != 0) {
+ gpu->temperature = adapterPerfData.Temperature / 10.0;
+ FF_DEBUG("Adapter #%u temperature: %.1f°C", i, gpu->temperature);
+ } else {
+ FF_DEBUG("Adapter #%u does not report temperature data", i);
+ }
+ } else {
+ FF_DEBUG("Failed to query temperature for adapter #%u: %s", i, ffDebugNtStatus(status));
+ }
+ }
+ }
+
+ if (gpu->type == FF_GPU_TYPE_UNKNOWN) {
+ FF_DEBUG("Using fallback GPU type detection");
+ if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA) {
+ if (ffStrbufStartsWithIgnCaseS(&gpu->name, "GeForce") ||
+ ffStrbufStartsWithIgnCaseS(&gpu->name, "Quadro") ||
+ ffStrbufStartsWithIgnCaseS(&gpu->name, "Tesla")) {
+ gpu->type = FF_GPU_TYPE_DISCRETE;
+ }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_MTHREADS) {
+ if (ffStrbufStartsWithIgnCaseS(&gpu->name, "MTT ")) { gpu->type = FF_GPU_TYPE_DISCRETE; }
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) {
+ // 0000:00:02.0 is reserved for Intel integrated graphics
+ gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
+ } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_VMWARE || gpu->vendor.chars == FF_GPU_VENDOR_NAME_PARALLELS) {
+ // Virtualized GPUs
+ gpu->type = FF_GPU_TYPE_INTEGRATED;
+ }
+
+ if (gpu->type != FF_GPU_TYPE_UNKNOWN) {
+ FF_DEBUG("Determined GPU type based on vendor (%s) and name: %u", gpu->vendor.chars, gpu->type);
+ }
+#if _WIN32
+ else if (ffIsWindows10OrGreater()) {
+ const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult * gpu);
+ FF_A_UNUSED const char* error = ffGPUDetectTypeWithDXCore(adapter->AdapterLuid, gpu);
+ FF_DEBUG("DXCore GPU type detection result: %s", error ?: "Success");
+ }
+#endif
+ else {
+ FF_DEBUG("Unable to determine GPU type by any method for this adapter");
+ }
+ }
+
+ FF_DEBUG("Adapter #%u summary: name='%s', vendor='%s', type=%u, deviceId=%" PRIu64,
+ i,
+ gpu->name.length ? gpu->name.chars : "unknown",
+ gpu->vendor.length ? gpu->vendor.chars : "unknown",
+ (uint32_t) gpu->type,
+ (uint64_t) gpu->deviceId);
+
+ close_adapter:
+ status = D3DKMTCloseAdapter(&(D3DKMT_CLOSEADAPTER) { .hAdapter = adapter->hAdapter });
+ if (NT_SUCCESS(status)) {
+ FF_DEBUG("Closed adapter #%u successfully", i);
+ } else {
+ FF_DEBUG("Failed to close adapter #%u: %s", i, ffDebugNtStatus(status));
+ }
+ }
+
+ return NULL;
+}
diff --git a/src/detection/gpu/gpu_windows.cpp b/src/detection/gpu/gpu_windows.cpp
new file mode 100644
index 0000000..ae6f00e
--- /dev/null
+++ b/src/detection/gpu/gpu_windows.cpp
@@ -0,0 +1,83 @@
+extern "C" {
+#include "gpu.h"
+#include "common/library.h"
+#include "common/debug.h"
+}
+
+#if __has_include(<directx/dxcore.h>) && __has_include(<dxguids/dxguids.h>)
+
+ #include <directx/dxcore.h>
+ #include <dxguids/dxguids.h>
+ #include "common/windows/util.hpp"
+
+static IDXCoreAdapterFactory* loadDxCoreFactory() {
+ static bool initialized = false;
+ static IDXCoreAdapterFactory* factory = nullptr;
+ if (initialized) {
+ return factory; // Already loaded
+ }
+
+ initialized = true;
+ FF_LIBRARY_LOAD(dxcore, NULL, "dxcore" FF_LIBRARY_EXTENSION, 1)
+
+ // DXCoreCreateAdapterFactory is a reloaded function, so we can't use FF_LIBRARY_LOAD_SYMBOL_MESSAGE here
+ typedef HRESULT (*DXCoreCreateAdapterFactory_t)(REFIID riid, void** ppvFactory);
+
+ #ifndef FF_DISABLE_DLOPEN
+ auto ffDXCoreCreateAdapterFactory = (DXCoreCreateAdapterFactory_t) dlsym(dxcore, "DXCoreCreateAdapterFactory");
+ if (ffDXCoreCreateAdapterFactory == nullptr) {
+ return NULL;
+ }
+ #else
+ auto ffDXCoreCreateAdapterFactory = (DXCoreCreateAdapterFactory_t) DXCoreCreateAdapterFactory;
+ #endif
+
+ HRESULT hr = ffDXCoreCreateAdapterFactory(IID_PPV_ARGS(&factory));
+ if (FAILED(hr)) {
+ FF_DEBUG("DXCoreCreateAdapterFactory failed with HRESULT: 0x%08lX (%s)", hr, ffDebugHResult(hr));
+ return NULL;
+ }
+
+ dxcore = NULL; // Don't unload
+ return factory;
+}
+
+extern "C" const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult* gpu) {
+ auto* factory = loadDxCoreFactory();
+ if (!factory) {
+ return "Failed to load DXCore library or create adapter factory";
+ }
+
+ IDXCoreAdapter* adapter = nullptr;
+ HRESULT hr = factory->GetAdapterByLuid(adapterLuid, IID_PPV_ARGS(&adapter));
+ if (FAILED(hr)) {
+ FF_DEBUG("GetAdapterByLuid failed with HRESULT: 0x%08lX (%s)", hr, ffDebugHResult(hr));
+ return "Failed to get adapter by LUID";
+ }
+
+ on_scope_exit releaseAdapter{ [adapter] { adapter->Release(); } };
+
+ bool isIntegrated = false;
+ hr = adapter->GetProperty(DXCoreAdapterProperty::IsIntegrated, sizeof(isIntegrated), &isIntegrated);
+ if (FAILED(hr)) {
+ FF_DEBUG("GetProperty(IsIntegrated) failed with HRESULT: 0x%08lX (%s)", hr, ffDebugHResult(hr));
+ return "Failed to get adapter properties";
+ }
+
+ gpu->type = isIntegrated ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
+ FF_DEBUG("GPU type determined using DXCore: %s", isIntegrated ? "Integrated" : "Discrete");
+
+ return nullptr;
+}
+
+#else
+
+ #warning "DXCore headers not available, GPU type detection may be less accurate"
+
+extern "C" const char* ffGPUDetectTypeWithDXCore(LUID adapterLuid, FFGPUResult* gpu) {
+ FF_UNUSED(adapterLuid, gpu);
+ FF_DEBUG("DXCore not available, skipping GPU type detection with DXCore");
+ return "DXCore not available";
+}
+
+#endif
diff --git a/src/detection/gpu/igcl.h b/src/detection/gpu/igcl.h
new file mode 100644
index 0000000..6bf4ffe
--- /dev/null
+++ b/src/detection/gpu/igcl.h
@@ -0,0 +1,216 @@
+#pragma once
+
+// DISCLAIMER:
+// THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL IGCL API
+// DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT.
+
+#include <stdint.h>
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv412ctl_result_t
+typedef enum ctl_result_t {
+ CTL_RESULT_SUCCESS = 0,
+} ctl_result_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctl_application_id_t
+typedef struct ctl_application_id_t {
+ uint32_t Data1;
+ uint16_t Data2;
+ uint16_t Data3;
+ uint8_t Data4[8];
+} ctl_application_id_t;
+
+#define CTL_IMPL_VERSION ((1 /*major*/ << 16) | (1 /*minor*/ & 0x0000ffff))
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_init_flag_t
+typedef enum ctl_init_flag_t {
+ CTL_INIT_FLAG_USE_LEVEL_ZERO = 1,
+ CTL_INIT_FLAG_MAX
+} ctl_init_flag_t;
+
+typedef uint32_t ctl_version_info_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_init_args_t
+typedef struct ctl_init_args_t {
+ uint32_t Size;
+ uint8_t Version;
+ ctl_version_info_t AppVersion;
+ ctl_init_flag_t flags;
+ ctl_version_info_t SupportedVersion;
+ ctl_application_id_t ApplicationUID;
+} ctl_init_args_t;
+
+typedef struct ctl_api_handle_t* ctl_api_handle_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv47ctlInitP15ctl_init_args_tP16ctl_api_handle_t
+extern ctl_result_t ctlInit(ctl_init_args_t* pInitDesc, ctl_api_handle_t* phAPIHandle);
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlclose
+extern ctl_result_t ctlClose(ctl_api_handle_t hAPIHandle);
+
+typedef struct ctl_device_adapter_handle_t* ctl_device_adapter_handle_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv419ctlEnumerateDevices16ctl_api_handle_tP8uint32_tP27ctl_device_adapter_handle_t
+extern ctl_result_t ctlEnumerateDevices(ctl_api_handle_t hAPIHandle, uint32_t* pCount, ctl_device_adapter_handle_t* phDevices);
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_device_type_t
+typedef enum ctl_device_type_t {
+ CTL_DEVICE_TYPE_GRAPHICS = 1,
+ CTL_DEVICE_TYPE_SYSTEM = 2,
+ CTL_DEVICE_TYPE_MAX
+} ctl_device_type_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctl_firmware_version_t
+typedef struct ctl_firmware_version_t {
+ uint64_t major_version;
+ uint64_t minor_version;
+ uint64_t build_number;
+} ctl_firmware_version_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_adapter_bdf_t
+typedef struct ctl_adapter_bdf_t {
+ uint8_t bus;
+ uint8_t device;
+ uint8_t function;
+} ctl_adapter_bdf_t;
+
+#define IGCL_CTL_MAX_DEVICE_NAME_LEN 100
+#define IGCL_CTL_MAX_RESERVED_SIZE 112
+
+typedef enum ctl_adapter_properties_flag_t {
+ CTL_ADAPTER_PROPERTIES_FLAG_INTEGRATED = 1,
+} ctl_adapter_properties_flag_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv431ctl_device_adapter_properties_t
+typedef struct ctl_device_adapter_properties_t {
+ uint32_t Size;
+ uint8_t Version;
+ void* pDeviceID;
+ uint32_t device_id_size;
+ ctl_device_type_t device_type;
+ uint32_t /*ctl_supported_functions_flags_t*/ supported_subfunction_flags;
+ uint64_t driver_version;
+ ctl_firmware_version_t firmware_version;
+ uint32_t pci_vendor_id;
+ uint32_t pci_device_id;
+ uint32_t rev_id;
+ uint32_t num_eus_per_sub_slice;
+ uint32_t num_sub_slices_per_slice;
+ uint32_t num_slices;
+ char name[IGCL_CTL_MAX_DEVICE_NAME_LEN];
+ ctl_adapter_properties_flag_t graphics_adapter_properties;
+ uint32_t Frequency;
+ uint16_t pci_subsys_id;
+ uint16_t pci_subsys_vendor_id;
+ ctl_adapter_bdf_t adapter_bdf;
+ char reserved[IGCL_CTL_MAX_RESERVED_SIZE];
+} ctl_device_adapter_properties_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctlGetDeviceProperties27ctl_device_adapter_handle_tP31ctl_device_adapter_properties_t
+extern ctl_result_t ctlGetDeviceProperties(ctl_device_adapter_handle_t hDAhandle, ctl_device_adapter_properties_t* pProperties);
+
+typedef struct ctl_temp_handle_t* ctl_temp_handle_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlenumtemperaturesensors
+extern ctl_result_t ctlEnumTemperatureSensors(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_temp_handle_t* phTemperature);
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv427ctlTemperatureGetProperties17ctl_temp_handle_tP21ctl_temp_properties_t
+
+typedef enum ctl_temp_sensors_t {
+ CTL_TEMP_SENSORS_GLOBAL = 0,
+ CTL_TEMP_SENSORS_GPU = 1,
+ CTL_TEMP_SENSORS_MEMORY = 2,
+ CTL_TEMP_SENSORS_GLOBAL_MIN = 3,
+ CTL_TEMP_SENSORS_GPU_MIN = 4,
+ CTL_TEMP_SENSORS_MEMORY_MIN = 5,
+ CTL_TEMP_SENSORS_MAX
+} ctl_temp_sensors_t;
+
+typedef struct _ctl_temp_properties_t {
+ uint32_t Size;
+ uint8_t Version;
+ ctl_temp_sensors_t type;
+ double maxTemperature;
+} ctl_temp_properties_t;
+
+extern ctl_result_t ctlTemperatureGetProperties(ctl_temp_handle_t hTemperature, ctl_temp_properties_t* pTemperature);
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctlEnumMemoryModules27ctl_device_adapter_handle_tP8uint32_tP16ctl_mem_handle_t
+
+typedef struct ctl_mem_handle_t* ctl_mem_handle_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctlEnumMemoryModules27ctl_device_adapter_handle_tP8uint32_tP16ctl_mem_handle_t
+extern ctl_result_t ctlEnumMemoryModules(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_mem_handle_t* phMemory);
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv415ctl_mem_state_t
+typedef struct ctl_mem_state_t {
+ uint32_t Size;
+ uint8_t Version;
+ uint64_t free;
+ uint64_t size;
+} ctl_mem_state_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctlMemoryGetState16ctl_mem_handle_tP15ctl_mem_state_t
+extern ctl_result_t ctlMemoryGetState(ctl_mem_handle_t hMemory, ctl_mem_state_t* pState);
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv414ctl_mem_type_t
+typedef enum ctl_mem_type_t {
+ CTL_MEM_TYPE_HBM = 0,
+ CTL_MEM_TYPE_DDR = 1,
+ CTL_MEM_TYPE_DDR3 = 2,
+ CTL_MEM_TYPE_DDR4 = 3,
+ CTL_MEM_TYPE_DDR5 = 4,
+ CTL_MEM_TYPE_LPDDR = 5,
+ CTL_MEM_TYPE_LPDDR3 = 6,
+ CTL_MEM_TYPE_LPDDR4 = 7,
+ CTL_MEM_TYPE_LPDDR5 = 8,
+ CTL_MEM_TYPE_GDDR4 = 9,
+ CTL_MEM_TYPE_GDDR5 = 10,
+ CTL_MEM_TYPE_GDDR5X = 11,
+ CTL_MEM_TYPE_GDDR6 = 12,
+ CTL_MEM_TYPE_GDDR6X = 13,
+ CTL_MEM_TYPE_GDDR7 = 14,
+ CTL_MEM_TYPE_MAX
+} ctl_mem_type_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv413ctl_mem_loc_t
+typedef enum ctl_mem_loc_t {
+ CTL_MEM_LOC_SYSTEM = 0,
+ CTL_MEM_LOC_DEVICE = 1,
+ CTL_MEM_LOC_MAX
+} ctl_mem_loc_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv420ctl_mem_properties_t
+typedef struct ctl_mem_properties_t {
+ uint32_t Size;
+ uint8_t Version;
+ ctl_mem_type_t type;
+ ctl_mem_loc_t location;
+ uint64_t physicalSize;
+ int32_t busWidth;
+ int32_t numChannels;
+} ctl_mem_properties_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv422ctlMemoryGetProperties16ctl_mem_handle_tP20ctl_mem_properties_t
+extern ctl_result_t ctlMemoryGetProperties(ctl_mem_handle_t hMemory, ctl_mem_properties_t* pProperties);
+
+typedef struct ctl_freq_handle_t* ctl_freq_handle_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#ctlenumfrequencydomains
+extern ctl_result_t ctlEnumFrequencyDomains(ctl_device_adapter_handle_t hDAhandle, uint32_t* pCount, ctl_freq_handle_t* phFrequency);
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv417ctl_freq_domain_t
+typedef enum ctl_freq_domain_t {
+ CTL_FREQ_DOMAIN_GPU = 0,
+ CTL_FREQ_DOMAIN_MEMORY = 1,
+ CTL_FREQ_DOMAIN_MAX
+} ctl_freq_domain_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv421ctl_freq_properties_t
+typedef struct ctl_freq_properties_t {
+ uint32_t Size;
+ uint8_t Version;
+ ctl_freq_domain_t type;
+ bool canControl;
+ double min;
+ double max;
+} ctl_freq_properties_t;
+
+// https://intel.github.io/drivers.gpu.control-library/Control/api.html#_CPPv425ctlFrequencyGetProperties17ctl_freq_handle_tP21ctl_freq_properties_t
+extern ctl_result_t ctlFrequencyGetProperties(ctl_freq_handle_t hFrequency, ctl_freq_properties_t* pProperties);
diff --git a/src/detection/gpu/intel_drm.h b/src/detection/gpu/intel_drm.h
new file mode 100644
index 0000000..3e25748
--- /dev/null
+++ b/src/detection/gpu/intel_drm.h
@@ -0,0 +1,125 @@
+#pragma once
+
+/* SPDX-License-Identifier: MIT */
+#include <drm.h>
+
+// xe_drm.h
+
+/*
+ * Copyright © 2023 Intel Corporation
+ */
+
+#define DRM_XE_DEVICE_QUERY 0x00
+
+#define DRM_IOCTL_XE_DEVICE_QUERY DRM_IOWR(DRM_COMMAND_BASE + DRM_XE_DEVICE_QUERY, struct drm_xe_device_query)
+
+enum drm_xe_memory_class {
+ DRM_XE_MEM_REGION_CLASS_SYSMEM = 0,
+ DRM_XE_MEM_REGION_CLASS_VRAM
+};
+
+struct drm_xe_mem_region {
+ __u16 mem_class;
+ __u16 instance;
+ __u32 min_page_size;
+ __u64 total_size;
+ __u64 used;
+ __u64 cpu_visible_size;
+ __u64 cpu_visible_used;
+ __u64 reserved[6];
+};
+
+struct drm_xe_query_mem_regions {
+ __u32 num_mem_regions;
+ __u32 pad;
+ struct drm_xe_mem_region mem_regions[];
+};
+
+struct drm_xe_query_topology_mask {
+ __u16 gt_id;
+
+#define DRM_XE_TOPO_DSS_GEOMETRY 1
+#define DRM_XE_TOPO_DSS_COMPUTE 2
+#define DRM_XE_TOPO_EU_PER_DSS 4
+ __u16 type;
+ __u32 num_bytes;
+ __u8 mask[];
+};
+
+struct drm_xe_device_query {
+ __u64 extensions;
+
+#define DRM_XE_DEVICE_QUERY_MEM_REGIONS 1
+#define DRM_XE_DEVICE_QUERY_GT_TOPOLOGY 5
+ __u32 query;
+ __u32 size;
+ __u64 data;
+ __u64 reserved[2];
+};
+
+// i915_drm.h
+
+/*
+ * Copyright 2003 Tungsten Graphics, Inc., Cedar Park, Texas.
+ * All Rights Reserved.
+ */
+
+#define DRM_IOCTL_I915_GETPARAM DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_GETPARAM, drm_i915_getparam_t)
+
+struct drm_i915_getparam {
+ __s32 param;
+ int* value;
+};
+typedef struct drm_i915_getparam drm_i915_getparam_t;
+
+#define DRM_I915_GETPARAM 0x06
+#define DRM_I915_QUERY 0x39
+#define DRM_I915_QUERY_MEMORY_REGIONS 4
+#define DRM_IOCTL_I915_QUERY DRM_IOWR(DRM_COMMAND_BASE + DRM_I915_QUERY, struct drm_i915_query)
+#define I915_PARAM_EU_TOTAL 34
+
+struct drm_i915_query_item {
+ __u64 query_id;
+#define DRM_I915_QUERY_MEMORY_REGIONS 4
+
+ __s32 length;
+ __u32 flags;
+ __u64 data_ptr;
+};
+
+struct drm_i915_query {
+ __u32 num_items;
+ __u32 flags;
+ __u64 items_ptr;
+};
+
+enum drm_i915_gem_memory_class {
+ I915_MEMORY_CLASS_SYSTEM = 0,
+ I915_MEMORY_CLASS_DEVICE,
+};
+
+struct drm_i915_gem_memory_class_instance {
+ __u16 memory_class;
+ __u16 memory_instance;
+};
+
+struct drm_i915_memory_region_info {
+ struct drm_i915_gem_memory_class_instance region;
+ __u32 rsvd0;
+ __u64 probed_size;
+ __u64 unallocated_size;
+
+ union {
+ __u64 rsvd1[8];
+ struct {
+ __u64 probed_cpu_visible_size;
+ __u64 unallocated_cpu_visible_size;
+ };
+ };
+};
+
+struct drm_i915_query_memory_regions {
+ __u32 num_regions;
+ __u32 rsvd[3];
+ struct drm_i915_memory_region_info regions[];
+};
diff --git a/src/detection/gpu/mtml.h b/src/detection/gpu/mtml.h
new file mode 100644
index 0000000..9f62fc3
--- /dev/null
+++ b/src/detection/gpu/mtml.h
@@ -0,0 +1,110 @@
+#pragma once
+
+// DISCLAIMER:
+// THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL MTML API
+// DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT.
+
+#define MTML_DEVICE_PCI_SBDF_BUFFER_SIZE 32
+#define MTML_DEVICE_NAME_BUFFER_SIZE 32
+
+/**
+ * Return values for MTML API calls.
+ */
+typedef enum {
+ MTML_SUCCESS = 0,
+} MtmlReturn;
+
+/**
+ * The brand of the device.
+ */
+typedef enum {
+ MTML_BRAND_MTT = 0, //!< MTT series.
+} MtmlBrandType;
+
+typedef struct MtmlLibrary MtmlLibrary;
+typedef struct MtmlSystem MtmlSystem;
+typedef struct MtmlDevice MtmlDevice;
+typedef struct MtmlGpu MtmlGpu;
+typedef struct MtmlMemory MtmlMemory;
+
+/**
+ * PCI information about a device.
+ */
+typedef struct
+{
+ char sbdf[MTML_DEVICE_PCI_SBDF_BUFFER_SIZE]; //!< The tuple segment:bus:device.function PCI identifier (&amp; NULL terminator).
+ unsigned int segment; //!< The PCI segment group(domain) on which the device's bus resides, 0 to 0xffffffff.
+ unsigned int bus; //!< The bus on which the device resides, 0 to 0xff.
+ unsigned int device; //!< The device ID on the bus, 0 to 31.
+ unsigned int pciDeviceId; //!< The combined 16-bit device ID and 16-bit vendor ID.
+ unsigned int pciSubsystemId; //!< The 32-bit sub system device ID.
+ unsigned int busWidth; //!< @deprecated This value set to zero.
+ float pciMaxSpeed; //!< The maximum link speed (transfer rate per lane) of the device. The unit is GT/s.
+ float pciCurSpeed; //!< The current link speed (transfer rate per lane) of the device. The unit is GT/s.
+ unsigned int pciMaxWidth; //!< The maximum link width of the device.
+ unsigned int pciCurWidth; //!< The current link width of the device.
+ unsigned int pciMaxGen; //!< The maximum supported generation of the device.
+ unsigned int pciCurGen; //!< The current generation of the device.
+ int rsvd[6]; //!< Reserved for future extension.
+} MtmlPciInfo;
+
+// Retrieves the number of cores of a device.
+MtmlReturn mtmlDeviceCountGpuCores(const MtmlDevice* device, unsigned int* numCores);
+// Retrieves the brand of a device.
+MtmlReturn mtmlDeviceGetBrand(const MtmlDevice* dev, MtmlBrandType* type);
+// Retrieves the index associated with the specified device.
+MtmlReturn mtmlDeviceGetIndex(const MtmlDevice* dev, unsigned int* index);
+// Retrieves the name of a device.
+MtmlReturn mtmlDeviceGetName(const MtmlDevice* dev, char* name, unsigned int length);
+// Retrieves the PCI attributes of a device.
+MtmlReturn mtmlDeviceGetPciInfo(const MtmlDevice* dev, MtmlPciInfo* pci);
+/**
+ * Retrieves the UUID of a specified device. The UUID is a hexadecimal string in the
+ * form of xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx, where each 'x' is an ASCII character that represents a hexadecimal
+ * digit. The UUID is globally unique for every single device thus can be used to identify different devices
+ * physically.
+ */
+MtmlReturn mtmlDeviceGetUUID(const MtmlDevice* dev, char* uuid, unsigned int length);
+// Initializes a GPU opaque object to represent a specific graphic core on the target device that is designated by its index.
+MtmlReturn mtmlDeviceInitGpu(const MtmlDevice* dev, MtmlGpu** gpu);
+// Initializes a memory opaque object to represent the memory on the target device.
+MtmlReturn mtmlDeviceInitMemory(const MtmlDevice* dev, MtmlMemory** mem);
+
+// Retrieves the maximum supported clock speed for the device's graphic core.
+MtmlReturn mtmlGpuGetMaxClock(const MtmlGpu* gpu, unsigned int* clockMhz);
+// Retrieves the current temperature readings for the device's graphic core, in degrees Celsius.
+MtmlReturn mtmlGpuGetTemperature(const MtmlGpu* gpu, unsigned int* temp);
+// Retrieves the current utilization rate for the device's graphic core.
+MtmlReturn mtmlGpuGetUtilization(const MtmlGpu* gpu, unsigned int* utilization);
+
+// Retrieves the number of devices that can be accessed by the library opaque object.
+MtmlReturn mtmlLibraryCountDevice(const MtmlLibrary* lib, unsigned int* count);
+/**
+ * Initializes a device opaque object to represent a device that is designated by its index.
+ * The index ranges from (0) to (deviceCount - 1), where deviceCount is retrieved from \ref mtmlLibraryCountDevice().
+ */
+MtmlReturn mtmlLibraryInit(MtmlLibrary** lib);
+/**
+ * Initializes a device opaque object to represent a device that is designated by its index.
+ * The index ranges from (0) to (deviceCount - 1), where deviceCount is retrieved from \ref mtmlLibraryCountDevice().
+ */
+MtmlReturn mtmlLibraryInitDeviceByIndex(const MtmlLibrary* lib, unsigned int index, MtmlDevice** dev);
+/**
+ * Initializes a device opaque object to represent a device that is designated by its PCI Sbdf.
+ * The PCI Sbdf format like 00000000:3a:00.0 refer to \ref MtmlPciInfo::sbdf.
+ */
+MtmlReturn mtmlLibraryInitDeviceByPciSbdf(const MtmlLibrary* lib, const char* pciSbdf, MtmlDevice** dev);
+// Initializes a MtmlSystem opaque pointer that is bound to a library opaque object.
+MtmlReturn mtmlLibraryInitSystem(const MtmlLibrary* lib, MtmlSystem** sys);
+/**
+ * Shuts down the library opaque object that is previously initialized by \ref mtmlLibraryInit() and releases its resources.
+ * The \a lib pointer cannot be used anymore after this function returns.
+ */
+MtmlReturn mtmlLibraryShutDown(MtmlLibrary* lib);
+
+// Retrieves the amount of total memory available on the device, in bytes.
+MtmlReturn mtmlMemoryGetTotal(const MtmlMemory* mem, unsigned long long* total);
+// Retrieves the amount of used memory on the device, in bytes.
+MtmlReturn mtmlMemoryGetUsed(const MtmlMemory* mem, unsigned long long* used);
+// Retrieves the current memory utilization rate for the device.
+MtmlReturn mtmlMemoryGetUtilization(const MtmlMemory* mem, unsigned int* utilization);
diff --git a/src/detection/gpu/nvapi.h b/src/detection/gpu/nvapi.h
new file mode 100644
index 0000000..102d57a
--- /dev/null
+++ b/src/detection/gpu/nvapi.h
@@ -0,0 +1,50 @@
+// References:
+// https://github.com/NVIDIA/nvapi (MIT License)
+// https://github.com/deathcamp/NVOC/blob/master/nvoc.c (Public Domain)
+
+typedef enum NvApiGPUMemoryType {
+ NVAPI_GPU_MEMORY_TYPE_UNKNOWN = 0,
+ NVAPI_GPU_MEMORY_TYPE_SDRAM,
+ NVAPI_GPU_MEMORY_TYPE_DDR1,
+ NVAPI_GPU_MEMORY_TYPE_DDR2,
+ NVAPI_GPU_MEMORY_TYPE_GDDR2,
+ NVAPI_GPU_MEMORY_TYPE_GDDR3,
+ NVAPI_GPU_MEMORY_TYPE_GDDR4,
+ NVAPI_GPU_MEMORY_TYPE_DDR3,
+ NVAPI_GPU_MEMORY_TYPE_GDDR5,
+ NVAPI_GPU_MEMORY_TYPE_LPDDR2,
+ NVAPI_GPU_MEMORY_TYPE_GDDR5X,
+ NVAPI_GPU_MEMORY_TYPE_LPDDR3,
+ NVAPI_GPU_MEMORY_TYPE_LPDDR4,
+ NVAPI_GPU_MEMORY_TYPE_LPDDR5,
+ NVAPI_GPU_MEMORY_TYPE_GDDR6,
+ NVAPI_GPU_MEMORY_TYPE_GDDR6X,
+ NVAPI_GPU_MEMORY_TYPE_GDDR7,
+} NvApiGPUMemoryType;
+
+typedef enum {
+ NV_SYSTEM_TYPE_GPU_UNKNOWN = 0,
+ NV_SYSTEM_TYPE_IGPU = 1, // Integrated
+ NV_SYSTEM_TYPE_DGPU = 2, // Discrete
+} NvApiGPUType;
+
+typedef int NvAPI_Status; // 0 = success; < 0 = error
+typedef struct NvPhysicalGpuHandle* NvPhysicalGpuHandle;
+
+typedef enum {
+ NVAPI_INTERFACE_OFFSET_INITIALIZE = 0x0150E828,
+ NVAPI_INTERFACE_OFFSET_UNLOAD = 0xD22BDD7E,
+ NVAPI_INTERFACE_OFFSET_ENUM_PHYSICAL_GPUS = 0xE5AC921F,
+ NVAPI_INTERFACE_OFFSET_GPU_GET_RAM_TYPE = 0x57F7CAAC,
+ NVAPI_INTERFACE_OFFSET_GPU_GET_GPU_TYPE = 0xC33BAEB1,
+
+ NVAPI_INTERFACE_OFFSET_FORCE_UINT32 = 0xFFFFFFFF
+} NvApiInterfaceOffsets;
+
+extern void* nvapi_QueryInterface(NvApiInterfaceOffsets offset);
+
+extern NvAPI_Status nvapi_Initialize(void);
+extern NvAPI_Status nvapi_Unload(void);
+extern NvAPI_Status nvapi_EnumPhysicalGPUs(NvPhysicalGpuHandle* handles, int* count);
+extern NvAPI_Status nvapi_GPU_GetRamType(NvPhysicalGpuHandle handle, NvApiGPUMemoryType* memtype);
+extern NvAPI_Status nvapi_GPU_GetGPUType(NvPhysicalGpuHandle handle, NvApiGPUType* gpuType);
diff --git a/src/detection/gpu/nvml.h b/src/detection/gpu/nvml.h
new file mode 100644
index 0000000..74b1840
--- /dev/null
+++ b/src/detection/gpu/nvml.h
@@ -0,0 +1,152 @@
+#pragma once
+
+// DISCLAIMER:
+// THIS FILE IS CREATED FROM SCRATCH, BY READING THE OFFICIAL NVML API
+// DOCUMENTATION REFERENCED BELOW, IN ORDER TO MAKE FASTFETCH MIT COMPLIANT.
+
+// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceStructs.html
+#define NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE 32
+#define NVML_DEVICE_PCI_BUS_ID_BUFFER_V2_SIZE 16
+#define NVML_DEVICE_NAME_V2_BUFFER_SIZE 96
+
+typedef enum { NVML_SUCCESS = 0 } nvmlReturn_t;
+typedef struct nvmlDevice_t* nvmlDevice_t;
+
+// https://docs.nvidia.com/deploy/nvml-api/structnvmlPciInfo__t.html
+// PCI information about a GPU device
+typedef struct {
+ // The legacy tuple domain:bus:device.function PCI identifier (& NULL terminator)
+ char busIdLegacy[NVML_DEVICE_PCI_BUS_ID_BUFFER_V2_SIZE];
+ // The PCI domain on which the device's bus resides, 0 to 0xffffffff
+ unsigned int domain;
+ // The bus on which the device resides, 0 to 0xff
+ unsigned int bus;
+ // The device's id on the bus, 0 to 31
+ unsigned int device;
+ // The combined 16-bit device id and 16-bit vendor id
+ unsigned int pciDeviceId;
+ // The 32-bit Sub System Device ID
+ unsigned int pciSubSystemId;
+ // The tuple domain:bus:device.function PCI identifier (& NULL terminator)
+ char busId[NVML_DEVICE_PCI_BUS_ID_BUFFER_SIZE];
+} nvmlPciInfo_t;
+
+// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1g2650b526841fa38b8f293c2d509a1de0
+// Temperature sensors
+typedef enum {
+ // Temperature sensor for the GPU die
+ NVML_TEMPERATURE_GPU = 0,
+ NVML_TEMPERATURE_COUNT,
+} nvmlTemperatureSensors_t;
+
+// https://docs.nvidia.com/deploy/nvml-api/structnvmlMemory__v2__t.html#structnvmlMemory__v2__t
+// Memory allocation information for a device (v2)
+typedef struct {
+ // Structure format version (must be 2)
+ unsigned int version;
+ // Total physical device memory (in bytes)
+ unsigned long long total;
+ // Device memory (in bytes) reserved for system use (driver or firmware)
+ unsigned long long reserved;
+ // Unallocated device memory (in bytes)
+ unsigned long long free;
+ // Allocated device memory (in bytes)
+ unsigned long long used;
+} nvmlMemory_v2_t;
+// https://github.com/NVIDIA/nvidia-settings/issues/78#issuecomment-1012837988
+enum { nvmlMemory_v2 = (unsigned int) (sizeof(nvmlMemory_v2_t) | (2 << 24U)) };
+
+// https://docs.nvidia.com/deploy/nvml-api/structnvmlMemory__t.html#structnvmlMemory__t
+// Memory allocation information for a device (v1)
+typedef struct
+{
+ // Total physical device memory (in bytes)
+ unsigned long long total;
+ // Unallocated device memory (in bytes)
+ unsigned long long free;
+ // Sum of Reserved and Allocated device memory (in bytes)
+ unsigned long long used;
+} nvmlMemory_t;
+
+// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1g805c0647be9996589fc5e3f6ff680c64
+// Clock types
+typedef enum {
+ // Graphics clock domain
+ NVML_CLOCK_GRAPHICS = 0,
+ // SM clock domain
+ NVML_CLOCK_SM = 1,
+ // Memory clock domain
+ NVML_CLOCK_MEM = 2,
+ // Video encoder/decoder clock domain
+ NVML_CLOCK_VIDEO = 3,
+ // Count of clock types
+ NVML_CLOCK_COUNT,
+} nvmlClockType_t;
+
+// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceEnumvs.html#group__nvmlDeviceEnumvs_1gfa6b01990b212f7b49089b7158eafd2b
+// The Brand of the GPU
+typedef enum {
+ NVML_BRAND_UNKNOWN = 0,
+ NVML_BRAND_QUADRO = 1,
+ NVML_BRAND_TESLA = 2,
+ NVML_BRAND_NVS = 3,
+ NVML_BRAND_GRID = 4,
+ NVML_BRAND_GEFORCE = 5,
+ NVML_BRAND_TITAN = 6,
+ NVML_BRAND_NVIDIA_VAPPS = 7,
+ NVML_BRAND_NVIDIA_VPC = 8,
+ NVML_BRAND_NVIDIA_VCS = 9,
+ NVML_BRAND_NVIDIA_VWS = 10,
+ NVML_BRAND_NVIDIA_CLOUD_GAMING = 11,
+ NVML_BRAND_NVIDIA_VGAMING = NVML_BRAND_NVIDIA_CLOUD_GAMING,
+ NVML_BRAND_QUADRO_RTX = 12,
+ NVML_BRAND_NVIDIA_RTX = 13,
+ NVML_BRAND_NVIDIA = 14,
+ NVML_BRAND_GEFORCE_RTX = 15,
+ NVML_BRAND_TITAN_RTX = 16,
+ NVML_BRAND_COUNT,
+} nvmlBrandType_t;
+
+// https://docs.nvidia.com/deploy/nvml-api/structnvmlUtilization__t.html#structnvmlUtilization__t
+// Utilization information for a device.
+typedef struct
+{
+ // Percent of time over the past second during which one or more kernels was executing on the GPU
+ unsigned int gpu;
+ // Percent of time over the past second during which global (device) memory was being read or written
+ unsigned int memory;
+} nvmlUtilization_t;
+
+// https://docs.nvidia.com/deploy/nvml-api/group__nvmlInitializationAndCleanup.html#group__nvmlInitializationAndCleanup
+// Initialize NVML, but don't initialize any GPUs yet
+nvmlReturn_t nvmlInit_v2(void);
+// Shut down NVML by releasing all GPU resources previously allocated with nvmlInit_v2()
+nvmlReturn_t nvmlShutdown(void);
+
+// https://docs.nvidia.com/deploy/nvml-api/group__nvmlDeviceQueries.html
+// Retrieves the number of compute devices in the system. A compute device is a single GPU
+extern nvmlReturn_t nvmlDeviceGetCount_v2(unsigned int* deviceCount);
+// Acquire the handle for a particular device, based on its index
+extern nvmlReturn_t nvmlDeviceGetHandleByIndex_v2(unsigned int index, nvmlDevice_t* device);
+// Acquire the handle for a particular device, based on its PCI bus id
+extern nvmlReturn_t nvmlDeviceGetHandleByPciBusId_v2(const char* pciBusId, nvmlDevice_t* device);
+// Retrieves the PCI attributes of this device
+extern nvmlReturn_t nvmlDeviceGetPciInfo_v3(nvmlDevice_t device, nvmlPciInfo_t* pci);
+// Retrieves the current temperature readings for the device, in degrees C
+extern nvmlReturn_t nvmlDeviceGetTemperature(nvmlDevice_t device, nvmlTemperatureSensors_t sensorType, unsigned int* temp);
+// Retrieves the amount of used, free, reserved and total memory available on the device, in bytes. The reserved amount is supported on version 2 only
+extern nvmlReturn_t nvmlDeviceGetMemoryInfo_v2(nvmlDevice_t device, nvmlMemory_v2_t* memory);
+// Retrieves the amount of used, free, total memory available on the device, in bytes.
+extern nvmlReturn_t nvmlDeviceGetMemoryInfo(nvmlDevice_t device, nvmlMemory_t* memory);
+// Gets the device's core count
+extern nvmlReturn_t nvmlDeviceGetNumGpuCores(nvmlDevice_t device, unsigned int* numCores);
+// Retrieves the maximum clock speeds for the device
+extern nvmlReturn_t nvmlDeviceGetMaxClockInfo(nvmlDevice_t device, nvmlClockType_t type, unsigned int* clock);
+// Retrieves the brand of this device
+extern nvmlReturn_t nvmlDeviceGetBrand(nvmlDevice_t device, nvmlBrandType_t* type);
+// Retrieves the current utilization rates for the device
+extern nvmlReturn_t nvmlDeviceGetUtilizationRates(nvmlDevice_t device, nvmlUtilization_t* utilization);
+// Retrieves the globally unique immutable UUID associated with this device, as a 5 part hexadecimal string, that augments the immutable, board serial identifier.
+extern nvmlReturn_t nvmlDeviceGetIndex(nvmlDevice_t device, unsigned int* index);
+// Retrieves the name of this device.
+extern nvmlReturn_t nvmlDeviceGetName(nvmlDevice_t device, char* name, unsigned int length);