Verify that writing 1 to cpuidle stateX/disable stops that state from gaining usage while other states continue to idle, then restore the previous disable value.
Signed-off-by: Yaxiong Tian <[email protected]> --- tools/testing/selftests/cpuidle/Makefile | 2 +- .../cpuidle/cpuidle_disable_state.py | 133 ++++++++++++++++++ tools/testing/selftests/cpuidle/settings | 2 +- 3 files changed, 135 insertions(+), 2 deletions(-) create mode 100755 tools/testing/selftests/cpuidle/cpuidle_disable_state.py diff --git a/tools/testing/selftests/cpuidle/Makefile b/tools/testing/selftests/cpuidle/Makefile index 116afec7fc0f..0e054199acc5 100644 --- a/tools/testing/selftests/cpuidle/Makefile +++ b/tools/testing/selftests/cpuidle/Makefile @@ -1,7 +1,7 @@ # SPDX-License-Identifier: GPL-2.0 all: -TEST_PROGS := cpuidle_latency_req_qos.py +TEST_PROGS := cpuidle_latency_req_qos.py cpuidle_disable_state.py TEST_FILES := cpuidle_lib.py include ../lib.mk diff --git a/tools/testing/selftests/cpuidle/cpuidle_disable_state.py b/tools/testing/selftests/cpuidle/cpuidle_disable_state.py new file mode 100755 index 000000000000..72ad5278c53c --- /dev/null +++ b/tools/testing/selftests/cpuidle/cpuidle_disable_state.py @@ -0,0 +1,133 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0 +""" +cpuidle: verify user disable of an idle state blocks selection. + +Writes 1 to /sys/devices/system/cpu/cpuN/cpuidle/stateX/disable and checks +that stateX does not gain usage while other states still idle. +""" + +from __future__ import annotations + +import os +import sys +from typing import Dict, Optional + +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) +import cpuidle_lib as cl +import ksft + + +def pick_disable_target(states: Dict[int, cl.IdleState]) -> Optional[int]: + """Prefer deepest user-enabled state; keep at least one shallower enabled.""" + ordered = sorted(states) + if len(ordered) < 2: + return None + for idx in reversed(ordered): + if idx == ordered[0]: + continue + s = states[idx] + if s.disable_path and s.disable == 0: + return idx + return None + + +class DisableStateGuard: + def __init__(self, path: str): + self.path = path + self.prev: Optional[str] = None + + def __enter__(self): + self.prev = open(self.path).read().strip() + with open(self.path, "w") as f: + f.write("1\n") + return self + + def __exit__(self, *args): + if self.prev is None: + return + with open(self.path, "w") as f: + f.write(f"{self.prev}\n") + + +def run_disable_case(desc: str, states: Dict[int, cl.IdleState], + target: int) -> None: + s = states[target] + ksft.print_msg(f"=== {desc} ===") + ksft.print_msg(f"disable state{target}:{s.name} (lat={s.latency_us})") + + with DisableStateGuard(s.disable_path): + ud = cl.measure_idle() + + total = sum(ud.values()) + cl.print_usage_table(states, ud, {target}) + ksft.print_msg(f"total_usage+={total} disabled_usage+={ud[target]}") + + if total <= 0: + ksft.test_result_fail(f"{desc}: too little idle activity ({total})") + return + if ud[target] > 0: + ksft.test_result_fail( + f"{desc}: state{target}({s.name}) usage+={ud[target]} " + "while user-disabled" + ) + return + ksft.test_result_pass(desc) + + +def run_reenable_case(desc: str, states: Dict[int, cl.IdleState], + target: int) -> None: + """After restore, confirm disable sysfs is cleared and idle still works.""" + s = states[target] + ksft.print_msg(f"=== {desc} ===") + ksft.print_msg(f"re-enabled state{target}:{s.name}") + + ud = cl.measure_idle() + total = sum(ud.values()) + cl.print_usage_table(states, ud) + ksft.print_msg(f"total_usage+={total} target_usage+={ud[target]}") + + if total <= 0: + ksft.test_result_fail(f"{desc}: too little idle activity ({total})") + return + cur = int(open(s.disable_path).read()) + if cur != 0: + ksft.test_result_fail(f"{desc}: disable sysfs still {cur}") + return + ksft.test_result_pass(desc) + + +def main() -> None: + ksft.print_header() + + states = cl.require_root_and_cpuidle(min_states=2) + # Need disable sysfs on states we might target. + states = {i: s for i, s in states.items() if s.disable_path} + if len(states) < 2: + ksft.set_plan(1) + ksft.test_result_skip("need at least 2 cpuidle states with disable") + ksft.finished() + + cl.log_states(states, extra=True) + cl.warn_preexisting_qos() + + target = pick_disable_target(states) + if target is None: + ksft.set_plan(1) + ksft.test_result_skip("no user-enabled deep state to disable") + ksft.finished() + + ksft.set_plan(2) + run_disable_case( + f"disable state{target} blocks selection", states, target + ) + states = cl.read_states() + run_reenable_case( + f"restore state{target} disable", states, target + ) + + ksft.finished() + + +if __name__ == "__main__": + main() diff --git a/tools/testing/selftests/cpuidle/settings b/tools/testing/selftests/cpuidle/settings index 1d2078f92780..1bbc754b982b 100644 --- a/tools/testing/selftests/cpuidle/settings +++ b/tools/testing/selftests/cpuidle/settings @@ -1,2 +1,2 @@ -# QoS ceilings x paths, plus no-QoS baselines. +# QoS ceilings x paths, no-QoS baselines, plus disable-state windows. timeout=180 -- 2.43.0

