From 76424950e373d3b04ac3dd13019151bfba3e8423 Mon Sep 17 00:00:00 2001 From: sumuel Date: Mon, 17 Aug 2026 20:44:55 +0000 Subject: Add the files --- src/detection/gpu/adl.h | 66 ++ src/detection/gpu/asahi_drm.h | 1194 +++++++++++++++++++++++++++++++ src/detection/gpu/d3dkmthk.h | 362 ++++++++++ src/detection/gpu/gpu.c | 208 ++++++ src/detection/gpu/gpu.h | 89 +++ src/detection/gpu/gpu_amd.c | 460 ++++++++++++ src/detection/gpu/gpu_android.c | 55 ++ src/detection/gpu/gpu_apple.c | 208 ++++++ src/detection/gpu/gpu_apple.m | 87 +++ src/detection/gpu/gpu_bsd.c | 230 ++++++ src/detection/gpu/gpu_driver_specific.h | 90 +++ src/detection/gpu/gpu_drm.c | 380 ++++++++++ src/detection/gpu/gpu_gnu.c | 146 ++++ src/detection/gpu/gpu_haiku.c | 51 ++ src/detection/gpu/gpu_intel.c | 231 ++++++ src/detection/gpu/gpu_linux.c | 663 +++++++++++++++++ src/detection/gpu/gpu_mthreads.c | 201 ++++++ src/detection/gpu/gpu_nbsd.c | 120 ++++ src/detection/gpu/gpu_nosupport.c | 6 + src/detection/gpu/gpu_nvidia.c | 299 ++++++++ src/detection/gpu/gpu_obsd.c | 101 +++ src/detection/gpu/gpu_pci.c | 251 +++++++ src/detection/gpu/gpu_sunos.c | 54 ++ src/detection/gpu/gpu_windows.c | 605 ++++++++++++++++ src/detection/gpu/gpu_windows.cpp | 83 +++ src/detection/gpu/igcl.h | 216 ++++++ src/detection/gpu/intel_drm.h | 125 ++++ src/detection/gpu/mtml.h | 110 +++ src/detection/gpu/nvapi.h | 50 ++ src/detection/gpu/nvml.h | 152 ++++ 30 files changed, 6893 insertions(+) create mode 100644 src/detection/gpu/adl.h create mode 100644 src/detection/gpu/asahi_drm.h create mode 100644 src/detection/gpu/d3dkmthk.h create mode 100644 src/detection/gpu/gpu.c create mode 100644 src/detection/gpu/gpu.h create mode 100644 src/detection/gpu/gpu_amd.c create mode 100644 src/detection/gpu/gpu_android.c create mode 100644 src/detection/gpu/gpu_apple.c create mode 100644 src/detection/gpu/gpu_apple.m create mode 100644 src/detection/gpu/gpu_bsd.c create mode 100644 src/detection/gpu/gpu_driver_specific.h create mode 100644 src/detection/gpu/gpu_drm.c create mode 100644 src/detection/gpu/gpu_gnu.c create mode 100644 src/detection/gpu/gpu_haiku.c create mode 100644 src/detection/gpu/gpu_intel.c create mode 100644 src/detection/gpu/gpu_linux.c create mode 100644 src/detection/gpu/gpu_mthreads.c create mode 100644 src/detection/gpu/gpu_nbsd.c create mode 100644 src/detection/gpu/gpu_nosupport.c create mode 100644 src/detection/gpu/gpu_nvidia.c create mode 100644 src/detection/gpu/gpu_obsd.c create mode 100644 src/detection/gpu/gpu_pci.c create mode 100644 src/detection/gpu/gpu_sunos.c create mode 100644 src/detection/gpu/gpu_windows.c create mode 100644 src/detection/gpu/gpu_windows.cpp create mode 100644 src/detection/gpu/igcl.h create mode 100644 src/detection/gpu/intel_drm.h create mode 100644 src/detection/gpu/mtml.h create mode 100644 src/detection/gpu/nvapi.h create mode 100644 src/detection/gpu/nvml.h (limited to 'src/detection/gpu') 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 + +#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 +#include +#include +#include + +#if _WIN32 + #include + #include +#else + #include + #include + #include + +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, ¤tSetting); + 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 + +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 + +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 + +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 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 +#import + +#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 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 +#include +#if __has_include() + #include // FreeBSD +#else + #include // 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 + +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 + #include + #include + + #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 + #include + +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 + #include + +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) ¶msGlobal, + .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 +#include +#include + +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 + +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 +#include + +#ifdef FF_HAVE_DRM_AMDGPU + #include + #include + #include +#endif + +#ifdef FF_HAVE_DRM + #include "intel_drm.h" + #include + #include +#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() + #include + #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 +#include +#include +#include +#include +#include + +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 +#include +#include +#include +#include +#include + +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 +#ifdef __FreeBSD__ + #include + #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 + +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 +#include "d3dkmthk.h" + +#if _WIN32 + #include "common/windows/unicode.h" + #include "common/windows/registry.h" + + #if FF_WIN81_COMPAT + #include "common/mallocHelper.h" + #include + #include + #include + + #define GUID_DEVCLASS_DISPLAY_STRING L"{4d36e968-e325-11ce-bfc1-08002be10318}" // Found in + +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 + #include + #include + #include + +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 = ®istryInfo, + .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() && __has_include() + + #include + #include + #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 + +// 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 + +// 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 (& 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); -- cgit v1.2.3