master ohmu/fastfetch-openbsd / src / detection / gpu / gpu_pci.c
ohmu  ·  2026-10-15
  1#include "gpu.h"
  2#include "common/debug.h"
  3#include "common/io.h"
  4#include "common/path.h"
  5#include "common/properties.h"
  6#include "common/memrchr.h"
  7#include "common/strutil.h"
  8
  9#if FF_HAVE_EMBEDDED_PCIIDS
 10    #include "fastfetch_pciids.c.inc"
 11#endif
 12#if FF_HAVE_EMBEDDED_AMDGPUIDS
 13    #include "fastfetch_amdgpuids.c.inc"
 14#endif
 15
 16static const FFstrbuf* loadPciIds() {
 17    static FFstrbuf pciids;
 18
 19    if (pciids.chars) {
 20        return &pciids;
 21    }
 22    ffStrbufInit(&pciids);
 23
 24#ifdef FF_CUSTOM_PCI_IDS_PATH
 25
 26    FF_DEBUG("Loading PCI IDs from custom path: %s", FF_STR(FF_CUSTOM_PCI_IDS_PATH));
 27    ffReadFileBuffer(FF_STR(FF_CUSTOM_PCI_IDS_PATH), &pciids);
 28
 29#else // FF_CUSTOM_PCI_IDS_PATH
 30
 31    #if __linux__
 32    FF_DEBUG("Loading PCI IDs from %s", FASTFETCH_TARGET_DIR_USR "/share/hwdata/pci.ids");
 33    ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/share/hwdata/pci.ids", &pciids);
 34    if (pciids.length == 0) {
 35        FF_DEBUG("PCI IDs not found, trying %s", FASTFETCH_TARGET_DIR_USR "/share/misc/pci.ids");
 36        ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/share/misc/pci.ids", &pciids); // debian?
 37        if (pciids.length == 0) {
 38            FF_DEBUG("PCI IDs not found, trying %s", FASTFETCH_TARGET_DIR_USR "/local/share/hwdata/pci.ids");
 39            ffReadFileBuffer(FASTFETCH_TARGET_DIR_USR "/local/share/hwdata/pci.ids", &pciids);
 40        }
 41    }
 42    #elif __OpenBSD__ || __FreeBSD__ || __NetBSD__
 43    FF_DEBUG("Loading PCI IDs from %s", FF_PATH_PKG_BASE "/share/hwdata/pci.ids");
 44    ffReadFileBuffer(FF_PATH_PKG_BASE "/share/hwdata/pci.ids", &pciids);
 45    if (pciids.length == 0) {
 46        FF_DEBUG("PCI IDs not found, trying %s", FF_PATH_PKG_BASE "/share/pciids/pci.ids");
 47        ffReadFileBuffer(FF_PATH_PKG_BASE "/share/pciids/pci.ids", &pciids);
 48    }
 49    #elif __sun
 50    FF_DEBUG("Loading PCI IDs from %s", FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids");
 51    ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/usr/share/hwdata/pci.ids", &pciids);
 52    #elif __HAIKU__
 53    FF_DEBUG("Loading PCI IDs from %s", FASTFETCH_TARGET_DIR_ROOT "/system/data/hwdata/pci.ids");
 54    ffReadFileBuffer(FASTFETCH_TARGET_DIR_ROOT "/system/data/hwdata/pci.ids", &pciids);
 55    #endif
 56
 57#endif // FF_CUSTOM_PCI_IDS_PATH
 58
 59    FF_DEBUG("PCI IDs data loaded: %u bytes", pciids.length);
 60    return &pciids;
 61}
 62
 63void ffGPUParsePciIdsFile(const FFstrbuf* content, uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
 64    FF_DEBUG("Searching pci.ids for vendor=0x%04x device=0x%04x subclass=0x%02x", vendor, device, subclass);
 65    if (content->length) {
 66        const char* const fileEnd = content->chars + content->length;
 67        char buffer[32];
 68
 69        // Search for vendor
 70        uint32_t len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), "\n%04x  ", vendor);
 71        const char* start = memmem(content->chars, content->length, buffer, len);
 72        if (start) {
 73            FF_DEBUG("Found PCI vendor entry for 0x%04x", vendor);
 74            start += len;
 75            const char* vendorEnd = memchr(start, '\n', (size_t) (fileEnd - start));
 76            if (!vendorEnd) {
 77                vendorEnd = fileEnd;
 78            }
 79            if (!gpu->vendor.length) {
 80                ffStrbufSetNS(&gpu->vendor, (uint32_t) (vendorEnd - start), start);
 81            }
 82
 83            // Search for device. Device lines are `\tXXXX  name`; their ids only
 84            // grow inside a vendor block, so one linear walk over the block is
 85            // enough: compare in place and stop either at the line starting the
 86            // next vendor, or -- because the ids are sorted -- right after the
 87            // first id greater than the one we are looking for. Doing the compare
 88            // here also removes the need to know where the block ends, so no
 89            // length is ever derived from a pointer that may have run past it.
 90            // pci.ids only asks contributors to keep the entries sorted, and it
 91            // is strictly lowercase hex, so the byte comparison below matches the
 92            // numeric order. A block that is not sorted (only reachable via
 93            // FF_CUSTOM_PCI_IDS_PATH) makes us give up early and fall back to the
 94            // synthesized name below.
 95            char deviceId[8];
 96            snprintf(deviceId, ARRAY_SIZE(deviceId), "%04x", device);
 97            [[maybe_unused]] bool found = false;
 98            for (const char* p = vendorEnd < fileEnd ? vendorEnd + 1 : fileEnd;
 99                 p < fileEnd && (*p == '\t' || *p == '#');) {
100                if (*p == '\t' && fileEnd - p >= 7) {
101                    int order = memcmp(p + 1, deviceId, 4);
102                    if (order > 0) {
103                        break;
104                    }
105                    if (order == 0 && p[5] == ' ' && p[6] == ' ') {
106                        FF_DEBUG("Found PCI device entry for 0x%04x:0x%04x", vendor, device);
107                        start = p + 7;
108                        const char* end = memchr(start, '\n', (size_t) (fileEnd - start));
109                        if (!end) {
110                            end = fileEnd;
111                        }
112
113                        const char* closingBracket = end - 1;
114                        if (*closingBracket == ']') {
115                            const char* openingBracket = memrchr(start, '[', (size_t) (closingBracket - start));
116                            if (openingBracket) {
117                                openingBracket++;
118                                ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);
119                            }
120                        }
121                        if (!gpu->name.length) {
122                            ffStrbufSetNS(&gpu->name, (uint32_t) (end - start), start);
123                        }
124                        found = true;
125                        break;
126                    }
127                }
128                p = memchr(p, '\n', (size_t) (fileEnd - p));
129                if (!p) {
130                    break;
131                }
132                p++;
133            }
134            if (!found) {
135                FF_DEBUG("PCI device entry 0x%04x:0x%04x was not found in pci.ids", vendor, device);
136            }
137        } else {
138            FF_DEBUG("PCI vendor entry 0x%04x was not found in pci.ids", vendor);
139        }
140    } else {
141        FF_DEBUG("PCI IDs data is empty; using a synthesized device name");
142    }
143
144    if (!gpu->name.length) {
145        const char* subclassStr;
146        switch (subclass) {
147            case 0 /*PCI_CLASS_DISPLAY_VGA*/:
148                subclassStr = " (VGA compatible)";
149                break;
150            case 1 /*PCI_CLASS_DISPLAY_XGA*/:
151                subclassStr = " (XGA compatible)";
152                break;
153            case 2 /*PCI_CLASS_DISPLAY_3D*/:
154                subclassStr = " (3D)";
155                break;
156            default:
157                subclassStr = "";
158                break;
159        }
160
161        ffStrbufSetF(&gpu->name, "%s Device %04X%s", gpu->vendor.length ? gpu->vendor.chars : "Unknown", device, subclassStr);
162        FF_DEBUG("PCI device name not found; synthesized name: %s", gpu->name.chars);
163    } else {
164        FF_DEBUG("Resolved PCI device name: vendor='%s', name='%s'", gpu->vendor.chars, gpu->name.chars);
165    }
166}
167
168#if FF_HAVE_EMBEDDED_PCIIDS
169static inline int pciDeviceCmp(const uint16_t* key, const FFPciDevice* element) {
170    return (int) *key - (int) element->id;
171}
172
173static bool loadPciidsInc(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
174    FF_DEBUG("Searching embedded PCI IDs for vendor=0x%04x device=0x%04x", vendor, device);
175    for (const FFPciVendor* pvendor = ffPciVendors; pvendor->name; pvendor++) {
176        if (pvendor->id != vendor) {
177            continue;
178        }
179
180        if (!gpu->vendor.length) {
181            ffStrbufSetS(&gpu->vendor, pvendor->name);
182        }
183
184        const FFPciDevice* pdevice = (const FFPciDevice*) bsearch(&device, pvendor->devices, pvendor->nDevices, sizeof(*pdevice), (void*) pciDeviceCmp);
185
186        if (pdevice) {
187            uint32_t nameLen = (uint32_t) strlen(pdevice->name);
188            if (nameLen == 0) {
189                FF_DEBUG("Embedded PCI device entry for 0x%04x:0x%04x has an empty name", vendor, device);
190                return false;
191            }
192            const char* closingBracket = pdevice->name + nameLen - 1;
193            if (*closingBracket == ']') {
194                const char* openingBracket = memrchr(pdevice->name, '[', nameLen - 1);
195                if (openingBracket) {
196                    openingBracket++;
197                    ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);
198                }
199            }
200            if (!gpu->name.length) {
201                ffStrbufSetNS(&gpu->name, nameLen, pdevice->name);
202            }
203            FF_DEBUG("Embedded PCI ID matched: vendor='%s', name='%s'", gpu->vendor.chars, gpu->name.chars);
204            return true;
205        }
206
207        FF_DEBUG("Embedded PCI vendor matched ('%s'), but device 0x%04x was not found", pvendor->name, device);
208        if (!gpu->name.length) {
209            const char* subclassStr;
210            switch (subclass) {
211                case 0 /*PCI_CLASS_DISPLAY_VGA*/:
212                    subclassStr = " (VGA compatible)";
213                    break;
214                case 1 /*PCI_CLASS_DISPLAY_XGA*/:
215                    subclassStr = " (XGA compatible)";
216                    break;
217                case 2 /*PCI_CLASS_DISPLAY_3D*/:
218                    subclassStr = " (3D)";
219                    break;
220                default:
221                    subclassStr = "";
222                    break;
223            }
224
225            ffStrbufSetF(&gpu->name, "%s Device %04X%s", gpu->vendor.length ? gpu->vendor.chars : "Unknown", device, subclassStr);
226        }
227        FF_DEBUG("Using synthesized PCI device name: %s", gpu->name.chars);
228        return true;
229    }
230    FF_DEBUG("Embedded PCI vendor 0x%04x was not found", vendor);
231    return false;
232}
233#endif
234
235void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
236    FF_DEBUG("Resolving PCI GPU name: vendor=0x%04x device=0x%04x subclass=0x%02x", vendor, device, subclass);
237    if (vendor == 0x1234 && device == 0x1111 && subclass == 0) { // Not exist in pci.ids
238        ffStrbufSetStatic(&gpu->name, "Virtual Video Controller");
239        FF_DEBUG("Matched special virtual video controller ID");
240        return;
241    }
242
243#if FF_HAVE_EMBEDDED_PCIIDS
244    bool ok = loadPciidsInc(subclass, vendor, device, gpu);
245    if (ok) {
246        FF_DEBUG("Resolved PCI GPU name from embedded IDs: vendor='%s', name='%s'", gpu->vendor.chars, gpu->name.chars);
247        return;
248    }
249#endif
250    ffGPUParsePciIdsFile(loadPciIds(), subclass, vendor, device, gpu);
251    FF_DEBUG("Resolved PCI GPU name from pci.ids: vendor='%s', name='%s'", gpu->vendor.chars, gpu->name.chars);
252}
253
254#if FF_HAVE_EMBEDDED_AMDGPUIDS
255static inline int amdGpuCmp(const uint32_t* key, const FFArmGpuProduct* element) {
256    // Maximum value of *key is 0x00FFFFFF. `(int) *key` should never overflow
257    return (int) *key - (int) element->id;
258}
259
260static bool loadAmdGpuIdsInc(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu) {
261    uint32_t key = (deviceId << 8u) | revision;
262    FFArmGpuProduct* product = bsearch(&key, ffAmdGpuProducts, ARRAY_SIZE(ffAmdGpuProducts), sizeof(*ffAmdGpuProducts), (void*) amdGpuCmp);
263    if (product) {
264        ffStrbufSetS(&gpu->name, product->name);
265        FF_DEBUG("Embedded AMD GPU ID matched: device=0x%04x revision=0x%02x name='%s'", deviceId, revision, gpu->name.chars);
266        return true;
267    }
268    FF_DEBUG("Embedded AMD GPU ID not found: device=0x%04x revision=0x%02x", deviceId, revision);
269    return false;
270}
271#endif
272
273static void parseAmdGpuIdsFile(uint16_t deviceId, uint8_t revision, FFGPUResult* gpu) {
274    char query[32];
275    snprintf(query, ARRAY_SIZE(query), "%X,\t%X,", (unsigned) deviceId, (unsigned) revision);
276#ifdef FF_CUSTOM_AMDGPU_IDS_PATH
277    FF_DEBUG("Searching AMD GPU IDs at custom path '%s' for device=0x%04x revision=0x%02x", FF_STR(FF_CUSTOM_AMDGPU_IDS_PATH), deviceId, revision);
278    ffParsePropFile(FF_STR(FF_CUSTOM_AMDGPU_IDS_PATH), query, &gpu->name);
279#else
280    FF_DEBUG("Searching libdrm/amdgpu.ids for device=0x%04x revision=0x%02x", deviceId, revision);
281    ffParsePropFileData("libdrm/amdgpu.ids", query, &gpu->name);
282#endif
283    FF_DEBUG("AMD GPU IDs file lookup %s: device=0x%04x revision=0x%02x name='%s'", gpu->name.length ? "matched" : "not matched", deviceId, revision, gpu->name.chars);
284}
285
286void ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu) {
287    FF_DEBUG("Resolving AMD GPU name: device=0x%04x revision=0x%02x", deviceId, revisionId);
288#if FF_HAVE_EMBEDDED_AMDGPUIDS
289    bool ok = loadAmdGpuIdsInc(deviceId, revisionId, gpu);
290    if (ok) {
291        return;
292    }
293#endif
294    parseAmdGpuIdsFile(deviceId, revisionId, gpu);
295}