diff mbox series

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

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

Commit Message

isaacyang Aug. 8, 2025, 7:35 a.m. UTC
---
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

Comments

Ivan Hu Aug. 18, 2025, 3:15 a.m. UTC | #1
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, &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
>
>
diff mbox series

Patch

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