master ohmu/fastfetch-openbsd / src / detection / cpu / cpu_windows.c
ohmu  ·  2026-10-15
  1#include "cpu.h"
  2#include "common/debug.h"
  3#include "common/endian.h"
  4#include "common/windows/registry.h"
  5#include "common/windows/nt.h"
  6#include "common/mallocHelper.h"
  7#include "common/smbios.h"
  8
  9#include <windows.h>
 10#include "common/windows/perflib_.h"
 11#include "common/windows/nt.h"
 12#include <wchar.h>
 13
 14static inline void ffPerfCloseQueryHandle(HANDLE* phQuery) {
 15    if (*phQuery != nullptr) {
 16        PerfCloseQueryHandle(*phQuery);
 17        *phQuery = nullptr;
 18    }
 19}
 20
 21const char* detectThermalTemp(const FFCPUOptions* options, double* result) {
 22    struct FFPerfQuerySpec {
 23        PERF_COUNTER_IDENTIFIER Identifier;
 24        WCHAR Name[16];
 25    } querySpec = {
 26        .Identifier = {
 27            // Thermal Zone Information
 28            // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{52bc5412-dac2-449c-8bc2-96443888fe6b}
 29            .CounterSetGuid = { 0x52bc5412, 0xdac2, 0x449c, { 0x8b, 0xc2, 0x96, 0x44, 0x38, 0x88, 0xfe, 0x6b } },
 30            .Size = sizeof(querySpec),
 31            .CounterId = PERF_WILDCARD_COUNTER,
 32            .InstanceId = PERF_WILDCARD_COUNTER,
 33        },
 34        .Name = L"\\_TZ.CPUZ", // The standard(?) instance name for CPU temperature in the thermal provider
 35    };
 36
 37    if (options->tempSensor.length > 0) {
 38        NTSTATUS status = RtlUTF8ToUnicodeN(querySpec.Name, (ULONG) sizeof(querySpec.Name), nullptr, options->tempSensor.chars, (ULONG) options->tempSensor.length + 1);
 39        if (!NT_SUCCESS(status)) {
 40            FF_DEBUG("Invalid temp sensor string: %s", ffDebugNtStatus(status));
 41            return "Invalid temp sensor string";
 42        }
 43    }
 44
 45    DWORD dataSize = 0;
 46    DWORD ret = PerfEnumerateCounterSetInstances(nullptr, &querySpec.Identifier.CounterSetGuid, nullptr, 0, &dataSize);
 47    if (ret != ERROR_NOT_ENOUGH_MEMORY) {
 48        FF_DEBUG("PerfEnumerateCounterSetInstances() failed: %s", ffDebugWin32Error(ret));
 49        return "PerfEnumerateCounterSetInstances() failed";
 50    }
 51
 52    if (dataSize <= sizeof(PERF_INSTANCE_HEADER)) {
 53        FF_DEBUG("No `Thermal Zone Information` instances found");
 54        return "No `Thermal Zone Information` instances found";
 55    }
 56
 57    {
 58        FF_AUTO_FREE PERF_INSTANCE_HEADER* const pHead = malloc(dataSize);
 59        ret = PerfEnumerateCounterSetInstances(nullptr, &querySpec.Identifier.CounterSetGuid, pHead, dataSize, &dataSize);
 60        if (ret != ERROR_SUCCESS) {
 61            FF_DEBUG("PerfEnumerateCounterSetInstances() failed to get instance headers: %s", ffDebugWin32Error(ret));
 62            return "PerfEnumerateCounterSetInstances() failed to get instance headers";
 63        }
 64
 65        PERF_INSTANCE_HEADER* pInstanceHeader = pHead;
 66        while (1) {
 67            const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pInstanceHeader + sizeof(*pInstanceHeader));
 68            if (wcscmp(instanceName, querySpec.Name) == 0) {
 69                break;
 70            }
 71
 72            dataSize -= pInstanceHeader->Size;
 73            if (dataSize == 0) {
 74                break;
 75            }
 76            pInstanceHeader = (PERF_INSTANCE_HEADER*) ((BYTE*) pInstanceHeader + pInstanceHeader->Size);
 77        }
 78
 79        if (dataSize == 0) {
 80            if (options->tempSensor.length > 0) {
 81                FF_DEBUG("Unable to find CPU sensor");
 82                return "Unable to find CPU sensor";
 83            }
 84
 85            const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pHead + sizeof(*pHead));
 86            wcscpy(querySpec.Name, instanceName); // Use the first instance name if the specific one is not found
 87        }
 88    }
 89
 90    [[gnu::cleanup(ffPerfCloseQueryHandle)]]
 91    HANDLE hQuery = nullptr;
 92
 93    ret = PerfOpenQueryHandle(nullptr, &hQuery);
 94    if (ret != ERROR_SUCCESS) {
 95        FF_DEBUG("PerfOpenQueryHandle() failed: %s", ffDebugWin32Error(ret));
 96        return "PerfOpenQueryHandle() failed";
 97    }
 98
 99    ret = PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec));
