[PATCH 3/3] selftests/resctrl: Add L3_BIT_USAGE to check bit_usage tracks allocation

From: Richard Cheng

Date: Mon Jun 08 2026 - 07:16:21 EST


info/L3/bit_usage annotates every cache portion as used by software ('X
or 'S') or not ('H' or '0'). Nothing in the suite checks it's reported
correctly.

Implement L3_BIT_USAGE to set the root group's CBM to a few masks and
confirm that, for every portion, bit_usage marks it software-used
exactly when it is in the CBM. With only the root group present this
must track the mask bit-for-bit.

Signed-off-by: Richard Cheng <icheng@xxxxxxxxxx>
---
tools/testing/selftests/resctrl/cat_test.c | 153 ++++++++++++++++++
tools/testing/selftests/resctrl/resctrl.h | 1 +
.../testing/selftests/resctrl/resctrl_tests.c | 1 +
3 files changed, 155 insertions(+)

diff --git a/tools/testing/selftests/resctrl/cat_test.c b/tools/testing/selftests/resctrl/cat_test.c
index d236988916d9..62af8ac98109 100644
--- a/tools/testing/selftests/resctrl/cat_test.c
+++ b/tools/testing/selftests/resctrl/cat_test.c
@@ -682,3 +682,156 @@ struct resctrl_test l3_cat_validate_test = {
.feature_check = test_resource_feature_check,
.run_test = cat_validate_run_test,
};
+
+/*
+ * L3_BIT_USAGE - Verify info/L3/bit_usage reflects the allocation.
+ *
+ * bit_usage annotates each cache portion: 'X'/'S' mean a portion is used by
+ * software, 'H'/'0' mean it is not. With only the root group present, a
+ * portion is software-used exactly when it is in the root CBM, so bit_usage
+ * must track the CBM bit-for-bit.
+ */
+#define BIT_USAGE_LEN 256
+
+static bool bit_usage_sw_used(char c)
+{
+ return c == 'X' || c == 'S';
+}
+
+static bool bit_usage_not_used(char c)
+{
+ return c == 'H' || c == '0';
+}
+
+static int bit_usage_for_domain(const char *resource, int domain_id,
+ char *out, size_t len)
+{
+ char path[1024], raw[BIT_USAGE_LEN], *tok, *save;
+ FILE *fp;
+
+ snprintf(path, sizeof(path), "%s/%s/bit_usage", INFO_PATH, resource);
+ fp = fopen(path, "r");
+ if (!fp) {
+ ksft_perror("Error opening bit_usage");
+ return -1;
+ }
+ if (!fgets(raw, sizeof(raw), fp)) {
+ ksft_perror("Error reading bit_usage");
+ fclose(fp);
+ return -1;
+ }
+ fclose(fp);
+
+ /* bit_usage is "id=chars;id=chars;..."; return the chars for domain_id. */
+ for (tok = strtok_r(raw, ";\n", &save); tok; tok = strtok_r(NULL, ";\n", &save)) {
+ char *eq = strchr(tok, '=');
+
+ if (!eq)
+ continue;
+ *eq = '\0';
+ if (atoi(tok) == domain_id) {
+ snprintf(out, len, "%s", eq + 1);
+ return 0;
+ }
+ }
+
+ ksft_print_msg("No bit_usage entry for domain %d\n", domain_id);
+ return -1;
+}
+
+static int bit_usage_check_mask(const struct resctrl_test *test, int cpu,
+ int domain_id, unsigned long mask,
+ int count_of_bits)
+{
+ char schemata[64], usage[BIT_USAGE_LEN];
+ int i, ret;
+
+ snprintf(schemata, sizeof(schemata), "%lx", mask);
+ ret = write_schemata("", schemata, cpu, test->resource);
+ if (ret) {
+ ksft_print_msg("Failed to set CBM 0x%lx\n", mask);
+ return ret;
+ }
+
+ ret = bit_usage_for_domain(test->resource, domain_id, usage, sizeof(usage));
+ if (ret)
+ return ret;
+
+ if (strlen(usage) != count_of_bits) {
+ ksft_print_msg("bit_usage \"%s\" has %zu chars, expected %d\n",
+ usage, strlen(usage), count_of_bits);
+ return 1;
+ }
+
+ /* bit_usage prints the highest portion first, so usage[0] is bit N-1. */
+ for (i = 0; i < count_of_bits; i++) {
+ int bit = count_of_bits - 1 - i;
+ bool in_cbm = (mask >> bit) & 1;
+ char c = usage[i];
+
+ if (!bit_usage_sw_used(c) && !bit_usage_not_used(c)) {
+ ksft_print_msg("Unexpected bit_usage char '%c' for CBM 0x%lx\n",
+ c, mask);
+ return 1;
+ }
+ if (in_cbm != bit_usage_sw_used(c)) {
+ ksft_print_msg("CBM 0x%lx portion %d shows '%c', %s allocation\n",
+ mask, bit, c, in_cbm ? "in" : "not in");
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static int bit_usage_run_test(const struct resctrl_test *test,
+ const struct user_params *uparams)
+{
+ unsigned long full_mask, masks[3];
+ char schemata[64];
+ int count_of_bits, domain_id, i, ret;
+
+ ret = get_full_cbm(test->resource, &full_mask);
+ if (ret)
+ return ret;
+
+ ret = get_domain_id(test->resource, uparams->cpu, &domain_id);
+ if (ret < 0)
+ return ret;
+
+ count_of_bits = count_bits(full_mask);
+
+ masks[0] = full_mask; /* every portion */
+ masks[1] = create_bit_mask(0, count_of_bits / 2); /* low half */
+ masks[2] = full_mask & ~masks[1]; /* high half */
+
+ for (i = 0; i < 3; i++) {
+ ret = bit_usage_check_mask(test, uparams->cpu, domain_id,
+ masks[i], count_of_bits);
+ if (ret)
+ break;
+ }
+
+ /* Restore the root group to the full CBM. */
+ snprintf(schemata, sizeof(schemata), "%lx", full_mask);
+ write_schemata("", schemata, uparams->cpu, test->resource);
+
+ if (!ret)
+ ksft_print_msg("Pass: bit_usage reflects the allocation\n");
+
+ return ret;
+}
+
+static bool bit_usage_feature_check(const struct resctrl_test *test)
+{
+ return test_resource_feature_check(test) &&
+ resource_info_file_exists(test->resource, "bit_usage");
+}
+
+struct resctrl_test l3_bit_usage_test = {
+ .name = "L3_BIT_USAGE",
+ .group = "CAT",
+ .resource = "L3",
+ .feature_check = bit_usage_feature_check,
+ .run_test = bit_usage_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index e2e3cf7833bc..270af2b26ba7 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -249,6 +249,7 @@ extern struct resctrl_test cmt_test;
extern struct resctrl_test l3_cat_test;
extern struct resctrl_test l3_cat_occup_test;
extern struct resctrl_test l3_cat_validate_test;
+extern struct resctrl_test l3_bit_usage_test;
extern struct resctrl_test l3_noncont_cat_test;
extern struct resctrl_test l2_noncont_cat_test;

diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index 66739e96f33c..e622928a2d7d 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -21,6 +21,7 @@ static struct resctrl_test *resctrl_tests[] = {
&l3_cat_test,
&l3_cat_occup_test,
&l3_cat_validate_test,
+ &l3_bit_usage_test,
&l3_noncont_cat_test,
&l2_noncont_cat_test,
};
--
2.43.0