master ohmu/fastfetch-openbsd / src / detection / gpu / gpu_amd.c
ohmu  ·  2026-10-15
  1#include "gpu_driver_specific.h"
  2
  3#include "adl.h"
  4#include "common/library.h"
  5#include "common/mallocHelper.h"
  6#include "common/debug.h"
  7
  8// Helper function to convert ADL status code to string
  9[[maybe_unused]] static const char* ffAdlStatusToString(int status) {
 10    switch (status) {
 11#define FF_ADL_STATUS_CASE(name) \
 12    case name:                   \
 13        return #name;
 14        FF_ADL_STATUS_CASE(ADL_OK)
 15        FF_ADL_STATUS_CASE(ADL_OK_WARNING)
 16        FF_ADL_STATUS_CASE(ADL_OK_MODE_CHANGE)
 17        FF_ADL_STATUS_CASE(ADL_OK_RESTART)
 18        FF_ADL_STATUS_CASE(ADL_OK_WAIT)
 19        FF_ADL_STATUS_CASE(ADL_ERR)
 20        FF_ADL_STATUS_CASE(ADL_ERR_NOT_INIT)
 21        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM)
 22        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_PARAM_SIZE)
 23        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_ADL_IDX)
 24        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CONTROLLER_IDX)
 25        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_DIPLAY_IDX)
 26        FF_ADL_STATUS_CASE(ADL_ERR_NOT_SUPPORTED)
 27        FF_ADL_STATUS_CASE(ADL_ERR_NULL_POINTER)
 28        FF_ADL_STATUS_CASE(ADL_ERR_DISABLED_ADAPTER)
 29        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_CALLBACK)
 30        FF_ADL_STATUS_CASE(ADL_ERR_RESOURCE_CONFLICT)
 31        FF_ADL_STATUS_CASE(ADL_ERR_SET_INCOMPLETE)
 32        FF_ADL_STATUS_CASE(ADL_ERR_NO_XDISPLAY)
 33        FF_ADL_STATUS_CASE(ADL_ERR_CALL_TO_INCOMPATIABLE_DRIVER)
 34        FF_ADL_STATUS_CASE(ADL_ERR_NO_ADMINISTRATOR_PRIVILEGES)
 35        FF_ADL_STATUS_CASE(ADL_ERR_FEATURESYNC_NOT_STARTED)
 36        FF_ADL_STATUS_CASE(ADL_ERR_INVALID_POWER_STATE)
 37#undef FF_ADL_STATUS_CASE
 38        default:
 39            return "Unknown ADL error";
 40    }
 41}
 42
 43// Memory allocation function
 44static void* __stdcall ffAdlMainMemoryAlloc(int iSize) {
 45    return malloc((size_t) iSize);
 46}
 47
 48struct FFAdlData {
 49    FF_LIBRARY_SYMBOL(ADL2_Main_Control_Destroy)
 50    FF_LIBRARY_SYMBOL(ADL2_Adapter_AdapterInfoX3_Get)
 51    FF_LIBRARY_SYMBOL(ADL2_Adapter_Graphic_Core_Info_Get)
 52    FF_LIBRARY_SYMBOL(ADL2_Adapter_MemoryInfo2_Get)
 53    FF_LIBRARY_SYMBOL(ADL2_Adapter_DedicatedVRAMUsage_Get)
 54    FF_LIBRARY_SYMBOL(ADL2_Adapter_ASICFamilyType_Get)
 55    FF_LIBRARY_SYMBOL(ADL2_Adapter_ChipSetInfo_Get)
 56    FF_LIBRARY_SYMBOL(ADL2_Overdrive_Caps)
 57    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_CapabilitiesX2_Get)
 58    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_SystemClocksX2_Get)
 59    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_PerformanceStatus_Get)
 60    FF_LIBRARY_SYMBOL(ADL2_OverdriveN_Temperature_Get)
 61    FF_LIBRARY_SYMBOL(ADL2_Overdrive8_Current_Setting_Get)
 62    FF_LIBRARY_SYMBOL(ADL2_New_QueryPMLogData_Get)
 63    FF_LIBRARY_SYMBOL(ADL2_Overdrive6_CurrentStatus_Get)
 64    FF_LIBRARY_SYMBOL(ADL2_Overdrive6_Temperature_Get)
 65    FF_LIBRARY_SYMBOL(ADL2_Overdrive6_StateInfo_Get)
 66
 67    bool inited;
 68    ADL_CONTEXT_HANDLE apiHandle;
 69} adlData;
 70
 71static void shutdownAdl() {
 72    if (adlData.apiHandle) {
 73        FF_DEBUG("Destroying ADL context");
 74        adlData.ffADL2_Main_Control_Destroy(adlData.apiHandle);
 75        adlData.apiHandle = nullptr;
 76    }
 77}
 78
 79const char* ffDetectAmdGpuInfo(const FFGpuDriverCondition* cond, FFGpuDriverResult result, const char* soName) {
 80    FF_DEBUG("Attempting to detect AMD GPU info using '%s'", soName);
 81
 82    if (!adlData.inited) {
 83        adlData.inited = true;
 84        FF_DEBUG("Initializing ADL library");
 85        FF_LIBRARY_LOAD(atiadl, "dlopen atiadlxx failed", soName, 1);
 86        FF_LIBRARY_LOAD_SYMBOL_MESSAGE(atiadl, ADL2_Main_Control_Create)
 87        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Main_Control_Destroy)
 88        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_AdapterInfoX3_Get)
 89        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_Graphic_Core_Info_Get)
 90        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_MemoryInfo2_Get)
 91        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_DedicatedVRAMUsage_Get)
 92        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_ASICFamilyType_Get)
 93        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Adapter_ChipSetInfo_Get)
 94        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive_Caps)
 95        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_CapabilitiesX2_Get)
 96        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_SystemClocksX2_Get)
 97        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_PerformanceStatus_Get)
 98        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive8_Current_Setting_Get)
 99        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_New_QueryPMLogData_Get)