100    if (ret != ERROR_SUCCESS) {
101        FF_DEBUG("PerfAddCounters() failed: %s", ffDebugWin32Error(ret));
102        return "PerfAddCounters() failed";
103    }
104
105    if (querySpec.Identifier.Status != ERROR_SUCCESS) {
106        FF_DEBUG("PerfAddCounters() reports invalid identifier: %s", ffDebugWin32Error(querySpec.Identifier.Status));
107        return "PerfAddCounters() reports invalid identifier";
108    }
109
110    ret = PerfQueryCounterData(hQuery, nullptr, 0, &dataSize);
111    if (ret != ERROR_NOT_ENOUGH_MEMORY) {
112        FF_DEBUG("PerfQueryCounterData(nullptr) failed: %s", ffDebugWin32Error(ret));
113        return "PerfQueryCounterData(nullptr) failed";
114    }
115
116    if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) { // PERF_ERROR_RETURN, should not happen
117        FF_DEBUG("instance doesn't exist");
118        return "instance doesn't exist";
119    }
120
121    FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = malloc(dataSize);
122
123    ret = PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize);
124    if (ret != ERROR_SUCCESS) {
125        FF_DEBUG("PerfQueryCounterData(pDataHeader) failed: %s", ffDebugWin32Error(ret));
126        return "PerfQueryCounterData(pDataHeader) failed";
127    }
128
129    PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*) (pDataHeader + 1);
130    if (pCounterHeader->dwType != PERF_MULTIPLE_COUNTERS) {
131        FF_DEBUG("Invalid counter type");
132        return "Invalid counter type";
133    }
134
135    PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*) (pCounterHeader + 1);
136    PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pMultiCounters + pMultiCounters->dwSize);
137
138    for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) {
139        if (pCounterData->dwDataSize == sizeof(int32_t)) {
140            DWORD* pCounterIds = (DWORD*) (pMultiCounters + 1);
141            int32_t value = *(int32_t*) (pCounterData + 1);
142            if (value == 0) {
143                return "Temperature data is zero";
144            }
145
146            switch (pCounterIds[iCounter]) {
147                case 0: // Temperature
148                    *result = value - 273;
149                    break;
150                case 3: // High Precision Temperature
151                    *result = value / 10.0 - 273;
152                    break;
153            }
154        }
155
156        pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pCounterData + pCounterData->dwSize);
157    }
158
159    return nullptr;
160}
161
162// 7.5
163typedef struct [[gnu::packed]] FFSmbiosProcessorInfo {
164    FFSmbiosHeader Header;
165
166    uint8_t SocketDesignation;     // string
167    uint8_t ProcessorType;         // enum
168    uint8_t ProcessorFamily;       // enum
169    uint8_t ProcessorManufacturer; // string
170    uint64_t ProcessorID;          // varies
171    uint8_t ProcessorVersion;      // string
172    uint8_t Voltage;               // varies
173    uint16_t ExternalClock;        // varies
174    uint16_t MaxSpeed;             // varies
175    uint16_t CurrentSpeed;         // varies
176    uint8_t Status;                // varies
177    uint8_t ProcessorUpgrade;      // enum
178
179    // 2.1+
180    uint16_t L1CacheHandle; // varies
181    uint16_t L2CacheHandle; // varies
182    uint16_t L3CacheHandle; // varies
183
184    // 2.3+
185    uint8_t SerialNumber; // string
186    uint8_t AssertTag;    // string
187    uint8_t PartNumber;   // string
188
189    // 2.5+
190    uint8_t CoreCount;                 // varies
191    uint8_t CoreEnabled;               // varies
192    uint8_t ThreadCount;               // varies
193    uint16_t ProcessorCharacteristics; // bit field
194
195    // 2.6+
196    uint16_t ProcessorFamily2; // enum
197
198    // 3.0+
199    uint16_t CoreCount2;   // varies
200    uint16_t CoreEnabled2; // varies
201    uint16_t ThreadCount2; // varies
202
203    // 3.6+
204    uint16_t ThreadEnabled; // varies
205} FFSmbiosProcessorInfo;
206
207static_assert(offsetof(FFSmbiosProcessorInfo, ThreadEnabled) == 0x30,
208    "FFSmbiosProcessorInfo: Wrong struct alignment");
209
210static const char* detectMaxSpeedBySmbios(FFCPUResult* cpu) {
211    const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
212    if (!smbiosTable) {
213        FF_DEBUG("Failed to get SMBIOS data");
214        return "Failed to get SMBIOS data";
215    }
216
217    const FFSmbiosProcessorInfo* data = (const FFSmbiosProcessorInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_PROCESSOR_INFO];
218
219    if (!data) {
220        FF_DEBUG("Processor information is not found in SMBIOS data");
221        return "Processor information is not found in SMBIOS data";
222    }
223
224    while (data->ProcessorType != 0x03 /*Central Processor*/ || (data->Status & 0b00000111) != 1 /*Enabled*/) {
225        data = (const FFSmbiosProcessorInfo*) ffSmbiosNextEntry(&data->Header);
226        if (data->Header.Type != FF_SMBIOS_TYPE_PROCESSOR_INFO) {
227            FF_DEBUG("No active CPU is found in SMBIOS data");
228            return "No active CPU is found in SMBIOS data";
229        }
230    }
231
232    uint32_t speed = FF_READ_LE(data->MaxSpeed);
233    // Sometimes SMBIOS reports invalid value. We assume that max speed is small than 2x of base
234    if (speed < cpu->frequencyBase || speed > cpu->frequencyBase * 2) {
235        FF_DEBUG("Possible invalid CPU max speed in SMBIOS data. See #800");
236        return "Possible invalid CPU max speed in SMBIOS data. See #800";
237    }
238
239    cpu->frequencyMax = speed;
240
241    return nullptr;
242}
243
244static uint32_t getNumLogicalCores(const SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr) {
245    uint32_t num = 0;
246    for (uint32_t i = 0; i < ptr->Processor.GroupCount; ++i) {
247        num += (uint32_t)
248#if _WIN64
249            __builtin_popcountll
250#else
251            __builtin_popcountl
252#endif
253            (ptr->Processor.GroupMask[i].Mask);
254    }
255    return num;
256}
257
258static const char* detectNCores(const FFCPUOptions* options, FFCPUResult* cpu) {
259    LOGICAL_PROCESSOR_RELATIONSHIP lpr = RelationAll;
260    ULONG length = 0;
261    NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), nullptr, 0, &length);
262    if (length == 0) {
263        FF_DEBUG("GetLogicalProcessorInformationEx(RelationAll, nullptr, &length) failed");
264        return "GetLogicalProcessorInformationEx(RelationAll, nullptr, &length) failed";
265    }
266
267    FF_AUTO_FREE SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*
268        pProcessorInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(length);
269
270    NTSTATUS status = NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), pProcessorInfo, length, &length);
271    if (!NT_SUCCESS(status)) {
272        FF_DEBUG("GetLogicalProcessorInformationEx(RelationAll, pProcessorInfo, &length) failed: %s", ffDebugNtStatus(status));
273        return "GetLogicalProcessorInformationEx(RelationAll, pProcessorInfo, &length) failed";
274    }
275
276    for (
277        SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr = pProcessorInfo;
278        (uint8_t*) ptr < ((uint8_t*) pProcessorInfo) + length;
279        ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((uint8_t*) ptr) + ptr->Size)) {
280        if (ptr->Relationship == RelationGroup) {
281            for (uint32_t index = 0; index < ptr->Group.ActiveGroupCount; ++index) {
282                cpu->coresOnline += ptr->Group.GroupInfo[index].ActiveProcessorCount;
283                cpu->coresLogical += ptr->Group.GroupInfo[index].MaximumProcessorCount;
284            }
285        } else if (ptr->Relationship == RelationProcessorCore) {
286            ++cpu->coresPhysical;
287
288            if (options->showPeCoreCount) {
289                for (uint32_t i = 0; i < ARRAY_SIZE(cpu->coreTypes); ++i) {
290                    if (ptr->Processor.EfficiencyClass + 1 == cpu->coreTypes[i].freq) {
291                        cpu->coreTypes[i].count += getNumLogicalCores(ptr);
292                        break;
293                    } else if (cpu->coreTypes[i].freq == 0) {
294                        cpu->coreTypes[i].freq = ptr->Processor.EfficiencyClass + 1;
295                        cpu->coreTypes[i].count += getNumLogicalCores(ptr);
296                        break;
297                    }
298                }
299            }
300        } else if (ptr->Relationship == RelationProcessorPackage) {
301            ++cpu->packages;
302        } else if (ptr->Relationship == RelationNumaNode) {
303            ++cpu->numaNodes;
304        }
305    }
306
307    return nullptr;
308}
309
310static const char* detectByRegistry(FFCPUResult* cpu) {
311    FF_AUTO_CLOSE_FD HANDLE hKey = nullptr;
312    if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", &hKey, nullptr)) {
313        FF_DEBUG("ffRegOpenKeyForRead() failed for HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");
314        return "ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0\", &hKey, nullptr) failed";
315    }
316
317    if (ffRegReadValues(hKey, 3, (FFRegValueArg[]){
318                                     FF_ARG(cpu->name, L"ProcessorNameString"),
319                                     FF_ARG(cpu->vendor, L"VendorIdentifier"),
320                                     FF_ARG(cpu->frequencyBase, L"~MHz"),
321                                 },
322            nullptr)) {
323        ffStrbufTrimRightSpace(&cpu->vendor);
324    } else {
325        FF_DEBUG("ffRegReadValues() failed for CPU registry key");
326        return "ffRegReadValues() failed for CPU registry key";
327    }
328
329    return nullptr;
330}
331
332const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
333    detectNCores(options, cpu);
334
335    const char* error = detectByRegistry(cpu);
336    if (error) {
337        return error;
338    }
339
340    ffCPUDetectByCpuid(cpu);
341
342    if (cpu->frequencyMax == 0) {
343        detectMaxSpeedBySmbios(cpu);
344    }
345
346    if (options->temp) {
347        detectThermalTemp(options, &cpu->temperature);
348    }
349
350    return nullptr;
351}