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}