diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ec3cee64..24e7f8407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Unreleased +Changes: +* Improved PCI GPU name resolution by checking udev's hardware database and the NVIDIA driver's procfs before falling back to pci.ids. (GPU, Linux) + Bugfixes: * Fixed libc version detection on FreeBSD without binutils installed, and on DragonFly BSD. (Libc, FreeBSD / DragonFly) diff --git a/src/detection/gpu/gpu_linux.c b/src/detection/gpu/gpu_linux.c index a594f7455..007e0633a 100644 --- a/src/detection/gpu/gpu_linux.c +++ b/src/detection/gpu/gpu_linux.c @@ -1,12 +1,10 @@ #include "detection/gpu/gpu.h" #include "common/debug.h" #include "common/io.h" -#include "common/FFstrbuf.h" +#include "common/properties.h" #include "common/strutil.h" -#include "modules/gpu/option.h" #include -#include static bool detectDriverFromSysfs(FFstrbuf* result, FFstrbuf* pciDir, FFstrbuf* buffer, [[maybe_unused]] const char* drmKey) { uint32_t pciDirLength = pciDir->length; @@ -337,6 +335,64 @@ static inline uint16_t pcieWidthToLanes(const FFstrbuf* buffer) { return FF_GPU_PCIE_SPEED_UNSET; } +static bool parseModelName(const char* data, const char* key, bool stripVendorPrefix, FFstrbuf* name) { + ffStrbufClear(name); + if (!ffParsePropLines(data, key, name) || !name->length) { + return false; + } + + ffStrbufDecodeHexEscapeSequences(name); // udev hex-escapes characters that are not allowed in property values + ffStrbufTrimSpace(name); + + // hwdb names have the form `Codename [Marketing Name]`. Reduce them exactly like + // ffGPUParsePciIdsFile() does, so displaying the name does not change with the source. + if (name->length > 1 && name->chars[name->length - 1] == ']') { + const char* openingBracket = memrchr(name->chars, '[', name->length - 1); + if (openingBracket) { + ffStrbufSubstrAfter(name, (uint32_t) (openingBracket - name->chars)); + ffStrbufSubstrBefore(name, name->length - 1); + } + } + + // The driver repeats the vendor in its own model line. Dropping it keeps the pci.ids + // shape and lets ffGPUDetectTypeByVendorAndName() see the product family prefix. + if (stripVendorPrefix && ffStrbufStartsWithIgnCaseS(name, "NVIDIA ")) { + ffStrbufSubstrAfter(name, (uint32_t) strlen("NVIDIA ") - 1); // excludes the index itself + } + + ffStrbufTrimSpace(name); + return name->length > 0; +} + +static bool nameFromFile(const FFstrbuf* path, const char* key, bool stripVendorPrefix, FFstrbuf* buffer, FFstrbuf* name) { + if (!ffReadFileBuffer(path->chars, buffer) || !parseModelName(buffer->chars, key, stripVendorPrefix, name)) { + return false; + } + FF_DEBUG("GPU name resolved from %s: %s", path->chars, name->chars); + return true; +} + +// The driver and udev already resolved the model name at probe / boot time. Only `Model:` is +// consumed: the sibling `power` file goes through the RM and can wake a GPU suspended in runtime D3. +static bool resolveNameFromFiles(const char* pciPath, bool isNvidia, FFstrbuf* buffer, FFstrbuf* name) { + FF_STRBUF_AUTO_DESTROY path = ffStrbufCreateA(128); + + if (isNvidia) { + ffStrbufSetF(&path, "/proc/driver/nvidia/gpus/%s/information", pciPath); + if (nameFromFile(&path, "Model:", true, buffer, name)) { + return true; + } + } + + ffStrbufSetF(&path, "/run/udev/data/+pci:%s", pciPath); + if (nameFromFile(&path, "E:ID_MODEL_FROM_DATABASE=", false, buffer, name)) { + return true; + } + + ffStrbufClear(name); + return false; +} + 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; @@ -465,7 +521,12 @@ static const char* detectPci(const FFGPUOptions* options, FFlist* gpus, FFstrbuf } if (gpu->name.length == 0) { - ffGPUFillVendorAndName(subclassId, (uint16_t) vendorId, (uint16_t) deviceId, gpu); + // Stop at the first hit; unknown vendors are left to pci.ids, which also fills in `vendor`. + bool resolved = gpu->vendor.length > 0 && + resolveNameFromFiles(pPciPath, gpu->vendor.chars == FF_GPU_VENDOR_NAME_NVIDIA, buffer, &gpu->name); + if (!resolved) { + ffGPUFillVendorAndName(subclassId, (uint16_t) vendorId, (uint16_t) deviceId, gpu); + } } ffGPUDetectTypeByVendorAndName(gpu);