[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, ¤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
>
>
-------------- 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