100        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_OverdriveN_Temperature_Get)
101        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_CurrentStatus_Get)
102        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_Temperature_Get)
103        FF_LIBRARY_LOAD_SYMBOL_VAR_MESSAGE(atiadl, adlData, ADL2_Overdrive6_StateInfo_Get)
104        FF_DEBUG("ADL library loaded");
105
106        int result = ffADL2_Main_Control_Create(ffAdlMainMemoryAlloc, 1 /*iEnumConnectedAdapters*/, &adlData.apiHandle);
107        FF_DEBUG("ADL2_Main_Control_Create returned %s (%d)", ffAdlStatusToString(result), result);
108        if (result != ADL_OK) {
109            return "ffADL2_Main_Control_Create() failed";
110        }
111
112        atexit(shutdownAdl);
113        atiadl = nullptr; // don't close atiadl
114        FF_DEBUG("ADL initialization complete");
115    }
116
117    if (!adlData.apiHandle) {
118        FF_DEBUG("ADL context not initialized");
119        return "ffADL2_Main_Control_Create() failed";
120    }
121
122    FF_AUTO_FREE AdapterInfo* devices = nullptr;
123    int numDevices = 0;
124    int adapterResult = adlData.ffADL2_Adapter_AdapterInfoX3_Get(adlData.apiHandle, -1, &numDevices, &devices);
125    FF_DEBUG("ADL2_Adapter_AdapterInfoX3_Get returned %s (%d)", ffAdlStatusToString(adapterResult), adapterResult);
126
127    if (adapterResult == ADL_OK) {
128        FF_DEBUG("found %d adapters", numDevices);
129    } else {
130        FF_DEBUG("ffADL2_Adapter_AdapterInfoX3_Get() failed");
131        return "ffADL2_Adapter_AdapterInfoX3_Get() failed";
132    }
133
134    const AdapterInfo* device = nullptr;
135    for (int iDev = 0; iDev < numDevices; iDev++) {
136        if (cond->type & FF_GPU_DRIVER_CONDITION_TYPE_BUS_ID) {
137            FF_DEBUG("Checking device %d: bus=%d, device=%d, func=%d against requested bus=%u, device=%u, func=%u",
138                iDev,
139                devices[iDev].iBusNumber,
140                devices[iDev].iDeviceNumber,
141                devices[iDev].iFunctionNumber,
142                cond->pciBusId.bus,
143                cond->pciBusId.device,
144                cond->pciBusId.func);
145
146            if (
147                cond->pciBusId.bus == (uint32_t) devices[iDev].iBusNumber &&
148                cond->pciBusId.device == (uint32_t) devices[iDev].iDeviceNumber &&
149                cond->pciBusId.func == (uint32_t) devices[iDev].iFunctionNumber) {
150                device = &devices[iDev];
151                FF_DEBUG("Found matching device: %s (index: %d)", device->strAdapterName, device->iAdapterIndex);
152                break;
153            }
154        }
155    }
156
157    if (!device) {
158        FF_DEBUG("Device not found");
159        return "Device not found";
160    }
161
162    if (result.coreCount) {
163        ADLGraphicCoreInfo coreInfo;
164        int status = adlData.ffADL2_Adapter_Graphic_Core_Info_Get(adlData.apiHandle, device->iAdapterIndex, &coreInfo);
165        FF_DEBUG("ADL2_Adapter_Graphic_Core_Info_Get returned %s (%d)", ffAdlStatusToString(status), status);
166
167        if (status == ADL_OK) {
168            FF_DEBUG("Core info - NumCUs: %d, NumPEsPerCU: %d", coreInfo.iNumCUs, coreInfo.iNumPEsPerCU);
169            *result.coreCount = (uint32_t) coreInfo.iNumCUs * (uint32_t) coreInfo.iNumPEsPerCU;
170            FF_DEBUG("Got core count: %u", *result.coreCount);
171        } else {
172            FF_DEBUG("Failed to get core count");
173        }
174    }
175
176    if (result.memory) {
177        int vramUsage = 0;
178        int status = adlData.ffADL2_Adapter_DedicatedVRAMUsage_Get(adlData.apiHandle, device->iAdapterIndex, &vramUsage);
179        FF_DEBUG("ADL2_Adapter_DedicatedVRAMUsage_Get returned %s (%d), usage: %d MB",
180            ffAdlStatusToString(status),
181            status,
182            vramUsage);
183
184        if (status == ADL_OK && vramUsage >= 0) {
185            result.memory->used = (uint64_t) vramUsage * 1024 * 1024;
186            FF_DEBUG("Dedicated VRAM usage: %llu bytes (%d MB)", result.memory->used, vramUsage);
187        } else {
188            FF_DEBUG("Failed to get dedicated VRAM usage");
189        }
190    }
191
192    if (result.memoryType) {
193        ADLMemoryInfo2 memoryInfo;
194        int status = adlData.ffADL2_Adapter_MemoryInfo2_Get(adlData.apiHandle, device->iAdapterIndex, &memoryInfo);
195        FF_DEBUG("ADL2_Adapter_MemoryInfo2_Get returned %s (%d)", ffAdlStatusToString(status), status);
196
197        if (status == ADL_OK) {
198            FF_DEBUG("Memory info - Type: %s, Size: %lld MB", memoryInfo.strMemoryType, memoryInfo.iMemorySize / 1024 / 1024);
199            ffStrbufSetS(result.memoryType, memoryInfo.strMemoryType);
200            FF_DEBUG("Got memory type: %s", memoryInfo.strMemoryType);
201        } else {
202            FF_DEBUG("Failed to get memory type");
203        }
204    }
205
206    if (result.type) {
207        int asicTypes = 0;
208        int valids = 0;
209        int status = adlData.ffADL2_Adapter_ASICFamilyType_Get(adlData.apiHandle, device->iAdapterIndex, &asicTypes, &valids);
210        FF_DEBUG("ADL2_Adapter_ASICFamilyType_Get returned %s (%d), asicTypes: 0x%x, valids: 0x%x",
211            ffAdlStatusToString(status),
212            status,
213            asicTypes,
214            valids);
215
216        if (status == ADL_OK) {
217            asicTypes &= valids; // This design is strange
218            *result.type = asicTypes & ADL_ASIC_INTEGRATED ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
219            FF_DEBUG("GPU type: %s (asicTypes: 0x%x, valids: 0x%x)",
220                *result.type == FF_GPU_TYPE_INTEGRATED ? "Integrated" : "Discrete",
221                asicTypes,
222                valids);
223        } else {
224            FF_DEBUG("Failed to get GPU type");
225        }
226    }
227
228    if (result.index) {
229        *result.index = (uint32_t) device->iAdapterIndex;
230        FF_DEBUG("Setting adapter index: %u", *result.index);
231    }
232
233    if (result.name) {
234        ffStrbufSetS(result.name, device->strAdapterName);
235        FF_DEBUG("Setting adapter name: %s; UDID: %s, Present: %d, Exist: %d", device->strAdapterName, device->strUDID, device->iPresent, device->iExist);
236    }
237
238    if (result.psMax || result.psCurr) {
239        ADLChipSetInfo chipSetInfo;
240        int status = adlData.ffADL2_Adapter_ChipSetInfo_Get(adlData.apiHandle, device->iAdapterIndex, &chipSetInfo);
241        FF_DEBUG("ADL2_Adapter_ChipSetInfo_Get returned %s (%d)", ffAdlStatusToString(status), status);
242
243        if (status == ADL_OK) {
244            FF_DEBUG("Chipset info - iBusSpeedType: %d, iMaxPCIELaneWidth: %d", chipSetInfo.iBusSpeedType, chipSetInfo.iMaxPCIELaneWidth);
245            if (result.psMax) {
246                if (chipSetInfo.iBusSpeedType >= 2) {
247                    result.psMax->gen = (uint16_t) (chipSetInfo.iBusSpeedType - 2);
248                    FF_DEBUG("Got PCIe Max Gen: %u", result.psMax->gen);
249                }
250                if (chipSetInfo.iMaxPCIELaneWidth > 0) {
251                    result.psMax->lanes = (uint16_t) chipSetInfo.iMaxPCIELaneWidth;
252                    FF_DEBUG("Got PCIe Max Lanes: %u", result.psMax->lanes);
253                }
254            }
255
256            FF_DEBUG("Chipset info - iBusType: %d, iCurrentPCIELaneWidth: %d", chipSetInfo.iBusType, chipSetInfo.iCurrentPCIELaneWidth);
257            if (result.psCurr) {
258                if (chipSetInfo.iBusType >= 2) {
259                    result.psCurr->gen = (uint16_t) (chipSetInfo.iBusType - 2);
260                    FF_DEBUG("Got PCIe Current Gen: %u", result.psCurr->gen);
261                }
262                if (chipSetInfo.iCurrentPCIELaneWidth > 0) {
263                    result.psCurr->lanes = (uint16_t) chipSetInfo.iCurrentPCIELaneWidth;
264                    FF_DEBUG("Got PCIe Current Lanes: %u", result.psCurr->lanes);
265                }
266            }
267        } else {
268            FF_DEBUG("Failed to get chipset information");
269        }
270    }
271
272    int odVersion = 0;
273
274    {
275        int odSupported = 0;
276        int odEnabled = 0;
277        int status = adlData.ffADL2_Overdrive_Caps(adlData.apiHandle, device->iAdapterIndex, &odSupported, &odEnabled, &odVersion);
278        FF_DEBUG("ADL2_Overdrive_Caps returned %s (%d); supported %d, enabled %d; version %d",
279            ffAdlStatusToString(status),
280            status,
281            odSupported,
282            odEnabled,
283            odVersion);
284        if (status != ADL_OK) {
285            FF_DEBUG("Overdrive not supported, results may be inaccurate");
286            // Note even if Overdrive is not supported, we can still get the OD version
287        }
288    }
289
290    if (odVersion == 8) {
291        FF_DEBUG("Using Overdrive8 API (odVersion=%d)", odVersion);
292
293        if (result.frequency) {
294            ADLOD8CurrentSetting currentSetting = { .count = OD8_COUNT };
295            int status = adlData.ffADL2_Overdrive8_Current_Setting_Get(adlData.apiHandle, device->iAdapterIndex, &currentSetting);
296            FF_DEBUG("ADL2_Overdrive8_Current_Setting_Get returned %s (%d)", ffAdlStatusToString(status), status);
297            if (status == ADL_OK) {
298                FF_DEBUG("OD8 Settings count: %d", currentSetting.count);
299
300                *result.frequency = (uint32_t) currentSetting.Od8SettingTable[OD8_GFXCLK_FMAX];
301                FF_DEBUG("Got max engine clock (OD8_GFXCLK_FMAX): %u MHz", *result.frequency);
302            } else {
303                FF_DEBUG("Failed to get max frequency information");
304            }
305        }
306
307        if (result.temp || result.coreUsage) {
308            ADLPMLogDataOutput pmLogDataOutput = {};
309            int status = adlData.ffADL2_New_QueryPMLogData_Get(adlData.apiHandle, device->iAdapterIndex, &pmLogDataOutput);
310            FF_DEBUG("ADL2_New_QueryPMLogData_Get returned %s (%d)", ffAdlStatusToString(status), status);
311            if (status == ADL_OK) {
312                if (result.temp) {
313                    ADLSingleSensorData* sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_HOTSPOT];
314                    FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_HOTSPOT, "ADL_PMLOG_TEMPERATURE_HOTSPOT", sensor->supported, sensor->value);
315                    if (sensor->supported) {
316                        *result.temp = sensor->value;
317                        FF_DEBUG("Temperature: %.1f°C (HOTSPOT)", *result.temp);
318                    } else {
319                        sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_GFX];
320                        FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_GFX, "ADL_PMLOG_TEMPERATURE_GFX", sensor->supported, sensor->value);
321                        if (sensor->supported) {
322                            *result.temp = sensor->value;
323                            FF_DEBUG("Temperature: %.1f°C (GFX)", *result.temp);
324                        } else {
325                            sensor = &pmLogDataOutput.sensors[ADL_PMLOG_TEMPERATURE_SOC];
326                            FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_TEMPERATURE_SOC, "ADL_PMLOG_TEMPERATURE_SOC", sensor->supported, sensor->value);
327                            if (sensor->supported) {
328                                *result.temp = sensor->value;
329                                FF_DEBUG("Temperature: %.1f°C (SOC)", *result.temp);
330                            } else {
331                                FF_DEBUG("No supported temp sensor found, temp detection failed");
332                            }
333                        }
334                    }
335                }
336                if (result.coreUsage) {
337                    ADLSingleSensorData* activity = &pmLogDataOutput.sensors[ADL_PMLOG_INFO_ACTIVITY_GFX];
338                    FF_DEBUG("Sensor %d: %s, supported: %d, value: %d", ADL_PMLOG_INFO_ACTIVITY_GFX, "ADL_PMLOG_INFO_ACTIVITY_GFX", activity->supported, activity->value);
339                    if (activity->supported) {
340                        *result.coreUsage = activity->value;
341                        FF_DEBUG("Core usage: %.1f%%", *result.coreUsage);
342                    } else {
343                        FF_DEBUG("Sensor %d not supported, GPU usage detection failed", ADL_PMLOG_INFO_ACTIVITY_GFX);
344                    }
345                }
346            } else {
347                FF_DEBUG("Failed to get temperature / GPU activity");
348            }
349        }
350    } else if (odVersion == 7) {
351        FF_DEBUG("Using OverdriveN API (odVersion=%d)", odVersion);
352
353        if (result.frequency) {
354            // https://github.com/MaynardMiner/odvii/blob/master/OverdriveN.cpp#L176
355            ADLODNCapabilitiesX2 odCapabilities = {};
356            int status = adlData.ffADL2_OverdriveN_CapabilitiesX2_Get(adlData.apiHandle, device->iAdapterIndex, &odCapabilities);
357            FF_DEBUG("ADL2_OverdriveN_CapabilitiesX2_Get returned %s (%d)", ffAdlStatusToString(status), status);
358
359            if (status == ADL_OK) {
360                if (odCapabilities.iMaximumNumberOfPerformanceLevels == 0) {
361                    FF_DEBUG("ADL2_OverdriveN_CapabilitiesX2_Get: no performance levels available");
362                } else {
363                    FF_DEBUG("ODN Capabilities - MaxPerformanceLevels: %d, GPU Clock Range: [%d - %d]",
364                        odCapabilities.iMaximumNumberOfPerformanceLevels,
365                        odCapabilities.sEngineClockRange.iMin,
366                        odCapabilities.sEngineClockRange.iMax);
367
368                    size_t size = sizeof(ADLODNPerformanceLevelsX2) + sizeof(ADLODNPerformanceLevelX2) * ((unsigned) odCapabilities.iMaximumNumberOfPerformanceLevels - 1);
369                    FF_AUTO_FREE ADLODNPerformanceLevelsX2* odPerfLevels = calloc(size, 1);
370                    odPerfLevels->iSize = (int) size;
371                    odPerfLevels->iNumberOfPerformanceLevels = odCapabilities.iMaximumNumberOfPerformanceLevels;
372                    odPerfLevels->iMode = ODNControlType_Current;
373
374                    int status = adlData.ffADL2_OverdriveN_SystemClocksX2_Get(adlData.apiHandle, device->iAdapterIndex, odPerfLevels);
375                    FF_DEBUG("ADL2_OverdriveN_SystemClocksX2_Get returned %s (%d), levels: %d",
376                        ffAdlStatusToString(status),
377                        status,
378                        odPerfLevels->iNumberOfPerformanceLevels);
379
380                    if (status != ADL_OK) {
381                        FF_DEBUG("Failed to get frequency information");
382                    } else {
383                        // lowest to highest
384                        for (int i = odPerfLevels->iNumberOfPerformanceLevels - 1; i >= 0; i--) {
385                            ADLODNPerformanceLevelX2* level = &odPerfLevels->aLevels[i];
386                            FF_DEBUG("Performance level %d: enabled: %d, engine clock = %d", i, level->iEnabled, level->iClock);
387                            if (level->iEnabled) {
388                                *result.frequency = (uint32_t) level->iClock / 100; // in 10 kHz
389                                FF_DEBUG("Got max engine clock: %u MHz", *result.frequency);
390                                break;
391                            }
392                        }
393                    }
394                }
395            } else {
396                FF_DEBUG("Failed to get frequency information");
397            }
398        }
399
400        if (result.coreUsage) {
401            ADLODNPerformanceStatus performanceStatus = {};
402            int status = adlData.ffADL2_OverdriveN_PerformanceStatus_Get(adlData.apiHandle, device->iAdapterIndex, &performanceStatus);
403            FF_DEBUG("ADL2_OverdriveN_PerformanceStatus_Get returned %s (%d)", ffAdlStatusToString(status), status);
404
405            if (status == ADL_OK) {
406                FF_DEBUG("Performance Status - Activity: %d%%, CoreClock: %dMHz, MemoryClock: %dMHz",
407                    performanceStatus.iGPUActivityPercent,
408                    performanceStatus.iCoreClock,
409                    performanceStatus.iMemoryClock);
410
411                *result.coreUsage = performanceStatus.iGPUActivityPercent;
412                FF_DEBUG("Got GPU activity: %d%%", performanceStatus.iGPUActivityPercent);
413            } else {
414                FF_DEBUG("Failed to get GPU activity");
415            }
416        }
417
418        if (result.temp) {
419            int milliDegrees = 0;
420            int status = adlData.ffADL2_OverdriveN_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, 1, &milliDegrees);
421            FF_DEBUG("ADL2_OverdriveN_Temperature_Get returned %s (%d)", ffAdlStatusToString(status), status);
422
423            if (status == ADL_OK) {
424                *result.temp = milliDegrees / 1000.0;
425                FF_DEBUG("Temperature: %.1f°C (raw: %d milliC)", *result.temp, milliDegrees);
426            } else {
427                FF_DEBUG("Failed to get temperature");
428            }
429        }
430    } else if (odVersion == 6) {
431        FF_DEBUG("Using Overdrive6 API (odVersion=%d)", odVersion);
432
433        if (result.frequency) {
434            FF_AUTO_FREE ADLOD6StateInfo* stateInfo = calloc(sizeof(ADLOD6StateInfo) + sizeof(ADLOD6PerformanceLevel), 1);
435            stateInfo->iNumberOfPerformanceLevels = 2;
436
437            int status = adlData.ffADL2_Overdrive6_StateInfo_Get(adlData.apiHandle, device->iAdapterIndex, ADL_OD6_GETSTATEINFO_CUSTOM_PERFORMANCE, stateInfo);
438            FF_DEBUG("ADL2_Overdrive6_StateInfo_Get returned %s (%d), performance levels: %d",
439                ffAdlStatusToString(status),
440                status,
441                stateInfo->iNumberOfPerformanceLevels);
442
443            if (status == ADL_OK) {
444                // OD6 uses clock ranges instead of discrete performance levels.
445                // iNumberOfPerformanceLevels is always 2.
446                // The 1st level indicates the minimum clocks in the range.
447                // The 2nd level indicates the maximum clocks in the range.
448                if (stateInfo->iNumberOfPerformanceLevels != 2) {
449                    FF_DEBUG("ADL2_Overdrive6_StateInfo_Get: unexpected number of performance levels: %d", stateInfo->iNumberOfPerformanceLevels);
450                } else {
451                    FF_DEBUG("OD6 Settings - MinPerformanceLevels: %d, MaxPerformanceLevels: %d",
452                        stateInfo->aLevels[0].iEngineClock,
453                        stateInfo->aLevels[1].iEngineClock);
454                    *result.frequency = (uint32_t) stateInfo->aLevels[1].iEngineClock / 100; // in 10 kHz
455                    FF_DEBUG("Got max engine clock: %u MHz", *result.frequency);
456                }
457            } else {
458                FF_DEBUG("Failed to get frequency information");
459            }
460        }
461
462        if (result.coreUsage) {
463            ADLOD6CurrentStatus status = {};
464            int apiStatus = adlData.ffADL2_Overdrive6_CurrentStatus_Get(adlData.apiHandle, device->iAdapterIndex, &status);
465            FF_DEBUG("ADL2_Overdrive6_CurrentStatus_Get returned %s (%d)", ffAdlStatusToString(apiStatus), apiStatus);
466
467            if (apiStatus == ADL_OK) {
468                *result.coreUsage = status.iActivityPercent;
469                FF_DEBUG("Got GPU activity: %d%%", status.iActivityPercent);
470            } else {
471                FF_DEBUG("Failed to get GPU activity");
472            }
473        }
474
475        if (result.temp) {
476            int milliDegrees = 0;
477            int status = adlData.ffADL2_Overdrive6_Temperature_Get(adlData.apiHandle, device->iAdapterIndex, &milliDegrees);
478            FF_DEBUG("ADL2_Overdrive6_Temperature_Get returned %s (%d), temperature: %d milliC",
479                ffAdlStatusToString(status),
480                status,
481                milliDegrees);
482
483            if (status == ADL_OK) {
484                *result.temp = milliDegrees / 1000.0;
485                FF_DEBUG("Temperature: %.1f°C", *result.temp);
486            } else {
487                FF_DEBUG("Failed to get temperature");
488            }
489        }
490    } else {
491        FF_DEBUG("Unknown Overdrive version: %d", odVersion);
492        return "Unknown Overdrive version";
493    }
494    FF_DEBUG("AMD GPU detection complete - returning success");
495    return nullptr;
496}