master ohmu/fastfetch-openbsd / src / detection / gpu / gpu_linux.c
ohmu  ·  2026-10-15
  1#include "detection/gpu/gpu.h"
  2#include "common/debug.h"
  3#include "common/io.h"
  4#include "common/FFstrbuf.h"
  5#include "common/strutil.h"
  6#include "modules/gpu/option.h"
  7
  8#include <inttypes.h>
  9#include <stdint.h>
 10
 11static bool detectDriverFromSysfs(FFstrbuf* result, FFstrbuf* pciDir, FFstrbuf* buffer, [[maybe_unused]] const char* drmKey) {
 12    uint32_t pciDirLength = pciDir->length;
 13    ffStrbufAppendS(pciDir, "/driver");
 14    char pathBuf[PATH_MAX];
 15    ssize_t resultLength = readlink(pciDir->chars, pathBuf, ARRAY_SIZE(pathBuf));
 16    if (resultLength <= 0) {
 17        return false;
 18    }
 19
 20    const char* slash = memrchr(pathBuf, '/', (size_t) resultLength);
 21    if (slash) {
 22        slash++;
 23        ffStrbufSetNS(result, (uint32_t) (resultLength - (slash - pathBuf)), slash);
 24    }
 25
 26    if (ffStrbufEqualS(result, "nvidia")) {
 27        if (ffReadFileBuffer("/proc/driver/nvidia/version", buffer)) {
 28            if (ffStrbufContainS(buffer, " Open ")) {
 29                ffStrbufAppendS(result, " (open source)");
 30            } else {
 31                ffStrbufAppendS(result, " (proprietary)");
 32            }
 33        }
 34    }
 35
 36    if (instance.config.general.detectVersion) {
 37        ffStrbufAppendS(pciDir, "/module/version");
 38        if (ffReadFileBuffer(pciDir->chars, buffer)) {
 39            ffStrbufTrimRightSpace(buffer);
 40            ffStrbufAppendC(result, ' ');
 41            ffStrbufAppend(result, buffer);
 42        } else if (ffStrbufEqualS(result, "zx")) {
 43            ffStrbufSubstrBefore(pciDir, pciDirLength);
 44            ffStrbufAppendS(pciDir, "/zx_info/driver_version");
 45            if (ffReadFileBuffer(pciDir->chars, buffer)) {
 46                ffStrbufTrimRightSpace(buffer);
 47                ffStrbufAppendC(result, ' ');
 48                ffStrbufAppend(result, buffer);
 49            }
 50        }
 51    }
 52
 53    return true;
 54}
 55
 56[[maybe_unused]] static const char* drmFindRenderFromCard(const char* drmCardKey, FFstrbuf* result) {
 57    char path[PATH_MAX];
 58    sprintf(path, "/sys/class/drm/%s/device/drm", drmCardKey);
 59    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(path);
 60    if (!dirp) {
 61        FF_DEBUG("opendir(%s) failed: %s", path, strerror(errno));
 62        return "Failed to open `/sys/class/drm/{drmCardKey}/device/drm`";
 63    }
 64
 65    struct dirent* entry;
 66    while ((entry = readdir(dirp)) != nullptr) {
 67        if (ffStrStartsWith(entry->d_name, "render")) {
 68            ffStrbufSetS(result, "/dev/dri/");
 69            ffStrbufAppendS(result, entry->d_name);
 70            return nullptr;
 71        }
 72    }
 73    FF_DEBUG("Failed to find render device");
 74    return "Failed to find render device";
 75}
 76
 77static const char* drmDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) {
 78    const char* error = drmFindRenderFromCard(drmKey, buffer);
 79    if (error) {
 80        return error;
 81    }
 82    if (ffStrbufEqualS(&gpu->driver, "radeon")) {
 83        return ffDrmDetectRadeon(options, gpu, buffer->chars);
 84    } else {
 85        return ffDrmDetectAmdgpu(options, gpu, buffer->chars);
 86    }
 87}
 88
 89static void pciDetectAmdSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) {
 90    // https://www.kernel.org/doc/html/v5.10/gpu/amdgpu.html#mem-info-vis-vram-total
 91    const uint32_t pciDirLen = pciDir->length;
 92
 93    ffStrbufAppendS(pciDir, "/hwmon/");
 94    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);
 95    if (!dirp) {
 96        ffStrbufSubstrBefore(pciDir, pciDirLen);
 97        return;
 98    }
 99
