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}