| Message ID | 20250808073512.2028662-1-isaac.yang@canonical.com |
|---|---|
| State | New |
| Headers | show |
| Series | [v2] Add the Cpuidle test and it is based on the cstates case Only non-X86 can run this test case | expand |
Hi Isaac, Since this is essentially the same as what the cstate test does, just for non-x86, and we cannot exclude only the non-x86 code in this test, let’s fall back to the cstate test instead of introducing another test that’s almost identical. Cheers, Ivan On Fri, Aug 8, 2025 at 3:35 PM isaacyang <isaac.yang@canonical.com> wrote: > --- > Changes in V2: > - Only allow the non-X86 can run this test case > > > src/Makefile.am | 1 + > src/cpu/cpuidle/cpuidle.c | 321 ++++++++++++++++++++++++++++++++++++++ > 2 files changed, 322 insertions(+) > create mode 100644 src/cpu/cpuidle/cpuidle.c > > diff --git a/src/Makefile.am b/src/Makefile.am > index 749be642..f31ca85d 100644 > --- a/src/Makefile.am > +++ b/src/Makefile.am > @@ -192,6 +192,7 @@ fwts_SOURCES = main.c \ > cpu/virt/virt_vmx.c \ > cpu/maxfreq/maxfreq.c \ > cpu/cpufreq/cpufreq.c \ > + cpu/cpuidle/cpuidle.c \ > cpu/nx/nx.c \ > cpu/msr/msr.c \ > cpu/microcode/microcode.c \ > diff --git a/src/cpu/cpuidle/cpuidle.c b/src/cpu/cpuidle/cpuidle.c > new file mode 100644 > index 00000000..607a4552 > --- /dev/null > +++ b/src/cpu/cpuidle/cpuidle.c > @@ -0,0 +1,321 @@ > +/* > + * Copyright (C) 2006, Intel Corporation > + * Copyright (C) 2010-2025 Canonical > + * > + * This file is was originally from the Linux-ready Firmware Developer Kit > + * > + * This program is free software; you can redistribute it and/or > + * modify it under the terms of the GNU General Public License > + * as published by the Free Software Foundation; either version 2 > + * of the License, or (at your option) any later version. > + * > + * This program 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 General Public License for more details. > + * > + * You should have received a copy of the GNU General Public License > + * along with this program; if not, write to the Free Software > + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA > 02110-1301, USA. > + * > + */ > +#define _GNU_SOURCE /* for sched_setaffinity */ > + > +#include "fwts.h" > + > +#ifndef FWTS_ARCH_INTEL > + > +#define PROCESSOR_PATH "/sys/devices/system/cpu" > + > +#include <stdio.h> > +#include <stdlib.h> > +#include <string.h> > +#include <unistd.h> > +#include <sys/types.h> > +#include <sys/stat.h> > +#include <dirent.h> > +#include <stdint.h> > +#include <ctype.h> > + > +#define MIN_CSTATE 0 > +#define MAX_CSTATE 16 > + > +typedef struct { > + int counts[MAX_CSTATE]; > + bool used[MAX_CSTATE]; > + bool present[MAX_CSTATE]; > + char names[MAX_CSTATE][32]; /* Store actual state names */ > +} fwts_cpuidle_states; > + > +static int statecount = -1; > +static int firstcpu = -1; > + > +static void get_cpuidle_states(char *path, fwts_cpuidle_states *state) > +{ > + struct dirent *entry; > + char filename[PATH_MAX]; > + char *data; > + DIR *dir; > + int i; > + > + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) { > + state->counts[i] = 0; > + state->present[i] = false; > + state->used[i] = false; > + state->names[i][0] = '\0'; /* Initialize names */ > + } > + > + if ((dir = opendir(path)) == NULL) > + return; > + > + while ((entry = readdir(dir)) != NULL) { > + if (strlen(entry->d_name) > 5 && strncmp(entry->d_name, > "state", 5) == 0) { > + long int nr; > + int count; > + > + snprintf(filename, sizeof(filename), "%s/%s/name", > + path, entry->d_name); > + if ((data = fwts_get(filename)) == NULL) > + continue; > + > + /* > + * For non-x86 platforms (ARM, etc.), use > directory name > + * which should be "state0", "state1", "state2", > etc. > + * No need to parse the state name for ARM > platforms. > + */ > + nr = strtol(entry->d_name + 5, NULL, 10); > + > + /* Store the state name */ > + if (nr >= 0 && nr < MAX_CSTATE) { > + strncpy(state->names[nr], data, > sizeof(state->names[nr]) - 1); > + state->names[nr][sizeof(state->names[nr]) > - 1] = '\0'; > + } > + > + free(data); > + > + snprintf(filename, sizeof(filename), "%s/%s/usage", > + path, entry->d_name); > + if ((data = fwts_get(filename)) == NULL) > + continue; > + count = strtoull(data, NULL, 10); > + free(data); > + > + if ((nr >= 0) && (nr < MAX_CSTATE)) { > + state->counts[nr] = count; > + state->present[nr] = true; > + } > + } > + } > + closedir(dir); > +} > + > +#define TOTAL_WAIT_TIME 20 > + > +static void do_cpu(fwts_framework *fw, int nth, int cpus, int cpu, char > *path) > +{ > + fwts_cpuidle_states initial, current; > + int count; > + char buffer[128]; > + char tmp[16]; /* Increased to accommodate "state%d " format */ > + bool keepgoing = true; > + int i; > + > + get_cpuidle_states(path, &initial); > + > + for (i = 0; (i < TOTAL_WAIT_TIME) && keepgoing; i++) { > + int j; > + > + /* Report progress less frequently to reduce overhead */ > + if ((i % 3) == 0) { > + snprintf(buffer, sizeof(buffer),"(CPU %d of %d)", > nth + 1, cpus); > + fwts_progress_message(fw, > + 100 * (i + (TOTAL_WAIT_TIME*nth)) / > + (cpus * TOTAL_WAIT_TIME), buffer); > + } > + > + if ((i & 7) < 4) > + sleep(1); > + else { > + fwts_cpu_benchmark_result result; > + > + if (fwts_cpu_benchmark(fw, cpu, &result) != > FWTS_OK) { > + fwts_failed(fw, LOG_LEVEL_HIGH, > "CPUFailedPerformance", > + "Could not determine the CPU > performance, this " > + "may be due to not being able to > get or set the " > + "CPU affinity for CPU %d.", cpu); > + } > + } > + > + get_cpuidle_states(path, ¤t); > + > + keepgoing = false; > + for (j = MIN_CSTATE; j < MAX_CSTATE; j++) { > + if (initial.counts[j] != current.counts[j]) { > + initial.counts[j] = current.counts[j]; > + initial.used[j] = true; > + } > + if (initial.present[j] && !initial.used[j]) > + keepgoing = true; > + } > + > + /* Early termination: if all states have been reached, we > can stop */ > + if (!keepgoing) { > + break; > + } > + } > + > + *buffer = '\0'; > + if (keepgoing) { > + /* Not a failure, but not a pass either! */ > + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) { > + if (initial.present[i] && !initial.used[i]) { > + if (initial.names[i][0] != '\0') { > + snprintf(tmp, sizeof(tmp), "%s ", > initial.names[i]); > + } else { > + snprintf(tmp, sizeof(tmp), > "state%d ", i); > + } > + strcat(buffer, tmp); > + } > + } > + fwts_log_info(fw, "Processor %d has not reached %s during > tests. " > + "This is not a failure, however it is > not a " > + "complete and thorough test.", cpu, > buffer); > + } else { > + /* Build the result string more efficiently */ > + int pos = 0; > + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) { > + if (initial.present[i] && initial.used[i]) { > + if (initial.names[i][0] != '\0') { > + pos += snprintf(buffer + pos, > sizeof(buffer) - pos, "%s ", initial.names[i]); > + } else { > + pos += snprintf(buffer + pos, > sizeof(buffer) - pos, "state%d ", i); > + } > + } > + } > + fwts_passed(fw, "Processor %d has reached all idle states: > %s", > + cpu, buffer); > + } > + > + count = 0; > + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) > + if (initial.present[i]) > + count++; > + > + if (statecount == -1) > + statecount = count; > + > + if (statecount != count) > + fwts_failed(fw, LOG_LEVEL_HIGH, "CPUNoIdleState", > + "Processor %d is expected to have %d idle states > but has %d.", > + cpu, statecount, count); > + else > + if (firstcpu == -1) > + firstcpu = cpu; > + else > + fwts_passed(fw, "Processor %d has the same number > of idle states as processor %d", > + cpu, firstcpu); > +} > + > +static int cpuidle_test1(fwts_framework *fw) > +{ > + DIR *dir; > + struct dirent *entry; > + int cpus; > + int i; > + bool has_cpuidle = false; > + > + fwts_log_info(fw, > + "This test checks if all processors have the same number > of " > + "idle states, if the idle state counter works and if idle > state " > + "transitions happen."); > + > + if ((dir = opendir(PROCESSOR_PATH)) == NULL) { > + fwts_failed(fw, LOG_LEVEL_HIGH, "CPUNoSysMounted", > + "Cannot open %s: /sys not mounted?", > PROCESSOR_PATH); > + return FWTS_ERROR; > + } > + > + /* How many CPUs are there? */ > + for (cpus = 0; (entry = readdir(dir)) != NULL; ) > + if (entry && > + (strlen(entry->d_name)>3) && > + (strncmp(entry->d_name, "cpu", 3) == 0) && > + (isdigit(entry->d_name[3]))) > + cpus++; > + > + rewinddir(dir); > + > + /* Check if any CPU has cpuidle support */ > + for (i = 0; (cpus > 0) && (entry = readdir(dir)) != NULL; ) { > + if (entry && > + (strlen(entry->d_name)>3) && > + (strncmp(entry->d_name, "cpu", 3) == 0) && > + (isdigit(entry->d_name[3]))) { > + char cpupath[PATH_MAX]; > + DIR *cpuidle_dir; > + > + snprintf(cpupath, sizeof(cpupath), "%s/%s/cpuidle", > + PROCESSOR_PATH, entry->d_name); > + > + if ((cpuidle_dir = opendir(cpupath)) != NULL) { > + has_cpuidle = true; > + closedir(cpuidle_dir); > + break; > + } > + } > + } > + > + /* If no CPU has cpuidle support, check if this is an ACPI system > */ > + if (!has_cpuidle) { > + struct stat statbuf; > + if (!stat("/sys/firmware/acpi", &statbuf)) { > + /* ACPI system without cpuidle - this is a failure > */ > + fwts_failed(fw, LOG_LEVEL_HIGH, > "ACPIWithoutCPUIDLE", > + "ACPI system detected but no CPU idle > states found. " > + "This indicates the CPU idle driver is not > properly " > + "configured or loaded."); > + closedir(dir); > + return FWTS_ERROR; > + } else { > + /* Non-ACPI system without cpuidle - skip the test > */ > + fwts_skipped(fw, "No CPU idle states found on this > system. " > + "This is normal for systems that don't > support " > + "CPU idle power management."); > + closedir(dir); > + return FWTS_SKIP; > + } > + } > + > + rewinddir(dir); > + > + /* Now run the actual tests */ > + for (i = 0; (cpus > 0) && (entry = readdir(dir)) != NULL; ) { > + if (entry && > + (strlen(entry->d_name)>3) && > + (strncmp(entry->d_name, "cpu", 3) == 0) && > + (isdigit(entry->d_name[3]))) { > + char cpupath[PATH_MAX]; > + > + snprintf(cpupath, sizeof(cpupath), "%s/%s/cpuidle", > + PROCESSOR_PATH, entry->d_name); > + do_cpu(fw, i++, cpus, strtoul(entry->d_name+3, > NULL, 10), cpupath); > + } > + } > + > + closedir(dir); > + > + return FWTS_OK; > +} > + > +static fwts_framework_minor_test cpuidle_tests[] = { > + { cpuidle_test1, "Test all CPUs idle states." }, > + { NULL, NULL } > +}; > + > +static fwts_framework_ops cpuidle_ops = { > + .description = "Processor idle state support test.", > + .minor_tests = cpuidle_tests > +}; > + > +FWTS_REGISTER("cpuidle", &cpuidle_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH) > +#endif > -- > 2.34.1 > >
diff --git a/src/Makefile.am b/src/Makefile.am index 749be642..f31ca85d 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -192,6 +192,7 @@ fwts_SOURCES = main.c \ cpu/virt/virt_vmx.c \ cpu/maxfreq/maxfreq.c \ cpu/cpufreq/cpufreq.c \ + cpu/cpuidle/cpuidle.c \ cpu/nx/nx.c \ cpu/msr/msr.c \ cpu/microcode/microcode.c \ diff --git a/src/cpu/cpuidle/cpuidle.c b/src/cpu/cpuidle/cpuidle.c new file mode 100644 index 00000000..607a4552 --- /dev/null +++ b/src/cpu/cpuidle/cpuidle.c @@ -0,0 +1,321 @@ +/* + * Copyright (C) 2006, Intel Corporation + * Copyright (C) 2010-2025 Canonical + * + * This file is was originally from the Linux-ready Firmware Developer Kit + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#define _GNU_SOURCE /* for sched_setaffinity */ + +#include "fwts.h" + +#ifndef FWTS_ARCH_INTEL + +#define PROCESSOR_PATH "/sys/devices/system/cpu" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <dirent.h> +#include <stdint.h> +#include <ctype.h> + +#define MIN_CSTATE 0 +#define MAX_CSTATE 16 + +typedef struct { + int counts[MAX_CSTATE]; + bool used[MAX_CSTATE]; + bool present[MAX_CSTATE]; + char names[MAX_CSTATE][32]; /* Store actual state names */ +} fwts_cpuidle_states; + +static int statecount = -1; +static int firstcpu = -1; + +static void get_cpuidle_states(char *path, fwts_cpuidle_states *state) +{ + struct dirent *entry; + char filename[PATH_MAX]; + char *data; + DIR *dir; + int i; + + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) { + state->counts[i] = 0; + state->present[i] = false; + state->used[i] = false; + state->names[i][0] = '\0'; /* Initialize names */ + } + + if ((dir = opendir(path)) == NULL) + return; + + while ((entry = readdir(dir)) != NULL) { + if (strlen(entry->d_name) > 5 && strncmp(entry->d_name, "state", 5) == 0) { + long int nr; + int count; + + snprintf(filename, sizeof(filename), "%s/%s/name", + path, entry->d_name); + if ((data = fwts_get(filename)) == NULL) + continue; + + /* + * For non-x86 platforms (ARM, etc.), use directory name + * which should be "state0", "state1", "state2", etc. + * No need to parse the state name for ARM platforms. + */ + nr = strtol(entry->d_name + 5, NULL, 10); + + /* Store the state name */ + if (nr >= 0 && nr < MAX_CSTATE) { + strncpy(state->names[nr], data, sizeof(state->names[nr]) - 1); + state->names[nr][sizeof(state->names[nr]) - 1] = '\0'; + } + + free(data); + + snprintf(filename, sizeof(filename), "%s/%s/usage", + path, entry->d_name); + if ((data = fwts_get(filename)) == NULL) + continue; + count = strtoull(data, NULL, 10); + free(data); + + if ((nr >= 0) && (nr < MAX_CSTATE)) { + state->counts[nr] = count; + state->present[nr] = true; + } + } + } + closedir(dir); +} + +#define TOTAL_WAIT_TIME 20 + +static void do_cpu(fwts_framework *fw, int nth, int cpus, int cpu, char *path) +{ + fwts_cpuidle_states initial, current; + int count; + char buffer[128]; + char tmp[16]; /* Increased to accommodate "state%d " format */ + bool keepgoing = true; + int i; + + get_cpuidle_states(path, &initial); + + for (i = 0; (i < TOTAL_WAIT_TIME) && keepgoing; i++) { + int j; + + /* Report progress less frequently to reduce overhead */ + if ((i % 3) == 0) { + snprintf(buffer, sizeof(buffer),"(CPU %d of %d)", nth + 1, cpus); + fwts_progress_message(fw, + 100 * (i + (TOTAL_WAIT_TIME*nth)) / + (cpus * TOTAL_WAIT_TIME), buffer); + } + + if ((i & 7) < 4) + sleep(1); + else { + fwts_cpu_benchmark_result result; + + if (fwts_cpu_benchmark(fw, cpu, &result) != FWTS_OK) { + fwts_failed(fw, LOG_LEVEL_HIGH, "CPUFailedPerformance", + "Could not determine the CPU performance, this " + "may be due to not being able to get or set the " + "CPU affinity for CPU %d.", cpu); + } + } + + get_cpuidle_states(path, ¤t); + + keepgoing = false; + for (j = MIN_CSTATE; j < MAX_CSTATE; j++) { + if (initial.counts[j] != current.counts[j]) { + initial.counts[j] = current.counts[j]; + initial.used[j] = true; + } + if (initial.present[j] && !initial.used[j]) + keepgoing = true; + } + + /* Early termination: if all states have been reached, we can stop */ + if (!keepgoing) { + break; + } + } + + *buffer = '\0'; + if (keepgoing) { + /* Not a failure, but not a pass either! */ + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) { + if (initial.present[i] && !initial.used[i]) { + if (initial.names[i][0] != '\0') { + snprintf(tmp, sizeof(tmp), "%s ", initial.names[i]); + } else { + snprintf(tmp, sizeof(tmp), "state%d ", i); + } + strcat(buffer, tmp); + } + } + fwts_log_info(fw, "Processor %d has not reached %s during tests. " + "This is not a failure, however it is not a " + "complete and thorough test.", cpu, buffer); + } else { + /* Build the result string more efficiently */ + int pos = 0; + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) { + if (initial.present[i] && initial.used[i]) { + if (initial.names[i][0] != '\0') { + pos += snprintf(buffer + pos, sizeof(buffer) - pos, "%s ", initial.names[i]); + } else { + pos += snprintf(buffer + pos, sizeof(buffer) - pos, "state%d ", i); + } + } + } + fwts_passed(fw, "Processor %d has reached all idle states: %s", + cpu, buffer); + } + + count = 0; + for (i = MIN_CSTATE; i < MAX_CSTATE; i++) + if (initial.present[i]) + count++; + + if (statecount == -1) + statecount = count; + + if (statecount != count) + fwts_failed(fw, LOG_LEVEL_HIGH, "CPUNoIdleState", + "Processor %d is expected to have %d idle states but has %d.", + cpu, statecount, count); + else + if (firstcpu == -1) + firstcpu = cpu; + else + fwts_passed(fw, "Processor %d has the same number of idle states as processor %d", + cpu, firstcpu); +} + +static int cpuidle_test1(fwts_framework *fw) +{ + DIR *dir; + struct dirent *entry; + int cpus; + int i; + bool has_cpuidle = false; + + fwts_log_info(fw, + "This test checks if all processors have the same number of " + "idle states, if the idle state counter works and if idle state " + "transitions happen."); + + if ((dir = opendir(PROCESSOR_PATH)) == NULL) { + fwts_failed(fw, LOG_LEVEL_HIGH, "CPUNoSysMounted", + "Cannot open %s: /sys not mounted?", PROCESSOR_PATH); + return FWTS_ERROR; + } + + /* How many CPUs are there? */ + for (cpus = 0; (entry = readdir(dir)) != NULL; ) + if (entry && + (strlen(entry->d_name)>3) && + (strncmp(entry->d_name, "cpu", 3) == 0) && + (isdigit(entry->d_name[3]))) + cpus++; + + rewinddir(dir); + + /* Check if any CPU has cpuidle support */ + for (i = 0; (cpus > 0) && (entry = readdir(dir)) != NULL; ) { + if (entry && + (strlen(entry->d_name)>3) && + (strncmp(entry->d_name, "cpu", 3) == 0) && + (isdigit(entry->d_name[3]))) { + char cpupath[PATH_MAX]; + DIR *cpuidle_dir; + + snprintf(cpupath, sizeof(cpupath), "%s/%s/cpuidle", + PROCESSOR_PATH, entry->d_name); + + if ((cpuidle_dir = opendir(cpupath)) != NULL) { + has_cpuidle = true; + closedir(cpuidle_dir); + break; + } + } + } + + /* If no CPU has cpuidle support, check if this is an ACPI system */ + if (!has_cpuidle) { + struct stat statbuf; + if (!stat("/sys/firmware/acpi", &statbuf)) { + /* ACPI system without cpuidle - this is a failure */ + fwts_failed(fw, LOG_LEVEL_HIGH, "ACPIWithoutCPUIDLE", + "ACPI system detected but no CPU idle states found. " + "This indicates the CPU idle driver is not properly " + "configured or loaded."); + closedir(dir); + return FWTS_ERROR; + } else { + /* Non-ACPI system without cpuidle - skip the test */ + fwts_skipped(fw, "No CPU idle states found on this system. " + "This is normal for systems that don't support " + "CPU idle power management."); + closedir(dir); + return FWTS_SKIP; + } + } + + rewinddir(dir); + + /* Now run the actual tests */ + for (i = 0; (cpus > 0) && (entry = readdir(dir)) != NULL; ) { + if (entry && + (strlen(entry->d_name)>3) && + (strncmp(entry->d_name, "cpu", 3) == 0) && + (isdigit(entry->d_name[3]))) { + char cpupath[PATH_MAX]; + + snprintf(cpupath, sizeof(cpupath), "%s/%s/cpuidle", + PROCESSOR_PATH, entry->d_name); + do_cpu(fw, i++, cpus, strtoul(entry->d_name+3, NULL, 10), cpupath); + } + } + + closedir(dir); + + return FWTS_OK; +} + +static fwts_framework_minor_test cpuidle_tests[] = { + { cpuidle_test1, "Test all CPUs idle states." }, + { NULL, NULL } +}; + +static fwts_framework_ops cpuidle_ops = { + .description = "Processor idle state support test.", + .minor_tests = cpuidle_tests +}; + +FWTS_REGISTER("cpuidle", &cpuidle_ops, FWTS_TEST_ANYTIME, FWTS_FLAG_BATCH) +#endif