100    struct dirent* entry;
101    while ((entry = readdir(dirp)) != nullptr) {
102        if (entry->d_name[0] == '.') {
103            continue;
104        }
105        break;
106    }
107    if (!entry) {
108        ffStrbufSubstrBefore(pciDir, pciDirLen);
109        return;
110    }
111    ffStrbufAppendS(pciDir, entry->d_name);
112    ffStrbufAppendC(pciDir, '/');
113
114    const uint32_t hwmonLen = pciDir->length;
115    uint64_t value = 0;
116    if (options->temp) {
117        ffStrbufAppendS(pciDir, "temp1_input"); // The on die GPU temperature in millidegrees Celsius
118        if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
119            gpu->temperature = (double) value / 1000;
120        }
121    }
122
123    if (ffStrbufEqualS(&gpu->driver, "amdgpu")) // Ancient radeon drivers don't have these files
124    {
125        ffStrbufSubstrBefore(pciDir, hwmonLen);
126        ffStrbufAppendS(pciDir, "in1_input"); // Northbridge voltage in millivolts (APUs only)
127        if (ffPathExists(pciDir->chars, FF_PATHTYPE_ANY)) {
128            gpu->type = FF_GPU_TYPE_INTEGRATED;
129        } else {
130            gpu->type = FF_GPU_TYPE_DISCRETE;
131        }
132
133        if (options->driverSpecific) {
134            ffStrbufSubstrBefore(pciDir, pciDirLen);
135            ffStrbufAppendS(pciDir, "/mem_info_vis_vram_total");
136            if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
137                if (gpu->type == FF_GPU_TYPE_DISCRETE) {
138                    gpu->dedicated.total = value;
139                } else {
140                    gpu->shared.total = value;
141                }
142
143                ffStrbufSubstrBefore(pciDir, pciDir->length - (uint32_t) strlen("/mem_info_vis_vram_total"));
144                ffStrbufAppendS(pciDir, "/mem_info_vis_vram_used");
145                if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
146                    if (gpu->type == FF_GPU_TYPE_DISCRETE) {
147                        gpu->dedicated.used = value;
148                    } else {
149                        gpu->shared.used = value;
150                    }
151                }
152            }
153
154            ffStrbufSubstrBefore(pciDir, pciDirLen);
155            ffStrbufAppendS(pciDir, "/gpu_busy_percent");
156            if (ffReadFileBuffer(pciDir->chars, buffer) && (value = ffStrbufToUInt(buffer, 0))) {
157                gpu->coreUsage = (double) value;
158            }
159        }
160    }
161}
162
163static void pciDetectIntelSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer, const char* drmKey) {
164    // Works for Intel GPUs
165    // https://patchwork.kernel.org/project/intel-gfx/patch/1422039866-11572-3-git-send-email-ville.syrjala@linux.intel.com/
166
167    // 0000:00:02.0 is reserved for Intel integrated graphics
168    gpu->type = gpu->deviceId == ffGPUPciAddr2Id(0, 0, 2, 0) ? FF_GPU_TYPE_INTEGRATED : FF_GPU_TYPE_DISCRETE;
169
170    if (!drmKey) {
171        return;
172    }
173
174    const uint32_t pciDirLen = pciDir->length;
175
176    bool isXE = ffStrbufEqualS(&gpu->driver, "xe");
177    if (isXE) {
178        ffStrbufAppendS(pciDir, "/tile0/gt0/freq0/max_freq");
179    } else {
180        ffStrbufAppendF(pciDir, "/drm/%s/gt_max_freq_mhz", drmKey);
181    }
182    if (ffReadFileBuffer(pciDir->chars, buffer)) {
183        gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, 0);
184    }
185    ffStrbufSubstrBefore(pciDir, pciDirLen);
186
187    if (options->temp) {
188        ffStrbufAppendS(pciDir, "/hwmon/");
189        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);
190        if (dirp) {
191            struct dirent* entry;
192            while ((entry = readdir(dirp)) != nullptr) {
193                if (entry->d_name[0] == '.') {
194                    continue;
195                }
196
197                ffStrbufSubstrBefore(pciDir, pciDirLen + strlen("/hwmon/"));
198                ffStrbufAppendS(pciDir, entry->d_name);
199                // https://github.com/Syllo/nvtop/blob/73291884d926445e499d6b9b71cb7a9bdbc7c393/src/extract_gpuinfo_intel.c#L279-L281
200                ffStrbufAppendS(pciDir, isXE ? "/temp2_input" : "/temp1_input");
201
202                if (ffReadFileBuffer(pciDir->chars, buffer)) {
203                    uint64_t value = ffStrbufToUInt(buffer, 0);
204                    if (value > 0) {
205                        gpu->temperature = (double) value / 1000;
206                        break;
207                    }
208                }
209            }
210        }
211        ffStrbufSubstrBefore(pciDir, pciDirLen);
212    }
213}
214
215static const char* drmDetectIntelSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) {
216    ffStrbufSetS(buffer, "/dev/dri/");
217    ffStrbufAppendS(buffer, drmKey);
218    FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);
219    if (fd < 0) {
220        FF_DEBUG("open(%s) failed: %s", buffer->chars, strerror(errno));
221        return "Failed to open drm device";
222    }
223
224    if (ffStrbufEqualS(&gpu->driver, "xe")) {
225        return ffDrmDetectXe(gpu, fd);
226    } else if (ffStrbufEqualS(&gpu->driver, "i915")) {
227        return ffDrmDetectI915(gpu, fd);
228    }
229    FF_DEBUG("Unknown Intel GPU driver");
230    return "Unknown Intel GPU driver";
231}
232
233static const char* pciDetectTempGeneral(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) {
234    if (options->temp) {
235        const uint32_t pciDirLen = pciDir->length;
236        ffStrbufAppendS(pciDir, "/hwmon/");
237        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDir->chars);
238        if (dirp) {
239            struct dirent* entry;
240            while ((entry = readdir(dirp))) {
241                if (entry->d_name[0] == '.') {
242                    continue;
243                }
244                ffStrbufAppendS(pciDir, entry->d_name);
245                ffStrbufAppendS(pciDir, "/temp1_input");
246                if (ffReadFileBuffer(pciDir->chars, buffer)) {
247                    uint64_t value = ffStrbufToUInt(buffer, 0);
248                    if (value > 0) {
249                        gpu->temperature = (double) value / 1000.0;
250                    }
251                }
252                break;
253            }
254        }
255        ffStrbufSubstrBefore(pciDir, pciDirLen);
256    }
257    return nullptr;
258}
259
260static const char* drmDetectNouveauSpecific(FFGPUResult* gpu, const char* drmKey, FFstrbuf* buffer) {
261    ffStrbufSetS(buffer, "/dev/dri/");
262    ffStrbufAppendS(buffer, drmKey);
263    FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);
264    if (fd < 0) {
265        FF_DEBUG("open(%s) failed: %s", buffer->chars, strerror(errno));
266        return "Failed to open drm device";
267    }
268
269    return ffDrmDetectNouveau(gpu, fd);
270}
271
272static const char* pciDetectZxSpecific(const FFGPUOptions* options, FFGPUResult* gpu, FFstrbuf* pciDir, FFstrbuf* buffer) {
273    gpu->type = FF_GPU_TYPE_INTEGRATED;
274
275    const uint32_t pciDirLen = pciDir->length;
276    ffStrbufAppendS(pciDir, "/zx_info/eclk");
277    if (ffReadFileBuffer(pciDir->chars, buffer)) {
278        gpu->frequency = (uint32_t) ffStrbufToUInt(buffer, FF_GPU_FREQUENCY_UNSET);
279    }
280    ffStrbufSubstrBefore(pciDir, pciDirLen);
281
282    if (options->driverSpecific) {
283        ffStrbufAppendS(pciDir, "/zx_info/engine_3d_usage");
284        if (ffReadFileBuffer(pciDir->chars, buffer)) {
285            gpu->coreUsage = ffStrbufToDouble(buffer, FF_GPU_CORE_USAGE_UNSET);
286        }
287        ffStrbufSubstrBefore(pciDir, pciDirLen);
288
289        ffStrbufAppendS(pciDir, "/zx_info/fb_size");
290        if (ffReadFileBuffer(pciDir->chars, buffer)) {
291            gpu->shared.total = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET);
292        }
293        ffStrbufSubstrBefore(pciDir, pciDirLen);
294
295        if (gpu->shared.total != FF_GPU_VMEM_SIZE_UNSET) {
296            gpu->shared.total *= 1024 * 1024;
297
298            ffStrbufAppendS(pciDir, "/zx_info/free_fb_mem");
299            if (ffReadFileBuffer(pciDir->chars, buffer)) {
300                gpu->shared.used = ffStrbufToUInt(buffer, FF_GPU_VMEM_SIZE_UNSET);
301            }
302            ffStrbufSubstrBefore(pciDir, pciDirLen);
303
304            if (gpu->shared.used != FF_GPU_VMEM_SIZE_UNSET) {
305                gpu->shared.used *= 1024 * 1024;
306                gpu->shared.used = gpu->shared.total - gpu->shared.used;
307            }
308        }
309    }
310
311    return nullptr;
312}
313
314static inline uint16_t pcieLinkSpeedToGen(const FFstrbuf* buffer) {
315    int64_t speed = ffStrbufToSInt(buffer, FF_GPU_PCIE_SPEED_UNSET);
316    if (speed >= 64) {
317        return 6;
318    } else if (speed >= 32) {
319        return 5;
320    } else if (speed >= 16) {
321        return 4;
322    } else if (speed >= 8) {
323        return 3;
324    } else if (speed >= 5) {
325        return 2;
326    } else if (speed >= 2) { // 2.5
327        return 1;
328    }
329    return FF_GPU_PCIE_SPEED_UNSET;
330}
331
332static inline uint16_t pcieWidthToLanes(const FFstrbuf* buffer) {
333    int64_t width = ffStrbufToSInt(buffer, FF_GPU_PCIE_SPEED_UNSET);
334    if (width > 0 && width < 255) { // kernel returns 255 if the value is unknown
335        return (uint16_t) width;
336    }
337    return FF_GPU_PCIE_SPEED_UNSET;
338}
339
340static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf* buffer, FFstrbuf* deviceDir, const char* drmKey) {
341    const uint32_t drmDirPathLength = deviceDir->length;
342    uint32_t vendorId, deviceId, subVendorId, subDeviceId;
343    uint8_t classId, subclassId;
344    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) {
345        FF_DEBUG("Failed to parse pci modalias");
346        return "Failed to parse pci modalias";
347    }
348
349    if (classId != 0x03 /*PCI_BASE_CLASS_DISPLAY*/) {
350        return "Not a GPU device";
351    }
352
353    char pciPath[PATH_MAX];
354    const char* pPciPath = nullptr;
355    if (drmKey) {
356        ssize_t pathLength = readlink(deviceDir->chars, pciPath, ARRAY_SIZE(pciPath) - 1);
357        if (pathLength <= 0) {
358            FF_DEBUG("readlink(%s) failed: %s", deviceDir->chars, strerror(errno));
359            return "Unable to get PCI device path";
360        }
361        pciPath[pathLength] = '\0';
362        pPciPath = strrchr(pciPath, '/');
363        if (__builtin_expect(pPciPath != nullptr, true)) {
364            pPciPath++;
365        } else {
366            pPciPath = pciPath;
367        }
368    } else {
369        pPciPath = memrchr(deviceDir->chars, '/', deviceDir->length);
370        assert(pPciPath);
371        pPciPath++;
372    }
373
374    uint32_t pciDomain, pciBus, pciDevice, pciFunc;
375    if (sscanf(pPciPath, "%" SCNx32 ":%" SCNx32 ":%" SCNx32 ".%" SCNx32, &pciDomain, &pciBus, &pciDevice, &pciFunc) != 4) {
376        FF_DEBUG("Invalid PCI device path");
377        return "Invalid PCI device path";
378    }
379
380    if (pciFunc > 0 && subclassId == 0x80 /*PCI_CLASS_DISPLAY_OTHER*/) {
381        return "Likely an auxiliary display controller"; // #2034
382    }
383
384    FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
385    ffStrbufInitStatic(&gpu->vendor, ffGPUGetVendorString((uint16_t) vendorId));
386    ffStrbufInit(&gpu->name);
387    ffStrbufInit(&gpu->driver);
388    ffStrbufInit(&gpu->platformApi);
389    ffStrbufInit(&gpu->memoryType);
390    gpu->index = FF_GPU_INDEX_UNSET;
391    gpu->temperature = FF_GPU_TEMP_UNSET;
392    gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
393    gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
394    gpu->type = FF_GPU_TYPE_UNKNOWN;
395    gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
396    gpu->deviceId = ffGPUPciAddr2Id(pciDomain, pciBus, pciDevice, pciFunc);
397    gpu->frequency = FF_GPU_FREQUENCY_UNSET;
398    gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
399
400    char drmKeyBuffer[8];
401    if (!drmKey) {
402        ffStrbufAppendS(deviceDir, "/drm");
403        FF_AUTO_CLOSE_DIR DIR* dirp = opendir(deviceDir->chars);
404        if (dirp) {
405            struct dirent* entry;
406            while ((entry = readdir(dirp)) != nullptr) {
407                if (ffStrStartsWith(entry->d_name, "card")) {
408                    ffStrCopy(drmKeyBuffer, entry->d_name, ARRAY_SIZE(drmKeyBuffer));
409                    drmKey = drmKeyBuffer;
410                    break;
411                }
412            }
413        }
414        ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
415    }
416
417    if (drmKey) {
418        ffStrbufSetF(&gpu->platformApi, "DRM (%s)", drmKey);
419    }
420
421    detectDriverFromSysfs(&gpu->driver, deviceDir, buffer, drmKey);
422    ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
423
424    if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_AMD) {
425        bool ok = false;
426        if (drmKey && options->driverSpecific) {
427            ok = drmDetectAmdSpecific(options, gpu, drmKey, buffer) == nullptr;
428        }
429
430        if (!ok) {
431            pciDetectAmdSpecific(options, gpu, deviceDir, buffer);
432            ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
433        }
434
435        ffStrbufAppendS(deviceDir, "/revision");
436        if (ffReadFileBuffer(deviceDir->chars, buffer)) {
437            char* pend;
438            uint32_t revision = (uint32_t) strtoul(buffer->chars, &pend, 16);
439            if (pend != buffer->chars) {
440                ffGPUQueryAmdGpuName((uint16_t) deviceId, (uint8_t) revision, gpu);
441            }
442        }
443        ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
444    } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_INTEL) {
445        pciDetectIntelSpecific(options, gpu, deviceDir, buffer, drmKey);
446        ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
447        if (options->driverSpecific && drmKey) {
448            drmDetectIntelSpecific(gpu, drmKey, buffer);
449        }
450    } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA && ffStrbufEqualS(&gpu->driver, "nouveau")) {
451        pciDetectTempGeneral(options, gpu, deviceDir, buffer);
452        if (options->driverSpecific && drmKey) {
453            drmDetectNouveauSpecific(gpu, drmKey, buffer);
454        }
455    } else if (gpu->vendor.chars == FF_GPU_VENDOR_NAME_ZHAOXIN && ffStrbufStartsWithS(&gpu->driver, "zx")) {
456        pciDetectTempGeneral(options, gpu, deviceDir, buffer);
457        pciDetectZxSpecific(options, gpu, deviceDir, buffer);
458    } else {
459        ffGPUDetectDriverSpecific(options, gpu, (FFGpuDriverPciBusId){
460                                                    .domain = pciDomain,
461                                                    .bus = pciBus,
462                                                    .device = pciDevice,
463                                                    .func = pciFunc,
464                                                });
465    }
466
467    if (gpu->name.length == 0) {
468        ffGPUFillVendorAndName(subclassId, (uint16_t) vendorId, (uint16_t) deviceId, gpu);
469    }
470
471    ffGPUDetectTypeByVendorAndName(gpu);
472
473    if (options->driverSpecific && gpu->pcieSpeed == FF_GPU_PCIE_SPEED_UNSET) {
474        ffStrbufAppendS(deviceDir, "/max_link_speed");
475        if (ffReadFileBuffer(deviceDir->chars, buffer)) {
476            gpu->psMax.gen = pcieLinkSpeedToGen(buffer);
477        }
478        ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
479
480        if (gpu->psMax.gen != FF_GPU_PCIE_SPEED_UNSET) {
481            ffStrbufAppendS(deviceDir, "/max_link_width");
482            if (ffReadFileBuffer(deviceDir->chars, buffer)) {
483                gpu->psMax.lanes = pcieWidthToLanes(buffer);
484            }
485            ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
486        }
487
488        ffStrbufAppendS(deviceDir, "/current_link_speed");
489        if (ffReadFileBuffer(deviceDir->chars, buffer)) {
490            gpu->psCurr.gen = pcieLinkSpeedToGen(buffer);
491        }
492        ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
493
494        if (gpu->psCurr.gen != FF_GPU_PCIE_SPEED_UNSET) {
495            ffStrbufAppendS(deviceDir, "/current_link_width");
496            if (ffReadFileBuffer(deviceDir->chars, buffer)) {
497                gpu->psCurr.lanes = pcieWidthToLanes(buffer);
498            }
499            ffStrbufSubstrBefore(deviceDir, drmDirPathLength);
500        }
501    }
502
503    return nullptr;
504}
505
506#if __aarch64__
507    #include "detection/cpu/cpu.h"
508
509[[maybe_unused]] static const char* drmDetectAsahiSpecific(FFGPUResult* gpu, const char* name, [[maybe_unused]] FFstrbuf* buffer, [[maybe_unused]] const char* drmKey) {
510    if (sscanf(name, "agx-t%lu", &gpu->deviceId) == 1) {
511        ffStrbufSetStatic(&gpu->name, ffCPUAppleCodeToName((uint32_t) gpu->deviceId));
512    }
513    ffStrbufSetStatic(&gpu->vendor, FF_GPU_VENDOR_NAME_APPLE);
514
515    ffStrbufSetS(buffer, "/dev/dri/");
516    ffStrbufAppendS(buffer, drmKey);
517    FF_AUTO_CLOSE_FD int fd = open(buffer->chars, O_RDONLY | O_CLOEXEC);
518    if (fd >= 0) {
519        return ffDrmDetectAsahi(gpu, fd);
520    }
521
522    return nullptr;
523}
524#endif
525
526static const char* detectOf(FFlist* gpus, FFstrbuf* buffer, FFstrbuf* drmDir, const char* drmKey) {
527    char compatible[256]; // vendor,model-name
528    if (sscanf(buffer->chars + strlen("of:"), "NgpuT%*[^C]C%255[^C]", compatible) != 1) {
529        FF_DEBUG("Failed to parse of modalias or not a GPU device");
530        return "Failed to parse of modalias or not a GPU device";
531    }
532
533    char* name = strchr(compatible, ',');
534    if (name) {
535        *name = '\0';
536        ++name;
537    }
538
539    FFGPUResult* gpu = FF_LIST_ADD(FFGPUResult, *gpus);
540    gpu->index = FF_GPU_INDEX_UNSET;
541    gpu->deviceId = 0;
542    ffStrbufInit(&gpu->name);
543    ffStrbufInit(&gpu->vendor);
544    ffStrbufInit(&gpu->driver);
545    ffStrbufInit(&gpu->memoryType);
546    ffStrbufInitF(&gpu->platformApi, "DRM (%s)", drmKey);
547    gpu->temperature = FF_GPU_TEMP_UNSET;
548    gpu->coreCount = FF_GPU_CORE_COUNT_UNSET;
549    gpu->coreUsage = FF_GPU_CORE_USAGE_UNSET;
550    gpu->type = FF_GPU_TYPE_INTEGRATED; // Open Firmware is only used on integrated GPUs
551    gpu->dedicated.total = gpu->dedicated.used = gpu->shared.total = gpu->shared.used = FF_GPU_VMEM_SIZE_UNSET;
552    gpu->frequency = FF_GPU_FREQUENCY_UNSET;
553    gpu->pcieSpeed = FF_GPU_PCIE_SPEED_UNSET;
554
555    detectDriverFromSysfs(&gpu->driver, drmDir, buffer, drmKey);
556
557#ifdef __aarch64__
558    if (ffStrbufEqualS(&gpu->driver, "asahi")) {
559        drmDetectAsahiSpecific(gpu, name, buffer, drmKey);
560    }
561#endif
562
563    if (!gpu->name.length) {
564        ffStrbufSetS(&gpu->name, name ?: compatible);
565        ffStrbufTrimRightSpace(&gpu->name);
566    }
567    if (!gpu->vendor.length && name) {
568        if (ffStrEquals(compatible, "brcm")) {
569            ffStrbufSetStatic(&gpu->vendor, "Broadcom"); // Raspberry Pi
570        } else {
571            ffStrbufSetS(&gpu->vendor, compatible);
572            gpu->vendor.chars[0] = (char) toupper((uint8_t) compatible[0]);
573        }
574    }
575
576    return nullptr;
577}
578
579static const char* drmDetectGPUs(const FFGPUOptions* options, FFlist* gpus) {
580    FF_STRBUF_AUTO_DESTROY drmDir = ffStrbufCreateA(64);
581    ffStrbufAppendS(&drmDir, "/sys/class/drm/");
582    const uint32_t drmDirLength = drmDir.length;
583
584    FF_AUTO_CLOSE_DIR DIR* dir = opendir(drmDir.chars);
585    if (dir == nullptr) {
586        FF_DEBUG("opendir(%s) failed: %s", drmDir.chars, strerror(errno));
587        return "Failed to open `/sys/class/drm/`";
588    }
589
590    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
591
592    struct dirent* entry;
593    while ((entry = readdir(dir)) != nullptr) {
594        if (!ffStrStartsWith(entry->d_name, "card") ||
595            strchr(entry->d_name + 4, '-') != nullptr) {
596            continue;
597        }
598
599        ffStrbufAppendS(&drmDir, entry->d_name);
600
601        ffStrbufAppendS(&drmDir, "/device/modalias");
602        ffReadFileBuffer(drmDir.chars, &buffer);
603        ffStrbufSubstrBefore(&drmDir, drmDir.length - (uint32_t) strlen("/modalias"));
604        if (!buffer.length) {
605            continue;
606        }
607
608        if (ffStrbufStartsWithS(&buffer, "pci:")) {
609            detectPci(options, gpus, &buffer, &drmDir, entry->d_name);
610        } else if (ffStrbufStartsWithS(&buffer, "of:")) { // Open Firmware
611            detectOf(gpus, &buffer, &drmDir, entry->d_name);
612        }
613
614        ffStrbufSubstrBefore(&drmDir, drmDirLength);
615    }
616
617    return nullptr;
618}
619
620static const char* pciDetectGPUs(const FFGPUOptions* options, FFlist* gpus) {
621    // https://www.kernel.org/doc/Documentation/ABI/testing/sysfs-bus-pci
622    const char* pciDirPath = "/sys/bus/pci/devices/";
623
624    FF_AUTO_CLOSE_DIR DIR* dirp = opendir(pciDirPath);
625    if (dirp == nullptr) {
626        FF_DEBUG("opendir(%s) failed: %s", pciDirPath, strerror(errno));
627        return "Failed to open `/sys/bus/pci/devices/`";
628    }
629
630    FF_STRBUF_AUTO_DESTROY pciDir = ffStrbufCreateA(64);
631    ffStrbufAppendS(&pciDir, pciDirPath);
632
633    const uint32_t pciBaseDirLength = pciDir.length;
634
635    FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreate();
636
637    struct dirent* entry;
638    while ((entry = readdir(dirp)) != nullptr) {
639        if (entry->d_name[0] == '.') {
640            continue;
641        }
642
643        ffStrbufSubstrBefore(&pciDir, pciBaseDirLength);
644        ffStrbufAppendS(&pciDir, entry->d_name);
645        const uint32_t pciDevDirLength = pciDir.length;
646
647        ffStrbufAppendS(&pciDir, "/modalias");
648        if (!ffReadFileBuffer(pciDir.chars, &buffer)) {
649            continue;
650        }
651        ffStrbufSubstrBefore(&pciDir, pciDevDirLength);
652        assert(ffStrbufStartsWithS(&buffer, "pci:"));
653
654        detectPci(options, gpus, &buffer, &pciDir, nullptr);
655        ffStrbufSubstrBefore(&pciDir, pciBaseDirLength);
656    }
657
658    return nullptr;
659}
660
661const char* ffDetectGPUImpl(const FFGPUOptions* options, FFlist* gpus) {
662#if __x86_64__ || __aarch64__
663    const char* ffGPUDetectWsl2(const FFGPUOptions* options, FFlist* gpus);
664    if (ffGPUDetectWsl2(options, gpus) == nullptr) {
665        return nullptr;
666    }
667#endif
668
669    if (options->detectionMethod == FF_GPU_DETECTION_METHOD_AUTO) {
670        if (drmDetectGPUs(options, gpus) == nullptr && gpus->length > 0) {
671            return nullptr;
672        }
673    }
674    return pciDetectGPUs(options, gpus);
675}