From: James Eder Subject: [PATCH 5/5] ntdll: Add more complete implementation of NtPowerInformation Message-Id: <1347049770-13349-5-git-send-email-jimportal@gmail.com> Date: Fri, 7 Sep 2012 14:29:30 -0600 This patch: * Moves CPU Hz detection out of fill_cpu_info() and implements it locally. The cpuHz global was only used by this function and we need to detect this on the fly for CurrentMhz anyway. * For Linux systems with cpufreq more complete and dynamic information is gathered. For systems without cpufreq (systems running under under QEMU, for example) the information is gathered from /proc/cpuinfo. * For Apple we a get better value for MaxMhz but we're still reporting the same information for all processors rather than providing it on a per-CPU basis. * For the BSDs we're still the same as before, reporting the same number for all fields across all processors. *** For this patch, some testing is needed. *** I have no Apples or BSDs so that bit of code is based on code removed from fill_cpu_info and some internet searching. I could try a BSD flavor or two in Qemu, but I have no intention of buying a Mac. So, since I have to ask for testing anyway, I might as well ask for it from BSDers too. Please test and report back if you happen to use one of those operating systems. For testing, you can do something like: $ WINEDEBUG=+ntdll wine notepad 2>&1 | grep NtPowerInformation which will show the contents of the array. For example it could look like: trace:ntdll:NtPowerInformation cpu_power[0] = 0 3200 800 3200 0 0 trace:ntdll:NtPowerInformation cpu_power[1] = 1 3200 800 3200 0 0 trace:ntdll:NtPowerInformation cpu_power[2] = 2 3200 3200 3200 0 0 trace:ntdll:NtPowerInformation cpu_power[3] = 3 3200 800 3200 0 0 The numbers after the '=' sign are the members of a PROCESSOR_POWER_INFORMATION structure (in order: Number, MaxMhz, CurrentMhz, MhzLimit, MaxIdleState, CurrentIdleState) --- dlls/ntdll/nt.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 156 insertions(+), 29 deletions(-) diff --git a/dlls/ntdll/nt.c b/dlls/ntdll/nt.c index d38fedb..07b75f3 100644 --- a/dlls/ntdll/nt.c +++ b/dlls/ntdll/nt.c @@ -810,7 +810,6 @@ NTSTATUS WINAPI NtSetIntervalProfile( } static SYSTEM_CPU_INFORMATION cached_sci; -static ULONGLONG cpuHz = 1000000000; /* default to a 1GHz */ #define AUTH 0x68747541 /* "Auth" */ #define ENTI 0x69746e65 /* "enti" */ @@ -898,10 +897,10 @@ static inline void get_cpuinfo(SYSTEM_CPU_INFORMATION* info) } /****************************************************************** - * fill_cpu_info + * fill_cpu_info * - * inits a couple of places with CPU related information: - * - cached_sci & cpuHZ in this file + * Initializes a couple of places with CPU related information: + * - cached_sci in this file * - Peb->NumberOfProcessors * - SharedUserData->ProcessFeatures[] array */ @@ -1019,16 +1018,6 @@ void fill_cpu_info(void) cached_sci.Revision = cached_sci.Revision | x; continue; } - if (!strcasecmp(line, "cpu MHz")) - { - double cmz; - if (sscanf( value, "%lf", &cmz ) == 1) - { - /* SYSTEMINFO doesn't have a slot for cpu speed, so store in a global */ - cpuHz = cmz * 1000 * 1000; - } - continue; - } if (!strcasecmp(line, "fdiv_bug")) { if (!strncasecmp(value, "yes",3)) @@ -1190,10 +1179,6 @@ void fill_cpu_info(void) ret = sysctlbyname("hw.ncpu", &num, &len, NULL, 0); if (!ret) NtCurrentTeb()->Peb->NumberOfProcessors = num; - - len = sizeof(num); - if (!sysctlbyname("hw.clockrate", &num, &len, NULL, 0)) - cpuHz = num * 1000 * 1000; } #elif defined(__sun) { @@ -1300,9 +1285,6 @@ void fill_cpu_info(void) default: break; } /* switch (cputype) */ } - valSize = sizeof(longVal); - if (!sysctlbyname("hw.cpufrequency", &longVal, &valSize, NULL, 0)) - cpuHz = longVal; } #else FIXME("not yet supported on this system\n"); @@ -2261,7 +2243,19 @@ NTSTATUS WINAPI NtInitiatePowerAction( SystemAction,MinSystemState,Flags,Asynchronous); return STATUS_NOT_IMPLEMENTED; } - + +#ifdef linux +static int have_cpufreq(void) +{ + FILE* f = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq", "r"); + if(f) + { + fclose(f); + return 1; + } + return 0; +} +#endif /****************************************************************************** * NtPowerInformation [NTDLL.@] @@ -2326,24 +2320,157 @@ NTSTATUS WINAPI NtPowerInformation( return STATUS_SUCCESS; } case ProcessorInformation: { + const int cannedMHz = 1000; /* We fake a 1GHz processor if we can't conjure up real values */ PROCESSOR_POWER_INFORMATION* cpu_power = lpOutputBuffer; - int i; + int i, out_cpus; WARN("semi-stub: ProcessorInformation\n"); if ((lpOutputBuffer == NULL) || (nOutputBufferSize == 0)) return STATUS_INVALID_PARAMETER; - if ((nOutputBufferSize / sizeof(PROCESSOR_POWER_INFORMATION)) < NtCurrentTeb()->Peb->NumberOfProcessors) + out_cpus = NtCurrentTeb()->Peb->NumberOfProcessors; + if ((nOutputBufferSize / sizeof(PROCESSOR_POWER_INFORMATION)) < out_cpus) return STATUS_BUFFER_TOO_SMALL; +#if defined(linux) + if(have_cpufreq()) { + char filename[128]; + FILE* f; + + for(i = 0; i < out_cpus; i++) { + sprintf(filename, "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_cur_freq", i); + f = fopen(filename, "r"); + if((f == NULL) || (fscanf(f, "%d", &cpu_power[i].CurrentMhz) != 1)) + cpu_power[i].CurrentMhz = cannedMHz; + else + cpu_power[i].CurrentMhz /= 1000; + fclose(f); + + sprintf(filename, "/sys/devices/system/cpu/cpu%d/cpufreq/cpuinfo_max_freq", i); + f = fopen(filename, "r"); + if((f == NULL) || (fscanf(f, "%d", &cpu_power[i].MaxMhz) != 1)) + cpu_power[i].MaxMhz = cpu_power[i].CurrentMhz; + else + cpu_power[i].MaxMhz /= 1000; + fclose(f); + + sprintf(filename, "/sys/devices/system/cpu/cpu%d/cpufreq/scaling_max_freq", i); + f = fopen(filename, "r"); + if((f == NULL) || (fscanf(f, "%d", &cpu_power[i].MhzLimit) != 1)) + cpu_power[i].MhzLimit = cpu_power[i].MaxMhz; + else + cpu_power[i].MhzLimit /= 1000; + fclose(f); + + cpu_power[i].Number = i; + cpu_power[i].MaxIdleState = 0; /* FIXME */ + cpu_power[i].CurrentIdleState = 0; /* FIXME */ + } + } + else { + /* Fall back to using /proc/cpuinfo for systems without cpufreq. For most + * distributions on recent enough hardware, this is only likely to happen + * while running in virtualized environments such as QEMU. */ + FILE* f = fopen("/proc/cpuinfo", "r"); + if (f) { + char line[512]; + int i = 0; + while (fgets(line, 512, f) != NULL) { + char *s, *value; + if (!(value = strchr(line,':'))) + continue; + /* terminate the valuename */ + s = value - 1; + while ((s >= line) && isspace(*s)) s--; + *(s + 1) = '\0'; + /* and strip leading spaces from value */ + value += 1; + while (isspace(*value)) value++; + if ((s = strchr(value,'\n'))) + *s = '\0'; + if (!strcasecmp(line, "processor")) { + unsigned long p; + if (sscanf(value, "%ld", &p)) + cpu_power[i].Number = p; + continue; + } + if (!strcasecmp(line, "cpu MHz")) { + double cmz; + if (sscanf(value, "%lf", &cmz) == 1) + cpu_power[i].CurrentMhz = cmz; + if (cpu_power[i].CurrentMhz > cpu_power[0].MaxMhz) + cpu_power[0].MaxMhz = cpu_power[i].CurrentMhz; + i++; + if (i >= out_cpus) + break; + continue; + } + } + fclose(f); + for(i = 0; i < out_cpus; i++) { + cpu_power[i].MaxMhz = cpu_power[i].MhzLimit = cpu_power[0].MaxMhz; + cpu_power[i].MaxIdleState = 0; /* FIXME */ + cpu_power[i].CurrentIdleState = 0; /* FIXME */ + } + } + } +#elif defined(__FreeBSD__) || defined (__FreeBSD_kernel__) || defined(__DragonFly__) + { + int num; + size_t valSize = sizeof(num); + if (sysctlbyname("hw.clockrate", &num, &valSize, NULL, 0)) + num = cannedMHz; + for(i = 0; i < out_cpus; i++) { + cpu_power[i].CurrentMhz = num; + cpu_power[i].MaxMhz = num; + cpu_power[i].MhzLimit = num; + cpu_power[i].Number = i; + cpu_power[i].MaxIdleState = 0; /* FIXME */ + cpu_power[i].CurrentIdleState = 0; /* FIXME */ + } + } +#elif defined (__APPLE__) + { + size_t valSize; + unsigned long long currentMhz; + unsigned long long maxMhz; + + valSize = sizeof(currentMhz); + if (!sysctlbyname("hw.cpufrequency", ¤tMhz, &valSize, NULL, 0)) + currentMhz /= 10000000; + else + currentMhz = cannedMHz; - for(i = 0; i < NtCurrentTeb()->Peb->NumberOfProcessors; i++) { + valSize = sizeof(maxMhz); + if (!sysctlbyname("hw.cpufrequency_max", &maxMhz, &valSize, NULL, 0)) + maxMhz /= 10000000; + else + maxMhz = currentMhz; + + for(i = 0; i < out_cpus; i++) { + cpu_power[i].CurrentMhz = currentMhz; + cpu_power[i].MaxMhz = maxMhz; + cpu_power[i].MhzLimit = maxMhz; + cpu_power[i].Number = i; + cpu_power[i].MaxIdleState = 0; /* FIXME */ + cpu_power[i].CurrentIdleState = 0; /* FIXME */ + } + } +#else + for(i = 0; i < out_cpus; i++) { + cpu_power[i].CurrentMhz = cannedMHz; + cpu_power[i].MaxMhz = cannedMHz; + cpu_power[i].MhzLimit = cannedMHz; cpu_power[i].Number = i; - cpu_power[i].MaxMhz = cpuHz / 1000000; - cpu_power[i].CurrentMhz = cpuHz / 1000000; - cpu_power[i].MhzLimit = cpuHz / 1000000; - cpu_power[i].MaxIdleState = 0; /* FIXME */ + cpu_power[i].MaxIdleState = 0; /* FIXME */ cpu_power[i].CurrentIdleState = 0; /* FIXME */ } + WARN("Unable to detect CPU MHz for this platform. Reporting %d MHz.\n", cannedMHz); +#endif + for(i = 0; i < out_cpus; i++) { + TRACE("cpu_power[%d] = %lu %lu %lu %lu %lu %lu\n", i, cpu_power[i].Number, + cpu_power[i].MaxMhz, cpu_power[i].CurrentMhz, cpu_power[i].MhzLimit, + cpu_power[i].MaxIdleState, cpu_power[i].CurrentIdleState); + } return STATUS_SUCCESS; } default: -- 1.7.12