mirror of
https://github.com/fastfetch-cli/fastfetch.git
synced 2026-10-28 02:31:25 +01:00
GPU (Pci): hardens the parser; adds tests
This commit is contained in:
@@ -2473,6 +2473,13 @@ if (BUILD_TESTS)
|
||||
PRIVATE libfastfetch
|
||||
)
|
||||
|
||||
add_executable(fastfetch-test-gpu-pci
|
||||
tests/gpu_pci.c
|
||||
)
|
||||
target_link_libraries(fastfetch-test-gpu-pci
|
||||
PRIVATE libfastfetch
|
||||
)
|
||||
|
||||
enable_testing()
|
||||
add_test(NAME test-cache COMMAND fastfetch-test-cache)
|
||||
add_test(NAME test-logo COMMAND fastfetch-test-logo)
|
||||
@@ -2492,6 +2499,7 @@ if (BUILD_TESTS)
|
||||
add_test(NAME test-temps COMMAND fastfetch-test-temps)
|
||||
add_test(NAME test-endian COMMAND fastfetch-test-endian)
|
||||
add_test(NAME test-endian-bigendian COMMAND fastfetch-test-endian-bigendian)
|
||||
add_test(NAME test-gpu-pci COMMAND fastfetch-test-gpu-pci)
|
||||
endif()
|
||||
|
||||
##################
|
||||
|
||||
@@ -80,6 +80,8 @@ typedef struct FFGpuDriverPciBusId {
|
||||
} FFGpuDriverPciBusId;
|
||||
|
||||
#if defined(__linux__) || defined(__FreeBSD__) || defined(__sun) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__HAIKU__) || defined(__GNU__)
|
||||
#define FF_GPU_HAS_PCI_IDS 1
|
||||
void ffGPUParsePciIdsFile(const FFstrbuf* content, uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu);
|
||||
void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu);
|
||||
void ffGPUQueryAmdGpuName(uint16_t deviceId, uint8_t revisionId, FFGPUResult* gpu);
|
||||
|
||||
|
||||
+56
-40
@@ -60,62 +60,78 @@ static const FFstrbuf* loadPciIds() {
|
||||
return &pciids;
|
||||
}
|
||||
|
||||
static void parsePciIdsFile(const FFstrbuf* content, uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
|
||||
void ffGPUParsePciIdsFile(const FFstrbuf* content, uint8_t subclass, uint16_t vendor, uint16_t device, FFGPUResult* gpu) {
|
||||
FF_DEBUG("Searching pci.ids for vendor=0x%04x device=0x%04x subclass=0x%02x", vendor, device, subclass);
|
||||
if (content->length) {
|
||||
const char* const fileEnd = content->chars + content->length;
|
||||
char buffer[32];
|
||||
|
||||
// Search for vendor
|
||||
uint32_t len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), "\n%04x ", vendor);
|
||||
char* start = (char*) memmem(content->chars, content->length, buffer, len);
|
||||
char* end = content->chars + content->length;
|
||||
const char* start = memmem(content->chars, content->length, buffer, len);
|
||||
if (start) {
|
||||
FF_DEBUG("Found PCI vendor entry for 0x%04x", vendor);
|
||||
start += len;
|
||||
end = memchr(start, '\n', (uint32_t) (end - start));
|
||||
if (!end) {
|
||||
end = content->chars + content->length;
|
||||
const char* vendorEnd = memchr(start, '\n', (size_t) (fileEnd - start));
|
||||
if (!vendorEnd) {
|
||||
vendorEnd = fileEnd;
|
||||
}
|
||||
if (!gpu->vendor.length) {
|
||||
ffStrbufSetNS(&gpu->vendor, (uint32_t) (end - start), start);
|
||||
ffStrbufSetNS(&gpu->vendor, (uint32_t) (vendorEnd - start), start);
|
||||
}
|
||||
|
||||
start = end; // point to '\n' of vendor
|
||||
end = start + 1; // point to start of devices
|
||||
// find the start of next vendor
|
||||
while (end[0] == '\t' || end[0] == '#') {
|
||||
end = strchr(end, '\n');
|
||||
if (!end) {
|
||||
end = content->chars + content->length;
|
||||
break;
|
||||
} else {
|
||||
end++;
|
||||
}
|
||||
}
|
||||
// Search for device. Device lines are `\tXXXX name`; their ids only
|
||||
// grow inside a vendor block, so one linear walk over the block is
|
||||
// enough: compare in place and stop either at the line starting the
|
||||
// next vendor, or -- because the ids are sorted -- right after the
|
||||
// first id greater than the one we are looking for. Doing the compare
|
||||
// here also removes the need to know where the block ends, so no
|
||||
// length is ever derived from a pointer that may have run past it.
|
||||
// pci.ids only asks contributors to keep the entries sorted, and it
|
||||
// is strictly lowercase hex, so the byte comparison below matches the
|
||||
// numeric order. A block that is not sorted (only reachable via
|
||||
// FF_CUSTOM_PCI_IDS_PATH) makes us give up early and fall back to the
|
||||
// synthesized name below.
|
||||
char deviceId[8];
|
||||
snprintf(deviceId, ARRAY_SIZE(deviceId), "%04x", device);
|
||||
[[maybe_unused]] bool found = false;
|
||||
for (const char* p = vendorEnd < fileEnd ? vendorEnd + 1 : fileEnd;
|
||||
p < fileEnd && (*p == '\t' || *p == '#');) {
|
||||
if (*p == '\t' && fileEnd - p >= 7) {
|
||||
int order = memcmp(p + 1, deviceId, 4);
|
||||
if (order > 0) {
|
||||
break;
|
||||
}
|
||||
if (order == 0 && p[5] == ' ' && p[6] == ' ') {
|
||||
FF_DEBUG("Found PCI device entry for 0x%04x:0x%04x", vendor, device);
|
||||
start = p + 7;
|
||||
const char* end = memchr(start, '\n', (size_t) (fileEnd - start));
|
||||
if (!end) {
|
||||
end = fileEnd;
|
||||
}
|
||||
|
||||
// Search for device
|
||||
len = (uint32_t) snprintf(buffer, ARRAY_SIZE(buffer), "\n\t%04x ", device);
|
||||
start = memmem(start, (size_t) (end - start), buffer, len);
|
||||
if (start) {
|
||||
FF_DEBUG("Found PCI device entry for 0x%04x:0x%04x", vendor, device);
|
||||
start += len;
|
||||
end = memchr(start, '\n', (uint32_t) (end - start));
|
||||
if (!end) {
|
||||
end = content->chars + content->length;
|
||||
}
|
||||
|
||||
char* closingBracket = end - 1;
|
||||
if (*closingBracket == ']') {
|
||||
char* openingBracket = memrchr(start, '[', (size_t) (closingBracket - start));
|
||||
if (openingBracket) {
|
||||
openingBracket++;
|
||||
ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);
|
||||
const char* closingBracket = end - 1;
|
||||
if (*closingBracket == ']') {
|
||||
const char* openingBracket = memrchr(start, '[', (size_t) (closingBracket - start));
|
||||
if (openingBracket) {
|
||||
openingBracket++;
|
||||
ffStrbufSetNS(&gpu->name, (uint32_t) (closingBracket - openingBracket), openingBracket);
|
||||
}
|
||||
}
|
||||
if (!gpu->name.length) {
|
||||
ffStrbufSetNS(&gpu->name, (uint32_t) (end - start), start);
|
||||
}
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!gpu->name.length) {
|
||||
ffStrbufSetNS(&gpu->name, (uint32_t) (end - start), start);
|
||||
p = memchr(p, '\n', (size_t) (fileEnd - p));
|
||||
if (!p) {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
p++;
|
||||
}
|
||||
if (!found) {
|
||||
FF_DEBUG("PCI device entry 0x%04x:0x%04x was not found in pci.ids", vendor, device);
|
||||
}
|
||||
} else {
|
||||
@@ -231,7 +247,7 @@ void ffGPUFillVendorAndName(uint8_t subclass, uint16_t vendor, uint16_t device,
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
parsePciIdsFile(loadPciIds(), subclass, vendor, device, gpu);
|
||||
ffGPUParsePciIdsFile(loadPciIds(), subclass, vendor, device, gpu);
|
||||
FF_DEBUG("Resolved PCI GPU name from pci.ids: vendor='%s', name='%s'", gpu->vendor.chars, gpu->name.chars);
|
||||
}
|
||||
|
||||
|
||||
+201
@@ -0,0 +1,201 @@
|
||||
// Tests for ffGPUParsePciIdsFile(), the pci.ids lookup that resolves a PCI GPU name on Linux and
|
||||
// the BSDs.
|
||||
//
|
||||
// The parser takes the file content as a buffer instead of opening the file itself, which is
|
||||
// exactly what makes it testable: a small hand written pci.ids excerpt is enough, and the
|
||||
// expectations stay readable.
|
||||
//
|
||||
// The function is only compiled where libfastfetch ships the pci.ids based detection, so on other
|
||||
// platforms this test has nothing to link against and only reports success.
|
||||
|
||||
#include "common/textModifier.h"
|
||||
#include "detection/gpu/gpu.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#if FF_GPU_HAS_PCI_IDS
|
||||
|
||||
static void verifyPciEx(
|
||||
const char* content, uint8_t subclass, uint16_t vendor, uint16_t device,
|
||||
const char* presetVendor, const char* presetName,
|
||||
const char* expectedVendor, const char* expectedName,
|
||||
int lineNo
|
||||
) {
|
||||
FFGPUResult gpu = {};
|
||||
ffStrbufInit(&gpu.vendor);
|
||||
ffStrbufInit(&gpu.name);
|
||||
|
||||
// A caller may already know the vendor (for example from the PCI subsystem), in which case the
|
||||
// parser must not clobber it.
|
||||
if (presetVendor) {
|
||||
ffStrbufSetS(&gpu.vendor, presetVendor);
|
||||
}
|
||||
if (presetName) {
|
||||
ffStrbufSetS(&gpu.name, presetName);
|
||||
}
|
||||
|
||||
FF_STRBUF_AUTO_DESTROY buffer = ffStrbufCreateS(content);
|
||||
ffGPUParsePciIdsFile(&buffer, subclass, vendor, device, &gpu);
|
||||
|
||||
if (!ffStrbufEqualS(&gpu.vendor, expectedVendor) || !ffStrbufEqualS(&gpu.name, expectedName)) {
|
||||
fprintf(stderr, FASTFETCH_TEXT_MODIFIER_ERROR "[%d] ffGPUParsePciIdsFile(vendor=0x%04x, device=0x%04x, subclass=%u): expected vendor=\"%s\" name=\"%s\", got vendor=\"%s\" name=\"%s\"\n" FASTFETCH_TEXT_MODIFIER_RESET,
|
||||
lineNo, vendor, device, (unsigned) subclass, expectedVendor, expectedName, gpu.vendor.chars, gpu.name.chars);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
ffStrbufDestroy(&gpu.vendor);
|
||||
ffStrbufDestroy(&gpu.name);
|
||||
}
|
||||
|
||||
#define VERIFY_PCI(content, subclass, vendor, device, expectedVendor, expectedName) \
|
||||
verifyPciEx((content), (subclass), (vendor), (device), nullptr, nullptr, (expectedVendor), (expectedName), __LINE__)
|
||||
|
||||
#define VERIFY_PCI_EX(content, subclass, vendor, device, presetVendor, presetName, expectedVendor, expectedName) \
|
||||
verifyPciEx((content), (subclass), (vendor), (device), (presetVendor), (presetName), (expectedVendor), (expectedName), __LINE__)
|
||||
|
||||
// A miniature pci.ids. Vendors are ordered, device ids grow within a block, and the block is closed
|
||||
// by a blank line -- the same shape the real file has.
|
||||
static const char PCI_IDS[] =
|
||||
"# pci.ids excerpt used by the tests\n"
|
||||
"#\n"
|
||||
"1a2b Vendor Alpha\n"
|
||||
"\t0001 Alpha One\n"
|
||||
"\t0005 Alpha Five\n"
|
||||
"\t\t0005 0abc Subsystem that must be ignored\n"
|
||||
"\t00ff Alpha FF [Alias]\n"
|
||||
"\t1000 Alpha Thousand\n"
|
||||
"\n"
|
||||
"1a2c Vendor Beta\n"
|
||||
"\t0001 Beta One\n"
|
||||
"\t0002 Beta Two\n"
|
||||
"\t1234 Beta TwelveThirtyFour\n"
|
||||
"\n"
|
||||
"abcd Vendor Delta\n"
|
||||
"\t0e1f Delta Device\n"
|
||||
"\n"
|
||||
"ffff Vendor NoDevices\n"
|
||||
"\n"
|
||||
"C 00 Unclassified device\n";
|
||||
|
||||
// The last line has no terminating newline, so the parser has to fall back to the end of the
|
||||
// buffer.
|
||||
static const char PCI_IDS_NO_TRAILING_NL[] =
|
||||
"#\n"
|
||||
"1a2b Vendor Alpha\n"
|
||||
"\t0001 Alpha One";
|
||||
|
||||
// A vendor with no device lines at all, again without a terminating newline.
|
||||
static const char PCI_IDS_VENDOR_ONLY[] =
|
||||
"#\n"
|
||||
"1a2b Vendor Alpha";
|
||||
|
||||
int main(void) {
|
||||
// An empty buffer is not an error: the name is synthesized from the vendor and device id.
|
||||
{
|
||||
VERIFY_PCI("", 0, 0x1a2b, 0x0001, "", "Unknown Device 0001 (VGA compatible)");
|
||||
VERIFY_PCI("", 2, 0x1a2b, 0x0001, "", "Unknown Device 0001 (3D)");
|
||||
}
|
||||
|
||||
// The happy path: vendor and device are both present.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 2, 0x1a2b, 0x0001, "Vendor Alpha", "Alpha One");
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0005, "Vendor Alpha", "Alpha Five");
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x1000, "Vendor Alpha", "Alpha Thousand");
|
||||
}
|
||||
|
||||
// Hex letters in the ids work on both sides.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0xabcd, 0x0e1f, "Vendor Delta", "Delta Device");
|
||||
}
|
||||
|
||||
// A name of the form `... [Alias]` is reduced to the alias. Only the trailing bracket pair is
|
||||
// considered, and a `[` without a matching `]` at the end is kept verbatim.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x00ff, "Vendor Alpha", "Alias");
|
||||
}
|
||||
|
||||
// The lookup must stay inside the vendor block. `Beta` also has a 0x1234, but a lookup under
|
||||
// `Alpha` must not find it -- `Alpha` has no 0x1234 of its own.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2c, 0x1234, "Vendor Beta", "Beta TwelveThirtyFour");
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x1234, "Vendor Alpha", "Vendor Alpha Device 1234 (VGA compatible)");
|
||||
}
|
||||
|
||||
// An unknown device under a known vendor synthesizes a name from the vendor.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (VGA compatible)");
|
||||
VERIFY_PCI(PCI_IDS, 1, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (XGA compatible)");
|
||||
VERIFY_PCI(PCI_IDS, 2, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (3D)");
|
||||
VERIFY_PCI(PCI_IDS, 5, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002");
|
||||
}
|
||||
|
||||
// The synthesized name uses the upper case form of the device id, while the lookup needle is
|
||||
// lower case.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0xabcd, 0xbeef, "Vendor Delta", "Vendor Delta Device BEEF (VGA compatible)");
|
||||
}
|
||||
|
||||
// A device id below the first entry, between two entries and above the last one all miss. The
|
||||
// first case is also what the "stop once the ids grow past the target" shortcut relies on.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0000, "Vendor Alpha", "Vendor Alpha Device 0000 (VGA compatible)");
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0020, "Vendor Alpha", "Vendor Alpha Device 0020 (VGA compatible)");
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x9999, "Vendor Alpha", "Vendor Alpha Device 9999 (VGA compatible)");
|
||||
}
|
||||
|
||||
// A subsystem id lives at twice the indentation and must never be mistaken for a device id,
|
||||
// even when it is the value being searched for.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1a2b, 0x0abc, "Vendor Alpha", "Vendor Alpha Device 0ABC (VGA compatible)");
|
||||
}
|
||||
|
||||
// A vendor with no device lines falls back to the synthesized name, and the vendor is still
|
||||
// reported.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0xffff, 0x0001, "Vendor NoDevices", "Vendor NoDevices Device 0001 (VGA compatible)");
|
||||
}
|
||||
|
||||
// An unknown vendor leaves the vendor buffer empty, so the synthesized name says "Unknown".
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x1234, 0x5678, "", "Unknown Device 5678 (VGA compatible)");
|
||||
}
|
||||
|
||||
// Lines beyond the vendor section (the class list) are not reachable from a vendor block.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS, 0, 0x000c, 0x0000, "", "Unknown Device 0000 (VGA compatible)");
|
||||
}
|
||||
|
||||
// A device line as the very last line of the buffer, with no trailing newline.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS_NO_TRAILING_NL, 0, 0x1a2b, 0x0001, "Vendor Alpha", "Alpha One");
|
||||
VERIFY_PCI(PCI_IDS_NO_TRAILING_NL, 0, 0x1a2b, 0x0002, "Vendor Alpha", "Vendor Alpha Device 0002 (VGA compatible)");
|
||||
}
|
||||
|
||||
// A vendor line as the very last line of the buffer, with no device lines after it.
|
||||
{
|
||||
VERIFY_PCI(PCI_IDS_VENDOR_ONLY, 0, 0x1a2b, 0x0001, "Vendor Alpha", "Vendor Alpha Device 0001 (VGA compatible)");
|
||||
}
|
||||
|
||||
// A known vendor is not overwritten, but resolving the device name still happens.
|
||||
{
|
||||
VERIFY_PCI_EX(PCI_IDS, 0, 0x1a2b, 0x0001, "Custom Vendor", nullptr, "Custom Vendor", "Alpha One");
|
||||
}
|
||||
|
||||
// Likewise a name the caller already resolved is kept when the entry is a plain one.
|
||||
{
|
||||
VERIFY_PCI_EX(PCI_IDS, 0, 0x1a2b, 0x0001, nullptr, "Custom Name", "Vendor Alpha", "Custom Name");
|
||||
}
|
||||
|
||||
puts("\033[32mAll tests passed!" FASTFETCH_TEXT_MODIFIER_RESET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else // FF_TEST_HAS_PCI_IDS
|
||||
|
||||
int main(void) {
|
||||
puts("\033[32mTests skipped. (pci.ids parser not available on this platform)" FASTFETCH_TEXT_MODIFIER_RESET);
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user