summaryrefslogtreecommitdiffstats
path: root/src/detection/cpu/cpu_windows.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/detection/cpu/cpu_windows.c')
-rw-r--r--src/detection/cpu/cpu_windows.c320
1 files changed, 320 insertions, 0 deletions
diff --git a/src/detection/cpu/cpu_windows.c b/src/detection/cpu/cpu_windows.c
new file mode 100644
index 0000000..86edce9
--- /dev/null
+++ b/src/detection/cpu/cpu_windows.c
@@ -0,0 +1,320 @@
+#include "cpu.h"
+#include "common/windows/registry.h"
+#include "common/windows/nt.h"
+#include "common/mallocHelper.h"
+#include "common/smbios.h"
+
+#include <windows.h>
+#include "common/windows/perflib_.h"
+#include "common/windows/nt.h"
+#include <wchar.h>
+
+static inline void ffPerfCloseQueryHandle(HANDLE* phQuery) {
+ if (*phQuery != NULL) {
+ PerfCloseQueryHandle(*phQuery);
+ *phQuery = NULL;
+ }
+}
+
+const char* detectThermalTemp(const FFCPUOptions* options, double* result) {
+ struct FFPerfQuerySpec {
+ PERF_COUNTER_IDENTIFIER Identifier;
+ WCHAR Name[16];
+ } querySpec = {
+ .Identifier = {
+ // Thermal Zone Information
+ // HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Perflib\_V2Providers\{383487a6-3676-4870-a4e7-d45b30c35629}\{52bc5412-dac2-449c-8bc2-96443888fe6b}
+ .CounterSetGuid = { 0x52bc5412, 0xdac2, 0x449c, { 0x8b, 0xc2, 0x96, 0x44, 0x38, 0x88, 0xfe, 0x6b } },
+ .Size = sizeof(querySpec),
+ .CounterId = PERF_WILDCARD_COUNTER,
+ .InstanceId = PERF_WILDCARD_COUNTER,
+ },
+ .Name = L"\\_TZ.CPUZ", // The standard(?) instance name for CPU temperature in the thermal provider
+ };
+
+ if (options->tempSensor.length > 0) {
+ if (!NT_SUCCESS(RtlUTF8ToUnicodeN(querySpec.Name, (ULONG) sizeof(querySpec.Name), NULL, options->tempSensor.chars, (ULONG) options->tempSensor.length + 1))) {
+ return "Invalid temp sensor string";
+ }
+ }
+
+ DWORD dataSize = 0;
+ if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) {
+ return "PerfEnumerateCounterSetInstances() failed";
+ }
+
+ if (dataSize <= sizeof(PERF_INSTANCE_HEADER)) {
+ return "No `Thermal Zone Information` instances found";
+ }
+
+ {
+ FF_AUTO_FREE PERF_INSTANCE_HEADER* const pHead = malloc(dataSize);
+ if (PerfEnumerateCounterSetInstances(NULL, &querySpec.Identifier.CounterSetGuid, pHead, dataSize, &dataSize) != ERROR_SUCCESS) {
+ return "PerfEnumerateCounterSetInstances() failed to get instance headers";
+ }
+
+ PERF_INSTANCE_HEADER* pInstanceHeader = pHead;
+ while (1) {
+ const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pInstanceHeader + sizeof(*pInstanceHeader));
+ if (wcscmp(instanceName, querySpec.Name) == 0) {
+ break;
+ }
+
+ dataSize -= pInstanceHeader->Size;
+ if (dataSize == 0) {
+ break;
+ }
+ pInstanceHeader = (PERF_INSTANCE_HEADER*) ((BYTE*) pInstanceHeader + pInstanceHeader->Size);
+ }
+
+ if (dataSize == 0) {
+ if (options->tempSensor.length > 0) {
+ return "Unable to find CPU sensor";
+ }
+
+ const wchar_t* instanceName = (const wchar_t*) ((BYTE*) pHead + sizeof(*pHead));
+ wcscpy(querySpec.Name, instanceName); // Use the first instance name if the specific one is not found
+ }
+ }
+
+ FF_A_CLEANUP(ffPerfCloseQueryHandle)
+ HANDLE hQuery = NULL;
+
+ if (PerfOpenQueryHandle(NULL, &hQuery) != ERROR_SUCCESS) {
+ return "PerfOpenQueryHandle() failed";
+ }
+
+ if (PerfAddCounters(hQuery, &querySpec.Identifier, sizeof(querySpec)) != ERROR_SUCCESS) {
+ return "PerfAddCounters() failed";
+ }
+
+ if (querySpec.Identifier.Status != ERROR_SUCCESS) {
+ return "PerfAddCounters() reports invalid identifier";
+ }
+
+ if (PerfQueryCounterData(hQuery, NULL, 0, &dataSize) != ERROR_NOT_ENOUGH_MEMORY) {
+ return "PerfQueryCounterData(NULL) failed";
+ }
+
+ if (dataSize <= sizeof(PERF_DATA_HEADER) + sizeof(PERF_COUNTER_HEADER)) { // PERF_ERROR_RETURN, should not happen
+ return "instance doesn't exist";
+ }
+
+ FF_AUTO_FREE PERF_DATA_HEADER* const pDataHeader = malloc(dataSize);
+
+ if (PerfQueryCounterData(hQuery, pDataHeader, dataSize, &dataSize) != ERROR_SUCCESS) {
+ return "PerfQueryCounterData(pDataHeader) failed";
+ }
+
+ PERF_COUNTER_HEADER* pCounterHeader = (PERF_COUNTER_HEADER*) (pDataHeader + 1);
+ if (pCounterHeader->dwType != PERF_MULTIPLE_COUNTERS) {
+ return "Invalid counter type";
+ }
+
+ PERF_MULTI_COUNTERS* pMultiCounters = (PERF_MULTI_COUNTERS*) (pCounterHeader + 1);
+ PERF_COUNTER_DATA* pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pMultiCounters + pMultiCounters->dwSize);
+
+ for (ULONG iCounter = 0; iCounter != pMultiCounters->dwCounters; iCounter++) {
+ if (pCounterData->dwDataSize == sizeof(int32_t)) {
+ DWORD* pCounterIds = (DWORD*) (pMultiCounters + 1);
+ int32_t value = *(int32_t*) (pCounterData + 1);
+ if (value == 0) {
+ return "Temperature data is zero";
+ }
+
+ switch (pCounterIds[iCounter]) {
+ case 0: // Temperature
+ *result = value - 273;
+ break;
+ case 3: // High Precision Temperature
+ *result = value / 10.0 - 273;
+ break;
+ }
+ }
+
+ pCounterData = (PERF_COUNTER_DATA*) ((BYTE*) pCounterData + pCounterData->dwSize);
+ }
+
+ return NULL;
+}
+
+// 7.5
+typedef struct FFSmbiosProcessorInfo {
+ FFSmbiosHeader Header;
+
+ uint8_t SocketDesignation; // string
+ uint8_t ProcessorType; // enum
+ uint8_t ProcessorFamily; // enum
+ uint8_t ProcessorManufacturer; // string
+ uint64_t ProcessorID; // varies
+ uint8_t ProcessorVersion; // string
+ uint8_t Voltage; // varies
+ uint16_t ExternalClock; // varies
+ uint16_t MaxSpeed; // varies
+ uint16_t CurrentSpeed; // varies
+ uint8_t Status; // varies
+ uint8_t ProcessorUpgrade; // enum
+
+ // 2.1+
+ uint16_t L1CacheHandle; // varies
+ uint16_t L2CacheHandle; // varies
+ uint16_t L3CacheHandle; // varies
+
+ // 2.3+
+ uint8_t SerialNumber; // string
+ uint8_t AssertTag; // string
+ uint8_t PartNumber; // string
+
+ // 2.5+
+ uint8_t CoreCount; // varies
+ uint8_t CoreEnabled; // varies
+ uint8_t ThreadCount; // varies
+ uint16_t ProcessorCharacteristics; // bit field
+
+ // 2.6+
+ uint16_t ProcessorFamily2; // enum
+
+ // 3.0+
+ uint16_t CoreCount2; // varies
+ uint16_t CoreEnabled2; // varies
+ uint16_t ThreadCount2; // varies
+
+ // 3.6+
+ uint16_t ThreadEnabled; // varies
+} FF_A_PACKED FFSmbiosProcessorInfo;
+
+static_assert(offsetof(FFSmbiosProcessorInfo, ThreadEnabled) == 0x30,
+ "FFSmbiosProcessorInfo: Wrong struct alignment");
+
+static const char* detectMaxSpeedBySmbios(FFCPUResult* cpu) {
+ const FFSmbiosHeaderTable* smbiosTable = ffGetSmbiosHeaderTable();
+ if (!smbiosTable) {
+ return "Failed to get SMBIOS data";
+ }
+
+ const FFSmbiosProcessorInfo* data = (const FFSmbiosProcessorInfo*) (*smbiosTable)[FF_SMBIOS_TYPE_PROCESSOR_INFO];
+
+ if (!data) {
+ return "Processor information is not found in SMBIOS data";
+ }
+
+ while (data->ProcessorType != 0x03 /*Central Processor*/ || (data->Status & 0b00000111) != 1 /*Enabled*/) {
+ data = (const FFSmbiosProcessorInfo*) ffSmbiosNextEntry(&data->Header);
+ if (data->Header.Type != FF_SMBIOS_TYPE_PROCESSOR_INFO) {
+ return "No active CPU is found in SMBIOS data";
+ }
+ }
+
+ uint32_t speed = data->MaxSpeed;
+ // Sometimes SMBIOS reports invalid value. We assume that max speed is small than 2x of base
+ if (speed < cpu->frequencyBase || speed > cpu->frequencyBase * 2) {
+ return "Possible invalid CPU max speed in SMBIOS data. See #800";
+ }
+
+ cpu->frequencyMax = speed;
+
+ return NULL;
+}
+
+static const char* detectNCores(FFCPUResult* cpu) {
+ LOGICAL_PROCESSOR_RELATIONSHIP lpr = RelationAll;
+ ULONG length = 0;
+ NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), NULL, 0, &length);
+ if (length == 0) {
+ return "GetLogicalProcessorInformationEx(RelationAll, NULL, &length) failed";
+ }
+
+ SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* FF_AUTO_FREE
+ pProcessorInfo = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) malloc(length);
+
+ if (!NT_SUCCESS(NtQuerySystemInformationEx(SystemLogicalProcessorAndGroupInformation, &lpr, sizeof(lpr), pProcessorInfo, length, &length))) {
+ return "GetLogicalProcessorInformationEx(RelationAll, pProcessorInfo, &length) failed";
+ }
+
+ for (
+ SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX* ptr = pProcessorInfo;
+ (uint8_t*) ptr < ((uint8_t*) pProcessorInfo) + length;
+ ptr = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX*) (((uint8_t*) ptr) + ptr->Size)) {
+ if (ptr->Relationship == RelationGroup) {
+ for (uint32_t index = 0; index < ptr->Group.ActiveGroupCount; ++index) {
+ cpu->coresOnline += ptr->Group.GroupInfo[index].ActiveProcessorCount;
+ cpu->coresLogical += ptr->Group.GroupInfo[index].MaximumProcessorCount;
+ }
+ } else if (ptr->Relationship == RelationProcessorCore) {
+ ++cpu->coresPhysical;
+ } else if (ptr->Relationship == RelationProcessorPackage) {
+ ++cpu->packages;
+ } else if (ptr->Relationship == RelationNumaNode) {
+ ++cpu->numaNodes;
+ }
+ }
+
+ return NULL;
+}
+
+static const char* detectByRegistry(FFCPUResult* cpu) {
+ FF_AUTO_CLOSE_FD HANDLE hKey = NULL;
+ if (!ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0", &hKey, NULL)) {
+ return "ffRegOpenKeyForRead(HKEY_LOCAL_MACHINE, L\"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0\", &hKey, NULL) failed";
+ }
+
+ if (ffRegReadValues(hKey, 3, (FFRegValueArg[]) {
+ FF_ARG(cpu->name, L"ProcessorNameString"),
+ FF_ARG(cpu->vendor, L"VendorIdentifier"),
+ FF_ARG(cpu->frequencyBase, L"~MHz"),
+ },
+ NULL)) {
+ ffStrbufTrimRightSpace(&cpu->vendor);
+ } else {
+ return "ffRegReadValues() failed for CPU registry key";
+ }
+
+ return NULL;
+}
+
+static const char* detectCoreTypes(FFCPUResult* cpu) {
+ FF_AUTO_FREE PROCESSOR_POWER_INFORMATION* pinfo = calloc(cpu->coresLogical, sizeof(PROCESSOR_POWER_INFORMATION));
+ if (!NT_SUCCESS(NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, (ULONG) sizeof(PROCESSOR_POWER_INFORMATION) * cpu->coresLogical))) {
+ return "NtPowerInformation(ProcessorInformation, NULL, 0, pinfo, size) failed";
+ }
+
+ for (uint32_t icore = 0; icore < cpu->coresLogical && pinfo[icore].MhzLimit; ++icore) {
+ uint32_t ifreq = 0;
+ while (cpu->coreTypes[ifreq].freq != pinfo[icore].MhzLimit && cpu->coreTypes[ifreq].freq > 0) {
+ ++ifreq;
+ }
+ if (cpu->coreTypes[ifreq].freq == 0) {
+ cpu->coreTypes[ifreq].freq = pinfo[icore].MhzLimit;
+ }
+ ++cpu->coreTypes[ifreq].count;
+ }
+
+ if (cpu->frequencyBase == 0) {
+ cpu->frequencyBase = pinfo->MaxMhz;
+ }
+ return NULL;
+}
+
+const char* ffDetectCPUImpl(const FFCPUOptions* options, FFCPUResult* cpu) {
+ detectNCores(cpu);
+
+ const char* error = detectByRegistry(cpu);
+ if (error) {
+ return error;
+ }
+
+ ffCPUDetectByCpuid(cpu);
+ if (options->showPeCoreCount) {
+ detectCoreTypes(cpu);
+ }
+
+ if (cpu->frequencyMax == 0) {
+ detectMaxSpeedBySmbios(cpu);
+ }
+
+ if (options->temp) {
+ detectThermalTemp(options, &cpu->temperature);
+ }
+
+ return NULL;
+}