* [LTP] [PATCH v2 1/3] lib: add tst_test.needs_debugfs flag
2026-09-07 12:10 [LTP] [PATCH v2 0/3] pmc_core: cover the PkgC LTR blocker counters Piotr Kubaj
@ 2026-09-07 12:10 ` Piotr Kubaj
2026-09-07 12:10 ` [LTP] [PATCH v2 2/3] pmc_core: add test for pkgc_ltr_blocker_show Piotr Kubaj
2026-09-07 12:10 ` [LTP] [PATCH v2 3/3] pmc_core: add ltr_counter test Piotr Kubaj
2 siblings, 0 replies; 5+ messages in thread
From: Piotr Kubaj @ 2026-09-07 12:10 UTC (permalink / raw)
To: ltp; +Cc: helena.anna.dubel, tomasz.ossowski, rafael.j.wysocki,
daniel.niestepski
Tests that read debugfs files have to check that debugfs is mounted at
/sys/kernel/debug and mount it when it is not. The mount point exists as
a plain sysfs directory whenever CONFIG_DEBUG_FS is enabled, whether or
not debugfs is mounted on it, so a test that only checks that the path is
there reports the platform as unsupported when debugfs simply is not
mounted, and each test ends up carrying the same statfs() and mount()
dance.
Move it into the library: with tst_test.needs_debugfs set the library
mounts debugfs during setup, exits with TCONF if it cannot, and unmounts
it at the end of the test if it was the one that mounted it. Add
TST_DEBUGFS_PATH so that tests build their paths from the library
constant, register the flag in the metadata parser, and add a
lib/newlib_tests self-test that covers both the mounted and the unmounted
case.
Signed-off-by: Piotr Kubaj <piotr.kubaj@intel.com>
---
doc/developers/writing_tests.rst | 3 +++
include/tst_fs.h | 4 +++
include/tst_test.h | 8 ++++++
lib/newlib_tests/.gitignore | 1 +
lib/newlib_tests/runtest.sh | 1 +
lib/newlib_tests/tst_needs_debugfs.c | 37 ++++++++++++++++++++++++++++
lib/tst_test.c | 35 ++++++++++++++++++++++++++
metadata/metaparse.c | 1 +
8 files changed, 90 insertions(+)
create mode 100644 lib/newlib_tests/tst_needs_debugfs.c
diff --git a/doc/developers/writing_tests.rst b/doc/developers/writing_tests.rst
index 0edf101ff..80a654e9b 100644
--- a/doc/developers/writing_tests.rst
+++ b/doc/developers/writing_tests.rst
@@ -440,6 +440,9 @@ LTP C And Shell Test API Comparison
* - .needs_cmds
- TST_NEEDS_CMDS
+ * - .needs_debugfs
+ - \-
+
* - .needs_devfs
- \-
diff --git a/include/tst_fs.h b/include/tst_fs.h
index c55f8a646..68176297b 100644
--- a/include/tst_fs.h
+++ b/include/tst_fs.h
@@ -8,6 +8,7 @@
/* man 2 statfs or kernel-source/include/uapi/linux/magic.h */
#define TST_BTRFS_MAGIC 0x9123683E
+#define TST_DEBUGFS_MAGIC 0x64626720
#define TST_NFS_MAGIC 0x6969
#define TST_RAMFS_MAGIC 0x858458f6
#define TST_TMPFS_MAGIC 0x01021994
@@ -58,6 +59,9 @@ enum {
#define OVL_WORK OVL_BASE_MNTPOINT"/work"
#define OVL_MNT OVL_BASE_MNTPOINT"/ovl"
+/* Where debugfs is mounted by the tst_test.needs_debugfs flag */
+#define TST_DEBUGFS_PATH "/sys/kernel/debug"
+
/*
* @path: path is the pathname of any file within the mounted file system
* @mult: mult should be TST_KB, TST_MB or TST_GB
diff --git a/include/tst_test.h b/include/tst_test.h
index 7d03014d4..d3a49776c 100644
--- a/include/tst_test.h
+++ b/include/tst_test.h
@@ -358,6 +358,13 @@ struct tst_fs {
* needed for tests that need to create device files since tmpfs
* at /tmp is usually mounted with 'nodev' option.
*
+ * @needs_debugfs: If set debugfs is mounted at TST_DEBUGFS_PATH i.e.
+ * /sys/kernel/debug, unless it is mounted there already, and
+ * the test exits with TCONF if it cannot be. The library
+ * unmounts it at the end of the test if it mounted it. Note
+ * that both mounting debugfs and reading its content require
+ * root, so tests using this also set tst_test.needs_root.
+ *
* @restore_wallclock: Saves wall clock at the start of the test and restores
* it at the end with the help of monotonic timers.
* Testcases that modify system wallclock use this to
@@ -573,6 +580,7 @@ struct tst_fs {
unsigned int child_needs_reinit:1;
unsigned int runs_script:1;
unsigned int needs_devfs:1;
+ unsigned int needs_debugfs:1;
unsigned int restore_wallclock:1;
unsigned int all_filesystems:1;
diff --git a/lib/newlib_tests/.gitignore b/lib/newlib_tests/.gitignore
index b85163824..7237723bf 100644
--- a/lib/newlib_tests/.gitignore
+++ b/lib/newlib_tests/.gitignore
@@ -60,6 +60,7 @@ tst_needs_cmds05
tst_needs_cmds06
tst_needs_cmds07
tst_needs_cmds08
+tst_needs_debugfs
test_runtime01
test_runtime02
test_children_cleanup
diff --git a/lib/newlib_tests/runtest.sh b/lib/newlib_tests/runtest.sh
index 71808ef8b..1a51d8b7a 100755
--- a/lib/newlib_tests/runtest.sh
+++ b/lib/newlib_tests/runtest.sh
@@ -27,6 +27,7 @@ tst_expiration_timer
tst_filesystems01
tst_fuzzy_sync0[1-3]
tst_needs_cmds0[1-36-8]
+tst_needs_debugfs
tst_res_hexd
tst_safe_sscanf
tst_strstatus}"
diff --git a/lib/newlib_tests/tst_needs_debugfs.c b/lib/newlib_tests/tst_needs_debugfs.c
new file mode 100644
index 000000000..fd6adf7a5
--- /dev/null
+++ b/lib/newlib_tests/tst_needs_debugfs.c
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2026 Piotr Kubaj <piotr.kubaj@intel.com>
+ */
+
+/*
+ * Test that tst_test.needs_debugfs gets debugfs mounted for the test.
+ *
+ * Run it both with debugfs mounted and with it unmounted, it has to pass either
+ * way. In the latter case the library reports mounting debugfs and has to leave
+ * it unmounted once the test is over.
+ */
+
+#include <sys/vfs.h>
+#include "tst_test.h"
+
+static void run(void)
+{
+ struct statfs sfs;
+
+ SAFE_STATFS(TST_DEBUGFS_PATH, &sfs);
+
+ if (sfs.f_type == TST_DEBUGFS_MAGIC)
+ tst_res(TPASS, "debugfs is mounted at %s", TST_DEBUGFS_PATH);
+ else
+ tst_res(TFAIL, "debugfs is not mounted at %s", TST_DEBUGFS_PATH);
+}
+
+static struct tst_test test = {
+ .needs_root = 1,
+ .needs_debugfs = 1,
+ .needs_kconfigs = (const char *const []) {
+ "CONFIG_DEBUG_FS",
+ NULL
+ },
+ .test_all = run,
+};
diff --git a/lib/tst_test.c b/lib/tst_test.c
index 166e0f672..d21eb382a 100644
--- a/lib/tst_test.c
+++ b/lib/tst_test.c
@@ -15,6 +15,7 @@
#include <errno.h>
#include <sys/mount.h>
#include <sys/types.h>
+#include <sys/vfs.h>
#include <sys/utsname.h>
#include <sys/wait.h>
#include <math.h>
@@ -82,6 +83,7 @@ struct context {
tst_atomic_t abort_flag;
uint32_t mntpoint_mounted:1;
uint32_t ovl_mounted:1;
+ uint32_t debugfs_mounted:1;
uint32_t tdebug;
};
@@ -1210,6 +1212,31 @@ static void prepare_and_mount_dev_fs(const char *mntpoint)
}
}
+static void mount_debugfs(void)
+{
+ struct statfs sfs;
+
+ /*
+ * TST_DEBUGFS_PATH exists as a plain sysfs directory whenever
+ * CONFIG_DEBUG_FS is enabled, whether or not debugfs is mounted on it,
+ * so the filesystem type has to be checked rather than the directory
+ * being present.
+ */
+ if (statfs(TST_DEBUGFS_PATH, &sfs))
+ tst_brk(TCONF | TERRNO, "Can't statfs %s", TST_DEBUGFS_PATH);
+
+ if (sfs.f_type == TST_DEBUGFS_MAGIC)
+ return;
+
+ if (mount("debugfs", TST_DEBUGFS_PATH, "debugfs", 0, NULL)) {
+ tst_brk(TCONF | TERRNO, "Can't mount debugfs at %s",
+ TST_DEBUGFS_PATH);
+ }
+
+ tst_res(TINFO, "Mounted debugfs at %s", TST_DEBUGFS_PATH);
+ context->debugfs_mounted = 1;
+}
+
static void prepare_and_mount_hugetlb_fs(void)
{
if (access(PATH_MM_HUGEPAGES, F_OK))
@@ -1546,6 +1573,9 @@ static void do_setup(int argc, char *argv[])
"Two or more of needs_{rofs, devfs, device, hugetlbfs} are set");
}
+ if (tst_test->needs_debugfs)
+ mount_debugfs();
+
if (tst_test->needs_devfs)
prepare_and_mount_dev_fs(tst_test->mntpoint);
@@ -1657,6 +1687,11 @@ static void do_cleanup(void)
if (context->mntpoint_mounted)
tst_umount(tst_test->mntpoint);
+ if (context->debugfs_mounted) {
+ tst_umount(TST_DEBUGFS_PATH);
+ context->debugfs_mounted = 0;
+ }
+
if (tst_test->needs_device && tdev.dev)
tst_release_device(tdev.dev);
diff --git a/metadata/metaparse.c b/metadata/metaparse.c
index cb141c383..7f2c09037 100644
--- a/metadata/metaparse.c
+++ b/metadata/metaparse.c
@@ -1169,6 +1169,7 @@ static struct typemap tst_test_typemap[] = {
{.id = "child_needs_reinit", .type = DATA_BOOL},
{.id = "runs_script", .type = DATA_BOOL},
{.id = "needs_devfs", .type = DATA_BOOL},
+ {.id = "needs_debugfs", .type = DATA_BOOL},
{.id = "restore_wallclock", .type = DATA_BOOL},
{.id = "all_filesystems", .type = DATA_BOOL},
{.id = "skip_in_lockdown", .type = DATA_BOOL},
--
2.47.3
---------------------------------------------------------------------
Intel Technology Poland sp. z o.o.
ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 5+ messages in thread* [LTP] [PATCH v2 2/3] pmc_core: add test for pkgc_ltr_blocker_show
2026-09-07 12:10 [LTP] [PATCH v2 0/3] pmc_core: cover the PkgC LTR blocker counters Piotr Kubaj
2026-09-07 12:10 ` [LTP] [PATCH v2 1/3] lib: add tst_test.needs_debugfs flag Piotr Kubaj
@ 2026-09-07 12:10 ` Piotr Kubaj
2026-09-07 12:10 ` [LTP] [PATCH v2 3/3] pmc_core: add ltr_counter test Piotr Kubaj
2 siblings, 0 replies; 5+ messages in thread
From: Piotr Kubaj @ 2026-09-07 12:10 UTC (permalink / raw)
To: ltp; +Cc: helena.anna.dubel, tomasz.ossowski, rafael.j.wysocki,
daniel.niestepski
Add coverage for the pkgc_ltr_blocker_show debugfs file added in kernel
commit 38c79dd63b72 ("platform/x86/intel/pmc: Enable PkgC LTR blocking
counter") and first released in v7.2. The counters report how many times
an LTR posted by each source blocked a package C-state entry, and so far
nothing checked that the driver exports them correctly.
The counter set is platform specific, so instead of a hardcoded list of
names the test samples the file twice and checks that it can be read in
full, that every line is a PKGC_PREVENT_LTR_<SOURCE> token followed by
exactly one value that fits in u32, that the counter set does not change
between the reads, and that no counter advances by more entries than the
package could have attempted over the interval.
The last check is the one that catches a telemetry region read at the
wrong offset. The values wrap, so the advance is evaluated modulo 2^32
and bounded by the time the counter had to advance over, widened by how
stale the first sample can be, because the PMC refreshes the telemetry
region behind the counters asynchronously. An arbitrary u32 read out of
the wrong offset overshoots that bound by orders of magnitude.
Tested on Nova Lake running 7.3.0-rc1.
Signed-off-by: Piotr Kubaj <piotr.kubaj@intel.com>
---
runtest/power_management_tests | 1 +
testcases/kernel/power_management/.gitignore | 1 +
.../power_management/pkgc_ltr_blocker_show.c | 294 ++++++++++++++++++
3 files changed, 296 insertions(+)
create mode 100644 testcases/kernel/power_management/pkgc_ltr_blocker_show.c
diff --git a/runtest/power_management_tests b/runtest/power_management_tests
index 5a22283d6..7708d3fd4 100644
--- a/runtest/power_management_tests
+++ b/runtest/power_management_tests
@@ -2,6 +2,7 @@
cpufreq_intel cpufreq_intel
rfim01 rfim01
high_freq_hwp_cap_cppc high_freq_hwp_cap_cppc
+pkgc_ltr_blocker_show pkgc_ltr_blocker_show
runpwtests03 runpwtests03.sh
runpwtests04 runpwtests04.sh
runpwtests06 runpwtests06.sh
diff --git a/testcases/kernel/power_management/.gitignore b/testcases/kernel/power_management/.gitignore
index 766f07901..b96084eca 100644
--- a/testcases/kernel/power_management/.gitignore
+++ b/testcases/kernel/power_management/.gitignore
@@ -1,3 +1,4 @@
high_freq_hwp_cap_cppc
cpufreq_intel
rfim01
+pkgc_ltr_blocker_show
diff --git a/testcases/kernel/power_management/pkgc_ltr_blocker_show.c b/testcases/kernel/power_management/pkgc_ltr_blocker_show.c
new file mode 100644
index 000000000..ef6b2110c
--- /dev/null
+++ b/testcases/kernel/power_management/pkgc_ltr_blocker_show.c
@@ -0,0 +1,294 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * Copyright (C) 2026 Piotr Kubaj <piotr.kubaj@intel.com>
+ */
+
+/*\
+ * Verify that pkgc_ltr_blocker_show reports well-formed package C-state LTR
+ * blocker counters that advance at a plausible rate.
+ *
+ * The intel_pmc_core driver prints one line per counter as "%-30s %-30u",
+ * where the name is a PKGC_PREVENT_LTR_<SOURCE> token and the value counts how
+ * many times an LTR posted by <SOURCE> blocked a package C-state entry.
+ *
+ * The counter set is platform specific, so instead of checking a hardcoded
+ * list the test samples the file twice and verifies that
+ *
+ * - the file can be read in full, so that a failing telemetry read in the
+ * driver is not mistaken for a short counter list,
+ * - every line is a PKGC_PREVENT_LTR_<SOURCE> token followed by exactly one
+ * unsigned value that fits in u32,
+ * - the set of counters does not change between the two reads,
+ * - no counter advances by more entries than the package could have attempted
+ * over the interval.
+ *
+ * The last check is the one that catches a telemetry region read at the wrong
+ * offset. The values are u32 and wrap, so the advance has to be evaluated
+ * modulo 2^32, and a wrap is then indistinguishable from a bogus read unless
+ * the delta is bounded by the time the counter had to advance over. An
+ * arbitrary u32 read out of the wrong offset averages 2^31, orders of
+ * magnitude past what the bound allows.
+ *
+ * The debugfs file is only created for a PMC that exposes a package C-state
+ * telemetry endpoint (pc_guid in the driver's pmc_dev_info), so on platforms
+ * without one the test is not applicable.
+ *
+ * The test needs root because /sys/kernel/debug is mode 0700 and owned by
+ * root, so the counter file cannot be opened otherwise, and because the test
+ * library mounts debugfs if it is not mounted already.
+ */
+
+#include <time.h>
+#include "tst_clocks.h"
+#include "tst_safe_stdio.h"
+#include "tst_test.h"
+#include "tst_timer.h"
+
+#define PATH TST_DEBUGFS_PATH "/pmc_core/pkgc_ltr_blocker_show"
+#define PREFIX "PKGC_PREVENT_LTR_"
+
+/* Nova Lake reports 5 counters, leave room for future platforms */
+#define MAX_COUNTERS 64
+#define NAME_LEN 64
+
+/*
+ * Cap, in seconds, on how long to poll for a second sample that differs from
+ * the first one. On an idle Nova Lake the counters advance in bursts a few
+ * seconds apart, because a blocked entry needs the package to attempt a C-state
+ * entry in the first place, so a window of a few seconds is what it takes for
+ * the delta check to have anything to look at. TST_RETRY_FN_EXP_BACKOFF() ends
+ * up sleeping about twice the cap in total, hence the modest value here.
+ */
+#define MAX_SAMPLE_DELAY 4
+
+/*
+ * Allowance for how stale the first sample can be. The PMC refreshes the
+ * telemetry region behind these counters asynchronously, so the first read can
+ * land just before a refresh and return a snapshot that is almost a whole
+ * refresh period old. The retry then observes a period worth of blocked entries
+ * after only microseconds of measured time, which the measured interval on its
+ * own does not account for.
+ *
+ * The allowance is the same window the test is willing to poll a refresh out
+ * of, which on Nova Lake is several times the observed refresh period.
+ */
+#define REFRESH_AGE_US (MAX_SAMPLE_DELAY * 1000000ULL)
+
+/*
+ * Blocked entries per microsecond the bound allows. Each count stands for one
+ * package C-state entry that was blocked, and entries can only be attempted as
+ * fast as the package can cycle between idle and running, which takes
+ * microseconds. Ten per microsecond is therefore already past what the hardware
+ * can produce - the counters were observed advancing by about 6 per second on
+ * an idle Nova Lake - and the headroom costs the check nothing, because the
+ * bogus read it exists to catch overshoots it by orders of magnitude anyway.
+ */
+#define MAX_BLOCKS_PER_US 10
+
+struct snapshot {
+ char names[MAX_COUNTERS][NAME_LEN];
+ uint32_t values[MAX_COUNTERS];
+ int cnt;
+};
+
+static struct snapshot first, second;
+
+/*
+ * Largest advance a counter can plausibly show over two samples taken
+ * @elapsed_us microseconds apart.
+ *
+ * The interval the counter had to advance over is the measured one widened by
+ * REFRESH_AGE_US, because the sampled values come from a telemetry snapshot
+ * that is already up to a refresh period old, not from the instants the file
+ * was read.
+ */
+static unsigned long long max_plausible_delta(unsigned long long elapsed_us)
+{
+ return (elapsed_us + REFRESH_AGE_US) * MAX_BLOCKS_PER_US;
+}
+
+static void read_snapshot(struct snapshot *snap)
+{
+ char line[256];
+ FILE *fp;
+
+ snap->cnt = 0;
+ fp = SAFE_FOPEN(PATH, "r");
+
+ while (fgets(line, sizeof(line), fp)) {
+ char name[NAME_LEN], value[NAME_LEN], extra;
+ unsigned long long parsed;
+
+ line[strcspn(line, "\n")] = '\0';
+
+ /*
+ * The value is scanned as a string rather than with %u so that
+ * a signed or out of range value is rejected instead of being
+ * quietly converted. The trailing %c rejects a third token; the
+ * space in front of it skips the padding the driver emits.
+ */
+ if (sscanf(line, "%63s %63s %c", name, value, &extra) != 2) {
+ tst_res(TFAIL, "malformed counter line: '%s'", line);
+ continue;
+ }
+
+ if (strncmp(name, PREFIX, sizeof(PREFIX) - 1) ||
+ !name[sizeof(PREFIX) - 1]) {
+ tst_res(TFAIL, "counter '%s' is not a " PREFIX "<SOURCE> token",
+ name);
+ continue;
+ }
+
+ if (value[strspn(value, "0123456789")]) {
+ tst_res(TFAIL, "counter '%s' has a non-numeric value '%s'",
+ name, value);
+ continue;
+ }
+
+ parsed = strtoull(value, NULL, 10);
+
+ if (parsed > UINT32_MAX) {
+ tst_res(TFAIL, "counter '%s' value '%s' does not fit in u32",
+ name, value);
+ continue;
+ }
+
+ if (snap->cnt == MAX_COUNTERS)
+ tst_brk(TBROK, "more than %d counters reported", MAX_COUNTERS);
+
+ strcpy(snap->names[snap->cnt], name);
+ snap->values[snap->cnt] = parsed;
+ snap->cnt++;
+ }
+
+ /*
+ * A telemetry read that fails in the driver aborts the seq_file read
+ * instead of ending it at EOF, so without this the counters lost to the
+ * error would look like a platform reporting fewer of them.
+ */
+ if (ferror(fp))
+ tst_brk(TFAIL, "reading " PATH " failed after %d counters", snap->cnt);
+
+ SAFE_FCLOSE(fp);
+}
+
+static void setup(void)
+{
+ if (access(PATH, R_OK))
+ tst_brk(TCONF | TERRNO, "%s not available", PATH);
+}
+
+/*
+ * Take the second sample, reporting whether it differs from the first one.
+ *
+ * A blocked entry is a sporadic event and the counters are backed by a PMT
+ * telemetry region that the PMC refreshes asynchronously, so the second sample
+ * is retried until it observes a change instead of being taken after a fixed
+ * delay. A differing counter count also counts as a difference, so that the
+ * mismatch is reported rather than polled over.
+ */
+static int second_sample_differs(void)
+{
+ read_snapshot(&second);
+
+ if (first.cnt != second.cnt)
+ return 1;
+
+ for (int i = 0; i < first.cnt; i++) {
+ if (first.values[i] != second.values[i])
+ return 1;
+ }
+
+ return 0;
+}
+
+static void run(void)
+{
+ struct timespec start, end;
+ unsigned long long elapsed_us, max_delta;
+ int differs, fails = 0;
+
+ tst_clock_gettime(CLOCK_MONOTONIC, &start);
+ read_snapshot(&first);
+
+ if (!first.cnt)
+ tst_brk(TFAIL, "no LTR blocker counters reported");
+
+ differs = TST_RETRY_FN_EXP_BACKOFF(second_sample_differs(),
+ TST_RETVAL_NOTNULL, MAX_SAMPLE_DELAY);
+
+ tst_clock_gettime(CLOCK_MONOTONIC, &end);
+
+ elapsed_us = tst_timespec_diff_us(end, start);
+ max_delta = max_plausible_delta(elapsed_us);
+
+ if (!differs) {
+ tst_res(TINFO, "no counter changed over %lluus, no entry was blocked",
+ elapsed_us);
+ }
+
+ if (first.cnt != second.cnt) {
+ tst_brk(TFAIL, "counter count changed between reads: %d -> %d",
+ first.cnt, second.cnt);
+ }
+
+ for (int i = 0; i < first.cnt; i++) {
+ uint32_t delta;
+
+ if (strcmp(first.names[i], second.names[i])) {
+ tst_res(TFAIL, "counter %d renamed between reads: %s -> %s",
+ i, first.names[i], second.names[i]);
+ fails++;
+ continue;
+ }
+
+ tst_res(TDEBUG, "%s: %u -> %u", first.names[i],
+ first.values[i], second.values[i]);
+
+ /*
+ * The values are u32 and wrap, so the advance has to be
+ * computed with modular arithmetic rather than by comparing the
+ * second value against the first one. A delta of 0 is normal:
+ * it means no entry was blocked by that source over the
+ * interval, and most counters on an idle system read 0
+ * permanently.
+ */
+ delta = second.values[i] - first.values[i];
+
+ if (delta > max_delta) {
+ tst_res(TFAIL,
+ "%s advanced by %u over %lluus, at most %llu plausible: %u -> %u",
+ first.names[i], delta, elapsed_us, max_delta,
+ first.values[i], second.values[i]);
+ fails++;
+ }
+ }
+
+ if (!fails) {
+ tst_res(TPASS, "%d LTR blocker counters well-formed, advancing plausibly",
+ first.cnt);
+ }
+}
+
+static struct tst_test test = {
+ .min_kver = "7.2",
+ .needs_root = 1,
+ .needs_debugfs = 1,
+ .needs_kconfigs = (const char *const []) {
+ "CONFIG_DEBUG_FS",
+ "CONFIG_INTEL_PMC_CORE",
+ NULL
+ },
+ .supported_archs = (const char *const []) {
+ "x86",
+ "x86_64",
+ NULL
+ },
+ .tags = (const struct tst_tag[]) {
+ {"linux-git", "38c79dd63b72e36919ef097d4e5025ca0fa17f34"},
+ {}
+ },
+ .setup = setup,
+ .test_all = run
+};
--
2.47.3
---------------------------------------------------------------------
Intel Technology Poland sp. z o.o.
ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 5+ messages in thread* [LTP] [PATCH v2 3/3] pmc_core: add ltr_counter test
2026-09-07 12:10 [LTP] [PATCH v2 0/3] pmc_core: cover the PkgC LTR blocker counters Piotr Kubaj
2026-09-07 12:10 ` [LTP] [PATCH v2 1/3] lib: add tst_test.needs_debugfs flag Piotr Kubaj
2026-09-07 12:10 ` [LTP] [PATCH v2 2/3] pmc_core: add test for pkgc_ltr_blocker_show Piotr Kubaj
@ 2026-09-07 12:10 ` Piotr Kubaj
2026-09-09 13:29 ` Kubaj, Piotr
2 siblings, 1 reply; 5+ messages in thread
From: Piotr Kubaj @ 2026-09-07 12:10 UTC (permalink / raw)
To: ltp; +Cc: helena.anna.dubel, tomasz.ossowski, rafael.j.wysocki,
daniel.niestepski
Add a test for the invariant behind the pkgc_ltr_blocker_show counters:
they only advance while a package C-state entry can be attempted, which
needs every CPU in the package to be idle. A counter that keeps advancing
with every CPU busy is not counting blocked package C-state entries,
which is what a telemetry region read at the wrong offset looks like.
The test first lets the system idle until it sees a counter advance, so
that a counter stuck at a constant value cannot pass the load phase for
free, and then pins a busy loop to every CPU it may run on and requires
the counters to hold still.
The counters are backed by a PMT telemetry region that the PMC refreshes
asynchronously and in bursts - roughly six seconds apart on Nova Lake -
so the load phase waits for the counters to go quiet rather than sampling
them after a fixed delay. Entries blocked before the load took hold
surface in a later refresh, while a counter that is not tracking blocked
entries never goes quiet at all and runs the window out.
Tested on Nova Lake running 7.3.0-rc1.
Signed-off-by: Piotr Kubaj <piotr.kubaj@intel.com>
---
runtest/power_management_tests | 1 +
testcases/kernel/power_management/.gitignore | 1 +
.../kernel/power_management/ltr_counter.c | 358 ++++++++++++++++++
3 files changed, 360 insertions(+)
create mode 100644 testcases/kernel/power_management/ltr_counter.c
diff --git a/runtest/power_management_tests b/runtest/power_management_tests
index 7708d3fd4..c4f661257 100644
--- a/runtest/power_management_tests
+++ b/runtest/power_management_tests
@@ -3,6 +3,7 @@ cpufreq_intel cpufreq_intel
rfim01 rfim01
high_freq_hwp_cap_cppc high_freq_hwp_cap_cppc
pkgc_ltr_blocker_show pkgc_ltr_blocker_show
+ltr_counter ltr_counter
runpwtests03 runpwtests03.sh
runpwtests04 runpwtests04.sh
runpwtests06 runpwtests06.sh
diff --git a/testcases/kernel/power_management/.gitignore b/testcases/kernel/power_management/.gitignore
index b96084eca..f54cec368 100644
--- a/testcases/kernel/power_management/.gitignore
+++ b/testcases/kernel/power_management/.gitignore
@@ -2,3 +2,4 @@ high_freq_hwp_cap_cppc
cpufreq_intel
rfim01
pkgc_ltr_blocker_show
+ltr_counter
diff --git a/testcases/kernel/power_management/ltr_counter.c b/testcases/kernel/power_management/ltr_counter.c
new file mode 100644
index 000000000..d6a2cb1e0
--- /dev/null
+++ b/testcases/kernel/power_management/ltr_counter.c
@@ -0,0 +1,358 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+/*
+ * Copyright (C) 2026 Intel - http://www.intel.com/
+ */
+
+/*\
+ * Verify that the package C-state LTR blocker counters only advance while a
+ * package C-state entry can be attempted.
+ *
+ * The pkgc_ltr_blocker_show debugfs file reports, per source, how many times an
+ * LTR posted by that source blocked a package C-state entry. Such an entry is
+ * only attempted once every CPU in the package has gone idle, so a workload
+ * that keeps every CPU busy has to stop the counters. A counter that keeps
+ * advancing under full load is not counting blocked package C-state entries,
+ * which is what a telemetry region read at the wrong offset looks like.
+ *
+ * The test first lets the system idle until it sees a counter advance, so that
+ * a counter stuck at a constant value cannot pass the load phase for free. It
+ * then pins a busy loop to every CPU and requires the counters to hold still.
+ *
+ * The counters are backed by a PMT telemetry region that the PMC refreshes
+ * asynchronously and in bursts - roughly six seconds apart on Nova Lake - so
+ * the load phase waits for the counters to go quiet rather than sampling them
+ * after a fixed delay. Entries blocked before the load took hold surface in a
+ * later refresh, while a counter that is not tracking blocked entries never
+ * goes quiet at all and runs the window out.
+ *
+ * The test needs root because /sys/kernel/debug is mode 0700 and owned by
+ * root, so the counter file cannot be opened otherwise, and because the test
+ * library mounts debugfs if it is not mounted already.
+ */
+
+#define _GNU_SOURCE
+
+#include <sched.h>
+#include <signal.h>
+#include <time.h>
+#include "tst_clocks.h"
+#include "tst_safe_stdio.h"
+#include "tst_test.h"
+#include "tst_timer.h"
+
+#define PATH TST_DEBUGFS_PATH "/pmc_core/pkgc_ltr_blocker_show"
+
+/* Nova Lake reports 5 counters, leave room for future platforms */
+#define MAX_COUNTERS 64
+#define NAME_LEN 64
+
+#define POLL_MS 500
+
+/*
+ * Seconds to wait for a counter to advance while the system idles. The bursts
+ * are about six seconds apart on Nova Lake, so this leaves room for two of
+ * them before giving up on the platform blocking a package C-state entry at
+ * all.
+ */
+#define IDLE_WINDOW 15
+
+/*
+ * How long, in seconds, the counters have to hold still under load, and the cap
+ * on how long to wait for that. One burst period of silence is what tells a
+ * stopped counter from one that is merely between refreshes, and the cap has to
+ * be long enough on top of that for a burst counted before the load took hold
+ * to arrive and drain.
+ */
+#define QUIET_WINDOW 6
+#define LOAD_WINDOW 20
+
+static char names[MAX_COUNTERS][NAME_LEN];
+static int cnt;
+static pid_t *spinners;
+static int nr_spinners;
+static volatile unsigned long long burn;
+
+/*
+ * Read the counter values, checking that the counter set itself has not changed
+ * since setup(). Only the values are of interest here - the format of the file
+ * is what the pkgc_ltr_blocker_show test covers - so anything unexpected about
+ * the lines makes this test unable to run rather than failing it.
+ */
+static void read_counters(uint32_t *values)
+{
+ char line[256];
+ FILE *fp;
+ int n = 0;
+
+ fp = SAFE_FOPEN(PATH, "r");
+
+ while (fgets(line, sizeof(line), fp)) {
+ char name[NAME_LEN];
+ unsigned int value;
+
+ line[strcspn(line, "\n")] = '\0';
+
+ if (n == MAX_COUNTERS)
+ tst_brk(TBROK, "more than %d counters reported", MAX_COUNTERS);
+
+ if (sscanf(line, "%63s %u", name, &value) != 2)
+ tst_brk(TBROK, "unexpected line format: '%s'", line);
+
+ values[n] = value;
+
+ if (cnt && strcmp(name, names[n])) {
+ tst_brk(TBROK, "counter %d renamed during the test: %s -> %s",
+ n, names[n], name);
+ }
+
+ if (!cnt)
+ strcpy(names[n], name);
+
+ n++;
+ }
+
+ /*
+ * A telemetry read that fails in the driver aborts the seq_file read
+ * instead of ending it at EOF, so without this the counters lost to the
+ * error would look like counters that stopped advancing.
+ */
+ if (ferror(fp))
+ tst_brk(TBROK, "reading " PATH " failed after %d counters", n);
+
+ SAFE_FCLOSE(fp);
+
+ if (cnt && n != cnt)
+ tst_brk(TBROK, "counter count changed during the test: %d -> %d", cnt, n);
+
+ cnt = n;
+}
+
+static void spin(void)
+{
+ /* volatile so that the loop survives optimization */
+ for (;;)
+ burn++;
+}
+
+/*
+ * Keep every CPU the test may run on busy, so that the package cannot go idle
+ * and no package C-state entry can be attempted.
+ *
+ * The spinners are pinned one per CPU taken from the test's own affinity mask,
+ * rather than left to the load balancer, so that a CPU is not left idle by a
+ * scheduling decision. Offline CPUs and a restricted mask are handled by
+ * construction.
+ */
+static void start_load(void)
+{
+ cpu_set_t mask;
+ int cpu = 0;
+
+ if (sched_getaffinity(0, sizeof(mask), &mask))
+ tst_brk(TBROK | TERRNO, "sched_getaffinity() failed");
+
+ nr_spinners = CPU_COUNT(&mask);
+ spinners = SAFE_MALLOC(nr_spinners * sizeof(*spinners));
+
+ for (int i = 0; i < nr_spinners; i++) {
+ pid_t pid;
+
+ while (!CPU_ISSET(cpu, &mask))
+ cpu++;
+
+ pid = SAFE_FORK();
+
+ if (!pid) {
+ cpu_set_t one;
+
+ CPU_ZERO(&one);
+ CPU_SET(cpu, &one);
+
+ if (sched_setaffinity(0, sizeof(one), &one)) {
+ tst_brk(TBROK | TERRNO,
+ "sched_setaffinity(CPU %d) failed", cpu);
+ }
+
+ spin();
+ }
+
+ spinners[i] = pid;
+ cpu++;
+ }
+
+ tst_res(TINFO, "keeping %d CPUs busy", nr_spinners);
+}
+
+static void stop_load(void)
+{
+ if (!spinners)
+ return;
+
+ for (int i = 0; i < nr_spinners; i++) {
+ SAFE_KILL(spinners[i], SIGKILL);
+ SAFE_WAITPID(spinners[i], NULL, 0);
+ }
+
+ free(spinners);
+ spinners = NULL;
+}
+
+/*
+ * Wait for a counter to advance while the system idles, reporting how many did.
+ *
+ * This is what tells a counter that stops under load from one that never moves.
+ * On an idle system most counters read 0 permanently, because nothing posts an
+ * LTR that would block a package C-state entry, so a single counter advancing
+ * is enough.
+ */
+static int wait_for_advance(const uint32_t *baseline)
+{
+ uint32_t values[MAX_COUNTERS];
+ struct timespec start, now;
+ int advanced = 0;
+
+ tst_clock_gettime(CLOCK_MONOTONIC, &start);
+
+ for (;;) {
+ read_counters(values);
+
+ for (int i = 0; i < cnt; i++) {
+ if (values[i] == baseline[i])
+ continue;
+
+ tst_res(TINFO, "%s advanced by %u while idle", names[i],
+ values[i] - baseline[i]);
+ advanced++;
+ }
+
+ if (advanced)
+ return advanced;
+
+ tst_clock_gettime(CLOCK_MONOTONIC, &now);
+
+ if (tst_timespec_diff_ms(now, start) >= IDLE_WINDOW * 1000)
+ return 0;
+
+ usleep(POLL_MS * 1000);
+ }
+}
+
+/*
+ * Require the counters to hold still for QUIET_WINDOW seconds of full load.
+ */
+static void check_counters_stop(void)
+{
+ uint32_t first[MAX_COUNTERS], prev[MAX_COUNTERS], values[MAX_COUNTERS];
+ bool moved[MAX_COUNTERS] = {};
+ struct timespec start, last_change, now;
+
+ read_counters(first);
+ memcpy(prev, first, sizeof(prev));
+
+ tst_clock_gettime(CLOCK_MONOTONIC, &start);
+ last_change = start;
+
+ for (;;) {
+ usleep(POLL_MS * 1000);
+
+ read_counters(values);
+ tst_clock_gettime(CLOCK_MONOTONIC, &now);
+
+ for (int i = 0; i < cnt; i++) {
+ if (values[i] == prev[i])
+ continue;
+
+ moved[i] = true;
+ last_change = now;
+ }
+
+ memcpy(prev, values, sizeof(prev));
+
+ if (tst_timespec_diff_ms(now, last_change) >= QUIET_WINDOW * 1000) {
+ tst_res(TPASS,
+ "%d counters held still over %ds with every CPU busy",
+ cnt, QUIET_WINDOW);
+ return;
+ }
+
+ if (tst_timespec_diff_ms(now, start) < LOAD_WINDOW * 1000)
+ continue;
+
+ for (int i = 0; i < cnt; i++) {
+ if (!moved[i])
+ continue;
+
+ tst_res(TFAIL,
+ "%s kept advancing over %ds with every CPU busy: %u -> %u",
+ names[i], LOAD_WINDOW, first[i], values[i]);
+ }
+
+ return;
+ }
+}
+
+static void setup(void)
+{
+ uint32_t values[MAX_COUNTERS];
+
+ if (access(PATH, R_OK))
+ tst_brk(TCONF | TERRNO, "%s not available", PATH);
+
+ /* Learns the counter set the rest of the test compares against */
+ read_counters(values);
+
+ if (!cnt)
+ tst_brk(TBROK, "no LTR blocker counters reported");
+}
+
+static void cleanup(void)
+{
+ stop_load();
+}
+
+static void run(void)
+{
+ uint32_t baseline[MAX_COUNTERS];
+
+ read_counters(baseline);
+
+ /*
+ * Without a counter that moves while idle there is nothing to stop
+ * under load, a stuck counter would look exactly the same.
+ */
+ if (!wait_for_advance(baseline)) {
+ tst_brk(TCONF,
+ "no LTR blocked a package C-state entry over %ds of idling",
+ IDLE_WINDOW);
+ }
+
+ start_load();
+ check_counters_stop();
+ stop_load();
+}
+
+static struct tst_test test = {
+ .min_kver = "7.2",
+ .timeout = 90,
+ .needs_root = 1,
+ .needs_debugfs = 1,
+ .forks_child = 1,
+ .needs_kconfigs = (const char *const []) {
+ "CONFIG_DEBUG_FS",
+ "CONFIG_INTEL_PMC_CORE",
+ NULL
+ },
+ .supported_archs = (const char *const []) {
+ "x86",
+ "x86_64",
+ NULL
+ },
+ .tags = (const struct tst_tag[]) {
+ {"linux-git", "38c79dd63b72e36919ef097d4e5025ca0fa17f34"},
+ {}
+ },
+ .setup = setup,
+ .cleanup = cleanup,
+ .test_all = run
+};
--
2.47.3
---------------------------------------------------------------------
Intel Technology Poland sp. z o.o.
ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply related [flat|nested] 5+ messages in thread* Re: [LTP] [PATCH v2 3/3] pmc_core: add ltr_counter test
2026-09-07 12:10 ` [LTP] [PATCH v2 3/3] pmc_core: add ltr_counter test Piotr Kubaj
@ 2026-09-09 13:29 ` Kubaj, Piotr
0 siblings, 0 replies; 5+ messages in thread
From: Kubaj, Piotr @ 2026-09-09 13:29 UTC (permalink / raw)
To: ltp@lists.linux.it
Cc: Dubel, Helena Anna, Ossowski, Tomasz, Wysocki, Rafael J,
Niestepski, Daniel
W dniu pon, 07.09.2026 o godzinie 14∶10 +0200, użytkownik Piotr Kubaj
napisał:
> Add a test for the invariant behind the pkgc_ltr_blocker_show
> counters:
> they only advance while a package C-state entry can be attempted,
> which
> needs every CPU in the package to be idle. A counter that keeps
> advancing
> with every CPU busy is not counting blocked package C-state entries,
> which is what a telemetry region read at the wrong offset looks like.
>
> The test first lets the system idle until it sees a counter advance,
> so
> that a counter stuck at a constant value cannot pass the load phase
> for
> free, and then pins a busy loop to every CPU it may run on and
> requires
> the counters to hold still.
>
> The counters are backed by a PMT telemetry region that the PMC
> refreshes
> asynchronously and in bursts - roughly six seconds apart on Nova Lake
> -
> so the load phase waits for the counters to go quiet rather than
> sampling
> them after a fixed delay. Entries blocked before the load took hold
> surface in a later refresh, while a counter that is not tracking
> blocked
> entries never goes quiet at all and runs the window out.
>
> Tested on Nova Lake running 7.3.0-rc1.
>
> Signed-off-by: Piotr Kubaj <piotr.kubaj@intel.com>
> ---
> runtest/power_management_tests | 1 +
> testcases/kernel/power_management/.gitignore | 1 +
> .../kernel/power_management/ltr_counter.c | 358
> ++++++++++++++++++
> 3 files changed, 360 insertions(+)
> create mode 100644 testcases/kernel/power_management/ltr_counter.c
>
> diff --git a/runtest/power_management_tests
> b/runtest/power_management_tests
> index 7708d3fd4..c4f661257 100644
> --- a/runtest/power_management_tests
> +++ b/runtest/power_management_tests
> @@ -3,6 +3,7 @@ cpufreq_intel cpufreq_intel
> rfim01 rfim01
> high_freq_hwp_cap_cppc high_freq_hwp_cap_cppc
> pkgc_ltr_blocker_show pkgc_ltr_blocker_show
> +ltr_counter ltr_counter
> runpwtests03 runpwtests03.sh
> runpwtests04 runpwtests04.sh
> runpwtests06 runpwtests06.sh
> diff --git a/testcases/kernel/power_management/.gitignore
> b/testcases/kernel/power_management/.gitignore
> index b96084eca..f54cec368 100644
> --- a/testcases/kernel/power_management/.gitignore
> +++ b/testcases/kernel/power_management/.gitignore
> @@ -2,3 +2,4 @@ high_freq_hwp_cap_cppc
> cpufreq_intel
> rfim01
> pkgc_ltr_blocker_show
> +ltr_counter
> diff --git a/testcases/kernel/power_management/ltr_counter.c
> b/testcases/kernel/power_management/ltr_counter.c
> new file mode 100644
> index 000000000..d6a2cb1e0
> --- /dev/null
> +++ b/testcases/kernel/power_management/ltr_counter.c
> @@ -0,0 +1,358 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later
> +
> +/*
> + * Copyright (C) 2026 Intel - http://www.intel.com/
> + */
> +
> +/*\
> + * Verify that the package C-state LTR blocker counters only advance
> while a
> + * package C-state entry can be attempted.
> + *
> + * The pkgc_ltr_blocker_show debugfs file reports, per source, how
> many times an
> + * LTR posted by that source blocked a package C-state entry. Such
> an entry is
> + * only attempted once every CPU in the package has gone idle, so a
> workload
> + * that keeps every CPU busy has to stop the counters. A counter
> that keeps
> + * advancing under full load is not counting blocked package C-state
> entries,
> + * which is what a telemetry region read at the wrong offset looks
> like.
> + *
> + * The test first lets the system idle until it sees a counter
> advance, so that
> + * a counter stuck at a constant value cannot pass the load phase
> for free. It
> + * then pins a busy loop to every CPU and requires the counters to
> hold still.
> + *
> + * The counters are backed by a PMT telemetry region that the PMC
> refreshes
> + * asynchronously and in bursts - roughly six seconds apart on Nova
> Lake - so
> + * the load phase waits for the counters to go quiet rather than
> sampling them
> + * after a fixed delay. Entries blocked before the load took hold
> surface in a
> + * later refresh, while a counter that is not tracking blocked
> entries never
> + * goes quiet at all and runs the window out.
> + *
> + * The test needs root because /sys/kernel/debug is mode 0700 and
> owned by
> + * root, so the counter file cannot be opened otherwise, and because
> the test
> + * library mounts debugfs if it is not mounted already.
> + */
> +
> +#define _GNU_SOURCE
> +
> +#include <sched.h>
> +#include <signal.h>
> +#include <time.h>
> +#include "tst_clocks.h"
> +#include "tst_safe_stdio.h"
> +#include "tst_test.h"
> +#include "tst_timer.h"
> +
> +#define PATH TST_DEBUGFS_PATH "/pmc_core/pkgc_ltr_blocker_show"
> +
> +/* Nova Lake reports 5 counters, leave room for future platforms */
> +#define MAX_COUNTERS 64
> +#define NAME_LEN 64
> +
> +#define POLL_MS 500
> +
> +/*
> + * Seconds to wait for a counter to advance while the system idles.
> The bursts
> + * are about six seconds apart on Nova Lake, so this leaves room for
> two of
> + * them before giving up on the platform blocking a package C-state
> entry at
> + * all.
> + */
> +#define IDLE_WINDOW 15
> +
> +/*
> + * How long, in seconds, the counters have to hold still under load,
> and the cap
> + * on how long to wait for that. One burst period of silence is what
> tells a
> + * stopped counter from one that is merely between refreshes, and
> the cap has to
> + * be long enough on top of that for a burst counted before the load
> took hold
> + * to arrive and drain.
> + */
> +#define QUIET_WINDOW 6
> +#define LOAD_WINDOW 20
> +
> +static char names[MAX_COUNTERS][NAME_LEN];
> +static int cnt;
> +static pid_t *spinners;
> +static int nr_spinners;
> +static volatile unsigned long long burn;
> +
> +/*
> + * Read the counter values, checking that the counter set itself has
> not changed
> + * since setup(). Only the values are of interest here - the format
> of the file
> + * is what the pkgc_ltr_blocker_show test covers - so anything
> unexpected about
> + * the lines makes this test unable to run rather than failing it.
> + */
> +static void read_counters(uint32_t *values)
> +{
> + char line[256];
> + FILE *fp;
> + int n = 0;
> +
> + fp = SAFE_FOPEN(PATH, "r");
> +
> + while (fgets(line, sizeof(line), fp)) {
> + char name[NAME_LEN];
> + unsigned int value;
> +
> + line[strcspn(line, "\n")] = '\0';
> +
> + if (n == MAX_COUNTERS)
> + tst_brk(TBROK, "more than %d counters
> reported", MAX_COUNTERS);
> +
> + if (sscanf(line, "%63s %u", name, &value) != 2)
> + tst_brk(TBROK, "unexpected line format:
> '%s'", line);
> +
> + values[n] = value;
> +
> + if (cnt && strcmp(name, names[n])) {
> + tst_brk(TBROK, "counter %d renamed during
> the test: %s -> %s",
> + n, names[n], name);
> + }
> +
> + if (!cnt)
> + strcpy(names[n], name);
> +
> + n++;
> + }
> +
> + /*
> + * A telemetry read that fails in the driver aborts the
> seq_file read
> + * instead of ending it at EOF, so without this the counters
> lost to the
> + * error would look like counters that stopped advancing.
> + */
> + if (ferror(fp))
> + tst_brk(TBROK, "reading " PATH " failed after %d
> counters", n);
> +
> + SAFE_FCLOSE(fp);
> +
> + if (cnt && n != cnt)
> + tst_brk(TBROK, "counter count changed during the
> test: %d -> %d", cnt, n);
> +
> + cnt = n;
> +}
> +
> +static void spin(void)
> +{
> + /* volatile so that the loop survives optimization */
> + for (;;)
> + burn++;
> +}
> +
> +/*
> + * Keep every CPU the test may run on busy, so that the package
> cannot go idle
> + * and no package C-state entry can be attempted.
> + *
> + * The spinners are pinned one per CPU taken from the test's own
> affinity mask,
> + * rather than left to the load balancer, so that a CPU is not left
> idle by a
> + * scheduling decision. Offline CPUs and a restricted mask are
> handled by
> + * construction.
> + */
> +static void start_load(void)
> +{
> + cpu_set_t mask;
> + int cpu = 0;
> +
> + if (sched_getaffinity(0, sizeof(mask), &mask))
> + tst_brk(TBROK | TERRNO, "sched_getaffinity()
> failed");
> +
> + nr_spinners = CPU_COUNT(&mask);
> + spinners = SAFE_MALLOC(nr_spinners * sizeof(*spinners));
> +
> + for (int i = 0; i < nr_spinners; i++) {
> + pid_t pid;
> +
> + while (!CPU_ISSET(cpu, &mask))
> + cpu++;
> +
> + pid = SAFE_FORK();
> +
> + if (!pid) {
> + cpu_set_t one;
> +
> + CPU_ZERO(&one);
> + CPU_SET(cpu, &one);
> +
> + if (sched_setaffinity(0, sizeof(one), &one))
> {
> + tst_brk(TBROK | TERRNO,
> + "sched_setaffinity(CPU %d)
> failed", cpu);
> + }
> +
> + spin();
> + }
> +
> + spinners[i] = pid;
> + cpu++;
> + }
> +
> + tst_res(TINFO, "keeping %d CPUs busy", nr_spinners);
> +}
> +
> +static void stop_load(void)
> +{
> + if (!spinners)
> + return;
> +
> + for (int i = 0; i < nr_spinners; i++) {
> + SAFE_KILL(spinners[i], SIGKILL);
> + SAFE_WAITPID(spinners[i], NULL, 0);
> + }
> +
> + free(spinners);
> + spinners = NULL;
> +}
> +
> +/*
> + * Wait for a counter to advance while the system idles, reporting
> how many did.
> + *
> + * This is what tells a counter that stops under load from one that
> never moves.
> + * On an idle system most counters read 0 permanently, because
> nothing posts an
> + * LTR that would block a package C-state entry, so a single counter
> advancing
> + * is enough.
> + */
> +static int wait_for_advance(const uint32_t *baseline)
> +{
> + uint32_t values[MAX_COUNTERS];
> + struct timespec start, now;
> + int advanced = 0;
> +
> + tst_clock_gettime(CLOCK_MONOTONIC, &start);
> +
> + for (;;) {
> + read_counters(values);
> +
> + for (int i = 0; i < cnt; i++) {
> + if (values[i] == baseline[i])
> + continue;
> +
> + tst_res(TINFO, "%s advanced by %u while
> idle", names[i],
> + values[i] - baseline[i]);
> + advanced++;
> + }
> +
> + if (advanced)
> + return advanced;
> +
> + tst_clock_gettime(CLOCK_MONOTONIC, &now);
> +
> + if (tst_timespec_diff_ms(now, start) >= IDLE_WINDOW
> * 1000)
> + return 0;
> +
> + usleep(POLL_MS * 1000);
> + }
> +}
> +
> +/*
> + * Require the counters to hold still for QUIET_WINDOW seconds of
> full load.
> + */
> +static void check_counters_stop(void)
> +{
> + uint32_t first[MAX_COUNTERS], prev[MAX_COUNTERS],
> values[MAX_COUNTERS];
> + bool moved[MAX_COUNTERS] = {};
> + struct timespec start, last_change, now;
> +
> + read_counters(first);
> + memcpy(prev, first, sizeof(prev));
> +
> + tst_clock_gettime(CLOCK_MONOTONIC, &start);
> + last_change = start;
> +
> + for (;;) {
> + usleep(POLL_MS * 1000);
> +
> + read_counters(values);
> + tst_clock_gettime(CLOCK_MONOTONIC, &now);
> +
> + for (int i = 0; i < cnt; i++) {
> + if (values[i] == prev[i])
> + continue;
> +
> + moved[i] = true;
> + last_change = now;
> + }
> +
> + memcpy(prev, values, sizeof(prev));
> +
> + if (tst_timespec_diff_ms(now, last_change) >=
> QUIET_WINDOW * 1000) {
> + tst_res(TPASS,
> + "%d counters held still over %ds
> with every CPU busy",
> + cnt, QUIET_WINDOW);
> + return;
> + }
> +
> + if (tst_timespec_diff_ms(now, start) < LOAD_WINDOW *
> 1000)
> + continue;
> +
> + for (int i = 0; i < cnt; i++) {
> + if (!moved[i])
> + continue;
> +
> + tst_res(TFAIL,
> + "%s kept advancing over %ds with
> every CPU busy: %u -> %u",
> + names[i], LOAD_WINDOW, first[i],
> values[i]);
> + }
> +
> + return;
> + }
> +}
> +
> +static void setup(void)
> +{
> + uint32_t values[MAX_COUNTERS];
> +
> + if (access(PATH, R_OK))
> + tst_brk(TCONF | TERRNO, "%s not available", PATH);
> +
> + /* Learns the counter set the rest of the test compares
> against */
> + read_counters(values);
> +
> + if (!cnt)
> + tst_brk(TBROK, "no LTR blocker counters reported");
> +}
> +
> +static void cleanup(void)
> +{
> + stop_load();
> +}
> +
> +static void run(void)
> +{
> + uint32_t baseline[MAX_COUNTERS];
> +
> + read_counters(baseline);
> +
> + /*
> + * Without a counter that moves while idle there is nothing
> to stop
> + * under load, a stuck counter would look exactly the same.
> + */
> + if (!wait_for_advance(baseline)) {
> + tst_brk(TCONF,
> + "no LTR blocked a package C-state entry over
> %ds of idling",
> + IDLE_WINDOW);
> + }
> +
> + start_load();
> + check_counters_stop();
> + stop_load();
> +}
> +
> +static struct tst_test test = {
> + .min_kver = "7.2",
> + .timeout = 90,
> + .needs_root = 1,
> + .needs_debugfs = 1,
> + .forks_child = 1,
> + .needs_kconfigs = (const char *const []) {
> + "CONFIG_DEBUG_FS",
> + "CONFIG_INTEL_PMC_CORE",
> + NULL
> + },
> + .supported_archs = (const char *const []) {
> + "x86",
> + "x86_64",
> + NULL
> + },
> + .tags = (const struct tst_tag[]) {
> + {"linux-git",
> "38c79dd63b72e36919ef097d4e5025ca0fa17f34"},
> + {}
> + },
> + .setup = setup,
> + .cleanup = cleanup,
> + .test_all = run
> +};
Hi,
the CI results on this series seem to fail from a partial download, not
because of a broken patch.
Every job, github-build-doc and all 16 github-build-* containers,
stopped in the "Apply Patchwork series" step:
100 3821 100 3821 0 0 17666 0
hint: When you have resolved this problem, run "git am --continue".
Patch is empty.
##[error]Process completed with exit code 128.
curl received a complete 3821 byte body for
https://patchwork.kernel.org/series/1159727/mbox/, while that mbox is
44397 bytes, so patchwork answered with an HTML page instead of the
series and git am rejected it as an empty patch.
The download works again now and the series applies to current master
with no conflicts, so re-dispatching series 1159727 should be enough.
Could someone re-trigger it? I am happy to resend as v3 instead if that
is easier.
---------------------------------------------------------------------
Intel Technology Poland sp. z o.o.
ul. Slowackiego 173 | 80-298 Gdansk | Sad Rejonowy Gdansk Polnoc | VII Wydzial Gospodarczy Krajowego Rejestru Sadowego - KRS 101882 | NIP 957-07-52-316 | Kapital zakladowy 200.000 PLN.
Spolka oswiadcza, ze posiada status duzego przedsiebiorcy w rozumieniu ustawy z dnia 8 marca 2013 r. o przeciwdzialaniu nadmiernym opoznieniom w transakcjach handlowych.
Ta wiadomosc wraz z zalacznikami jest przeznaczona dla okreslonego adresata i moze zawierac informacje poufne. W razie przypadkowego otrzymania tej wiadomosci, prosimy o powiadomienie nadawcy oraz trwale jej usuniecie; jakiekolwiek przegladanie lub rozpowszechnianie jest zabronione.
This e-mail and any attachments may contain confidential material for the sole use of the intended recipient(s). If you are not the intended recipient, please contact the sender and delete all copies; any review or distribution by others is strictly prohibited.
--
Mailing list info: https://lists.linux.it/listinfo/ltp
^ permalink raw reply [flat|nested] 5+ messages in thread