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}