/* * Copyright (C) 2006, Intel Corporation * * This file is part of the Linux-ready Firmware Developer Kit * * This program file is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation;version 2.1 of the License. * * This library is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program in a file named COPYING; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include struct freq { unsigned long Hz; unsigned long speed; }; static int nrspeeds = -1; static int totaltests = 1; static int performedtests = 0; static int no_cpufreq = 0; static unsigned long topspeed=1; #define GET_PERFORMANCE_MAX (0) #define GET_PERFORMANCE_MIN (1) #define GET_PERFORMANCE_AVG (2) static int count_ints(char *str) { char *c; int count=0; c=str; while (c && strlen(c)>0) { char *c2; c2=strchr(c,' '); if (!c2) break; c=c2+1; count++; } return count; } static void set_governor(int cpunr) { char path[PATH_MAX]; FILE *file; sprintf(path, "/sys/devices/system/cpu/cpu%i/cpufreq/scaling_governor", cpunr); file = fopen(path, "w"); if (file == NULL) { if (!no_cpufreq) report_result("cpufreq", WARN, "Frequency scaling not supported", NULL, NULL); no_cpufreq = 1; return; } fprintf(file,"userspace"); fclose(file); } static int cpu_exists(int cpunr) { char path[PATH_MAX]; sprintf(path, "/sys/devices/system/cpu/cpu%i/cpufreq/scaling_governor", cpunr); return !access(path, R_OK); } static void set_HZ(int cpunr, unsigned long Hz) { cpu_set_t mask, oldset; char path[PATH_MAX]; FILE *file; /* First, go to the right cpu */ sched_getaffinity(0, sizeof(oldset), &oldset); CPU_ZERO(&mask); CPU_SET(cpunr, &mask); sched_setaffinity(0, sizeof(mask), &mask); set_governor(cpunr); /* then set the speed */ sprintf(path, "/sys/devices/system/cpu/cpu%i/cpufreq/scaling_setspeed", cpunr); file = fopen(path, "w"); if (!file) return; fprintf(file, "%lu", Hz); fclose(file); sched_setaffinity(0, sizeof(oldset), &oldset); } static unsigned long get_performance(int cpunr) { cpu_set_t mask, oldset; time_t current; unsigned long loopcount = 0; /* First, go to the right cpu */ sched_getaffinity(0, sizeof(oldset), &oldset); CPU_ZERO(&mask); CPU_SET(cpunr, &mask); sched_setaffinity(0, sizeof(mask), &mask); current = time(NULL); while (current == time(NULL)) sched_yield(); current = time(NULL); do { double A, B; int i; A = 1.234567; B = 3.121213; for (i=0; i<100; i++) { A = A * B; B = A * A; A = A - B + sqrt(A); A = A * B; B = A * A; A = A - B + sqrt(A); A = A * B; B = A * A; A = A - B + sqrt(A); A = A * B; B = A * A; A = A - B + sqrt(A); } loopcount++; } while (current == time(NULL)); sched_setaffinity(0, sizeof(oldset), &oldset); return loopcount; } static unsigned long get_performance_repeat(int cpunr, unsigned long Hz, int count, int type) { int i; unsigned long max = 0, min = ULONG_MAX, real_count = 0; unsigned long long cumulative; unsigned long retval; set_HZ(cpunr, Hz); for (i = 0; i < count; i++) { unsigned long temp; temp = get_performance(cpunr); if (temp) { if (temp < min) min = temp; if (temp > max) max = temp; cumulative += temp; real_count++; } } switch (type) { case GET_PERFORMANCE_MAX: retval = max; break; case GET_PERFORMANCE_MIN: retval = min; break; case GET_PERFORMANCE_AVG: retval = cumulative/real_count; break; default: retval = 0; break; } return retval; } static char *HzToHuman(unsigned long hz) { static char buffer[1024]; memset(buffer, 0, 1024); unsigned long long Hz; Hz = hz; /* default: just put the Number in */ sprintf(buffer,"%9lli", Hz); if (Hz>1000) sprintf(buffer, "%6lli Mhz", (Hz+500)/1000); if (Hz>1500000) sprintf(buffer, "%6.2f Ghz", (Hz+50000.0)/1000000); return buffer; } static unsigned long get_claimed_hz(int cpunr) { FILE *file; char path[PATH_MAX]; unsigned long long value; sprintf(path, "/sys/devices/system/cpu/cpu%i/cpufreq/scaling_max_freq",cpunr); file = fopen(path, "r"); if (!file) return 0; if (!fgets(path, PATH_MAX, file)) { fclose(file); return 0; } value = strtoull(path, NULL, 10); fclose(file); return value; } static void do_cpu(int cpunr) { char path[PATH_MAX]; char line[4096]; struct freq freqs[32]; FILE *file; char *c, *c2; int i, delta; int speedcount; static int warned=0; char *details = NULL; int warned_PSS = 0; memset(freqs, 0, sizeof(freqs)); memset(line, 0, 4096); set_governor(cpunr); sprintf(path, "/sys/devices/system/cpu/cpu%i/cpufreq/scaling_available_frequencies", cpunr); file = fopen(path, "r"); if (file == NULL) { if (!no_cpufreq) report_result("cpufreq", WARN, "Frequency scaling not supported", NULL, NULL); no_cpufreq = 1; return; } if (fgets(line, 4095, file)==NULL) return; fclose(file); if (totaltests==1) totaltests = (2+count_ints(line)) * sysconf(_SC_NPROCESSORS_CONF) + 2; c = line; i = 0; while (c && strlen(c)>1) { c2 = strchr(c, ' '); if (c2) { *c2=0; c2++; } else { c2 = NULL; } freqs[i].Hz = strtoull(c, NULL, 10); set_HZ(cpunr, freqs[i].Hz); freqs[i].speed = get_performance(cpunr); if (freqs[i].speed > topspeed) topspeed = freqs[i].speed; performedtests++; report_testrun_progress((75 * performedtests)/totaltests); i++; c = c2; } speedcount = i; details = strdup(" Frequency | Speed \n-----------+---------\n"); sprintf(line,"%i CPU frequency steps supported", speedcount); for (i=0; i < speedcount; i++) details = scatprintf(details, "%9s | %5.1f %%\n", HzToHuman(freqs[i].Hz), 100.0*freqs[i].speed/topspeed); if (nrspeeds == -1) { report_result("cpufreq", INFO, line, details, NULL); nrspeeds = speedcount; } if (nrspeeds != speedcount) report_result("cpufreq", FAIL, "Not all processors support the same number of P states", NULL, NULL); if (speedcount<2) return; /* Now.. sort the frequencies */ delta=1; while (delta) { struct freq tmp; delta = 0; for (i=0; i freqs[i+1].Hz) { tmp = freqs[i]; freqs[i] = freqs[i+1]; freqs[i+1] = tmp; delta = 1; } } } /* now check for 1) increasing HZ and 2) increasing speed */ for (i=0; i freqs[i+1].speed) { char outbuf[4095]; sprintf(outbuf, "Supposedly higher frequency is slower on CPU %i!", cpunr); report_result("cpufreq", FAIL, outbuf, details,NULL); } if (freqs[i].Hz > get_claimed_hz(cpunr) && !warned_PSS) { char outbuf[4096]; warned_PSS = 1; sprintf(outbuf, "Frequency %lu not achievable; _PSS limit of %lu in effect?", freqs[i].Hz, get_claimed_hz(cpunr)); report_result("cpufreq", WARN, outbuf, NULL, NULL); } } free(details); } static void lowest_speed(int cpunr) { char path[PATH_MAX]; char line[4096]; unsigned long Hz; FILE *file; char *c, *c2; int i; unsigned long lowspeed=0; sprintf(path, "/sys/devices/system/cpu/cpu%i/cpufreq/scaling_available_frequencies", cpunr); file = fopen(path, "r"); if (file == NULL) return; if (fgets(line, 4095, file)==NULL) return; fclose(file); c = line; i = 0; while (c && strlen(c)>1) { c2 = strchr(c, ' '); if (c2) { *c2=0; c2++; } else { c2 = NULL; } Hz = strtoull(c, NULL, 10); if (Hz < lowspeed || lowspeed==0) lowspeed = Hz; c = c2; } set_HZ(cpunr, lowspeed); } static void highest_speed(int cpunr) { char path[PATH_MAX]; char line[4096]; uint64_t Hz; FILE *file; char *c, *c2; int i; unsigned long highspeed=0; sprintf(path, "/sys/devices/system/cpu/cpu%i/cpufreq/scaling_available_frequencies", cpunr); file = fopen(path, "r"); if (file == NULL) return; if (fgets(line, 4095, file)==NULL) return; fclose(file); c = line; i = 0; while (c && strlen(c)>1) { c2 = strchr(c, ' '); if (c2) { *c2=0; c2++; } else { c2 = NULL; } Hz = strtoull(c, NULL, 10); if (Hz > highspeed || highspeed==0) highspeed = Hz; c = c2; } set_HZ(cpunr, highspeed); } /* 4) Is BIOS wrongly doing Sw_All P-state coordination across cpus - Change frequency on all CPU to the lowest value - Change frequency on one particular CPU to the highest - If BIOS is doing Sw_All, the last high freq request will not work */ static void do_sw_all_test(void) { DIR *dir; struct dirent *entry; unsigned long highperf, lowperf; int first_cpu_index = -1; dir = opendir("/sys/devices/system/cpu"); if (!dir) { printf("FATAL: cpufreq: sysfs not mounted\n"); return; } do { int cpunr; entry = readdir(dir); if (entry && strlen(entry->d_name)>3) { cpunr = strtoul(entry->d_name+3, NULL, 10); if (first_cpu_index == -1) first_cpu_index = cpunr; lowest_speed(cpunr); } } while (entry); closedir(dir); /* All CPUs at the lowest frequency */ lowperf = 100 * get_performance_repeat(first_cpu_index, 0, 5, GET_PERFORMANCE_MIN) / topspeed; highest_speed(first_cpu_index); highperf = 100 * get_performance_repeat(first_cpu_index, 0, 5, GET_PERFORMANCE_MAX) / topspeed; if (lowperf >= highperf) { char outbuf[4095]; sprintf(outbuf, "Firmware not implementing hardware " "coordination cleanly. Firmware using SW_ALL " "instead?\n"); report_result("cpufreq", FAIL, outbuf, NULL, NULL); } } /* 5) Is BIOS wrongly doing Sw_Any P-state coordination across cpus - Change frequency on all CPU to the lowest value - Change frequency on one particular CPU to the highest - Change frequency on all CPU to the lowest value - If BIOS is doing Sw_Any, the high freq request will not work */ static void do_sw_any_test(void) { DIR *dir; struct dirent *entry; unsigned long highperf, lowperf; int first_cpu_index = -1; dir = opendir("/sys/devices/system/cpu"); if (!dir) { printf("FATAL: cpufreq: sysfs not mounted\n"); return; } do { int cpunr; entry = readdir(dir); if (entry && strlen(entry->d_name)>3) { cpunr = strtoul(entry->d_name+3, NULL, 10); if (first_cpu_index == -1) first_cpu_index = cpunr; lowest_speed(cpunr); } } while (entry); closedir(dir); /* All CPUs at the lowest frequency */ lowperf = 100 * get_performance_repeat(first_cpu_index, 0, 5, GET_PERFORMANCE_MIN) / topspeed; highest_speed(first_cpu_index); dir = opendir("/sys/devices/system/cpu"); if (!dir) { printf("FATAL: cpufreq: sysfs not mounted\n"); return; } do { int cpunr; entry = readdir(dir); if (entry && strlen(entry->d_name)>3) { cpunr = strtoul(entry->d_name+3, NULL, 10); if (cpunr == first_cpu_index) { continue; } lowest_speed(cpunr); } } while (entry); closedir(dir); highperf = 100 * get_performance_repeat(first_cpu_index, 0, 5, GET_PERFORMANCE_MAX) / topspeed; if (lowperf >= highperf) { char outbuf[4095]; sprintf(outbuf, "Firmware not implementing hardware " "coordination cleanly. Firmware using SW_ANY " "instead?\n"); report_result("cpufreq", FAIL, outbuf, NULL, NULL); } } static void check_sw_any(void) { DIR *dir; struct dirent *entry; uint64_t low_perf, high_perf, newhigh_perf; static int once = 0; int max_cpu = 0, i,j; /* First set all processors to their lowest speed */ dir = opendir("/sys/devices/system/cpu"); if (!dir) { printf("FATAL: cpufreq: sysfs not mounted\n"); return; } do { int cpunr; entry = readdir(dir); if (entry && strlen(entry->d_name)>3) { cpunr = strtoul(entry->d_name+3,NULL,10); lowest_speed(cpunr); if (cpunr > max_cpu) max_cpu = cpunr; } } while (entry); closedir(dir); if (max_cpu == 0) return; /* Single processor machine, no point in checking anything */ /* assume that all processors have the same low performance */ low_perf = get_performance(max_cpu); for (i=0; i<= max_cpu; i++) { highest_speed(i); if (!cpu_exists(i)) continue; high_perf = get_performance(i); performedtests++; report_testrun_progress((75 * performedtests)/totaltests); /* now set all the others to low again; sw_any will cause the core in question to now also get the low speed, while hardware max will keep the performance */ for (j=0; j <= max_cpu; j++) if (i!=j) lowest_speed(j); newhigh_perf = get_performance(i); if (high_perf - newhigh_perf > (high_perf - low_perf)/4 && once==0 && (high_perf - low_perf > 20)) { report_result("cpufreq", FAIL, "Processors are set to SW_ANY", NULL, NULL); once++; lowest_speed(i); } performedtests++; report_testrun_progress((75 * performedtests)/totaltests); } if (!once) report_result("cpufreq", PASS, "P-state coordination done by Harware", NULL, NULL); } int main(int argc, char **argv) { DIR *dir; struct dirent *entry; start_test("cpufreq", "CPU frequency scaling tests (1-2 mins)", "For each processor in the system, this test steps through the " "various frequency states (P-states) that the BIOS advertises " "for the processor. For each processor/frequency combination, " "a quick performance value is measured. The test then validates that: \n" " 1) Each processor has the same number of frequency states\n" " 2) Higher advertised frequencies have a higher performance\n" " 3) No duplicate frequency values are reported by the BIOS\n" " 4) Is BIOS wrongly doing Sw_All P-state coordination across cores\n" " 5) Is BIOS wrongly doing Sw_Any P-state coordination across cores\n" ); /* First set all processors to their lowest speed */ dir = opendir("/sys/devices/system/cpu"); if (!dir) { printf("FATAL: cpufreq: sysfs not mounted\n"); return 0; } do { int cpunr; entry = readdir(dir); if (entry && strlen(entry->d_name)>3) { cpunr = strtoul(entry->d_name+3,NULL,10); lowest_speed(cpunr); } } while (entry); closedir(dir); /* then do the benchmark */ dir = opendir("/sys/devices/system/cpu"); if (!dir) { printf("FATAL: cpufreq: sysfs not mounted\n"); return 0; } do { int cpunr; entry = readdir(dir); if (entry && strlen(entry->d_name)>3) { cpunr = strtoul(entry->d_name+3,NULL,10); do_cpu(cpunr); lowest_speed(cpunr); if (no_cpufreq) break; } } while (entry); closedir(dir); /* set everything back to the highest speed again */ dir = opendir("/sys/devices/system/cpu"); if (!dir) { printf("FATAL: cpufreq: sysfs not mounted\n"); return 0; } do { int cpunr; entry = readdir(dir); if (entry && strlen(entry->d_name)>3) { cpunr = strtoul(entry->d_name+3,NULL,10); highest_speed(cpunr); } } while (entry); closedir(dir); if (!no_cpufreq) check_sw_any(); /* * Check for more than one CPU and more than one frequency and * then do the benchmark set 2 */ if (sysconf(_SC_NPROCESSORS_CONF) > 1 && nrspeeds > 1) { do_sw_all_test(); performedtests++; do_sw_any_test(); performedtests++; } else if (nrspeeds > 1) { performedtests += 2; } report_testrun_progress(100); finish_test("cpufreq"); return 0; }