[PATCH v2] Add the Cpuidle test and it is based on the cstates case Only non-X86 can run this test case

Ivan Hu ivan.hu at canonical.com
Mon Aug 18 03:15:12 UTC 2025


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 at 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, &current);
> +
> +               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
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.ubuntu.com/archives/fwts-devel/attachments/20250818/a2b7cae1/attachment-0001.html>


More information about the fwts-devel mailing list