[PATCH 4/4] selftests/resctrl: Add MBA schemata per-group isolation selftest

From: Richard Cheng

Date: Tue Jul 21 2026 - 23:08:09 EST


MB cpas are per control group, implement MBA_SCHEMATA_ISOLATE test to
verify a write to one group leaves other groups and the default group
untouched.

Create 2 groups, set the first to the max and the second to a lower cap,
then read the first after writing the second, each holds its own value.
The default group stays unchanged. The lower cap is gran-aligned and at
or above min_bandwidth, so read-back is exact and provably below the
maximum.

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

diff --git a/tools/testing/selftests/resctrl/mba_schemata_test.c b/tools/testing/selftests/resctrl/mba_schemata_test.c
index 0cd257dcec91..d98946b831b1 100644
--- a/tools/testing/selftests/resctrl/mba_schemata_test.c
+++ b/tools/testing/selftests/resctrl/mba_schemata_test.c
@@ -388,3 +388,134 @@ struct resctrl_test mba_schemata_inval_test = {
.feature_check = mba_schemata_feature_check,
.run_test = mba_schemata_inval_run_test,
};
+
+/*
+ * MB allocations are per control group: two groups hold different
+ * percentages on the same domain without disturbing each other or the
+ * default group.
+ */
+static int mba_schemata_isolate_run_test(const struct resctrl_test *test,
+ const struct user_params *uparams)
+{
+ unsigned int gran, min_bw, low, num_closids, def_before = 0, def_after = 0;
+ unsigned int got1 = 0, got2 = 0, pct[MB_MAX_DOMAINS];
+ int ids[MB_MAX_DOMAINS], n, ret, dom, fail = 0;
+ char vals[MB_SCHEMATA_LEN], path1[256], path2[256], line[64];
+ const char *grp1 = "mba_schemata_iso1", *grp2 = "mba_schemata_iso2";
+
+ /*
+ * Isolation works with any CLOSID count, but the
+ * default group plus the two test groups each need to hold one.
+ */
+ if (resource_info_unsigned_get(test->resource, "num_closids", &num_closids))
+ return 1;
+ if (num_closids < 3) {
+ ksft_print_msg("Skipping: num_closids=%u < 3, cannot create two test groups\n",
+ num_closids);
+ return 0;
+ }
+
+ if (resource_info_unsigned_get(test->resource, "bandwidth_gran", &gran) || !gran)
+ return 1;
+ if (resource_info_unsigned_get(test->resource, "min_bandwidth", &min_bw))
+ return 1;
+
+ /* Enumerate the real MB domain ids from the default schemata. */
+ ret = resctrl_get_schemata("", test->resource, vals, sizeof(vals));
+ if (ret) {
+ ksft_print_msg("Fail: could not read %s schemata line (ret=%d)\n",
+ test->resource, ret);
+ return 1;
+ }
+ n = parse_mb_schemata(vals, ids, pct, MB_MAX_DOMAINS);
+ if (n < 1) {
+ ksft_print_msg("Fail: could not parse %s schemata \"%s\"\n",
+ test->resource, vals);
+ return 1;
+ }
+ dom = ids[0];
+
+ /*
+ * grp2's cap must stay provably below grp1's 100% even after the
+ * kernel rounds it to bandwidth_gran: align it here, at or above
+ * min_bandwidth, and expect an exact read-back. If no aligned value
+ * below the maximum exists, the two groups cannot be told apart.
+ */
+ low = min_bw > 20 ? min_bw : 20;
+ low = (low + gran - 1) / gran * gran;
+ if (low >= MB_PERCENT_MAX) {
+ ksft_print_msg("Skipping: no %s cap below %u representable (min_bw=%u, gran=%u)\n",
+ test->resource, MB_PERCENT_MAX, min_bw, gran);
+ return 0;
+ }
+
+ if (mb_read_domain("", test->resource, dom, &def_before)) {
+ ksft_print_msg("Fail: read default group %s domain %d\n",
+ test->resource, dom);
+ return 1;
+ }
+
+ snprintf(path1, sizeof(path1), "%s/%s", RESCTRL_PATH, grp1);
+ if (mkdir(path1, 0755) && errno != EEXIST) {
+ ksft_print_msg("Fail: mkdir %s: %m\n", path1);
+ return 1;
+ }
+ snprintf(path2, sizeof(path2), "%s/%s", RESCTRL_PATH, grp2);
+ if (mkdir(path2, 0755) && errno != EEXIST) {
+ ksft_print_msg("Fail: mkdir %s: %m\n", path2);
+ rmdir(path1);
+ return 1;
+ }
+
+ /* Pin the two groups to different caps on the same domain. */
+ snprintf(line, sizeof(line), "%s:%d=%u",
+ test->resource, dom, MB_PERCENT_MAX);
+ ret = resctrl_write_schemata(grp1, line);
+ if (ret) {
+ ksft_print_msg("Fail: write \"%s\" to %s (ret=%d)\n",
+ line, grp1, ret);
+ fail = 1;
+ }
+ snprintf(line, sizeof(line), "%s:%d=%u", test->resource, dom, low);
+ ret = resctrl_write_schemata(grp2, line);
+ if (ret) {
+ ksft_print_msg("Fail: write \"%s\" to %s (ret=%d)\n",
+ line, grp2, ret);
+ fail = 1;
+ }
+
+ /* Each group keeps its own cap; reading g1 after writing g2. */
+ if (mb_read_domain(grp1, test->resource, dom, &got1) ||
+ got1 != MB_PERCENT_MAX) {
+ ksft_print_msg("Fail: %s domain %d: %u%%, expected %u%%\n",
+ grp1, dom, got1, MB_PERCENT_MAX);
+ fail = 1;
+ }
+ if (mb_read_domain(grp2, test->resource, dom, &got2) ||
+ got2 != low) {
+ ksft_print_msg("Fail: %s domain %d: %u%%, expected %u%%\n",
+ grp2, dom, got2, low);
+ fail = 1;
+ }
+
+ /* The default group must be untouched by either write. */
+ if (mb_read_domain("", test->resource, dom, &def_after) ||
+ def_after != def_before) {
+ ksft_print_msg("Fail: default group domain %d changed %u%% -> %u%%\n",
+ dom, def_before, def_after);
+ fail = 1;
+ }
+
+ rmdir(path1);
+ rmdir(path2);
+
+ return fail;
+}
+
+struct resctrl_test mba_schemata_isolate_test = {
+ .name = "MBA_SCHEMATA_ISOLATE",
+ .group = "MBA_SCHEMATA",
+ .resource = "MB",
+ .feature_check = mba_schemata_feature_check,
+ .run_test = mba_schemata_isolate_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index c32ab5c2cc56..07a67ac9292c 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -252,5 +252,6 @@ extern struct resctrl_test l2_noncont_cat_test;
extern struct resctrl_test mba_schemata_info_test;
extern struct resctrl_test mba_schemata_rw_test;
extern struct resctrl_test mba_schemata_inval_test;
+extern struct resctrl_test mba_schemata_isolate_test;

#endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index 7a7184783e6a..583ee97bac20 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -24,6 +24,7 @@ static struct resctrl_test *resctrl_tests[] = {
&mba_schemata_info_test,
&mba_schemata_rw_test,
&mba_schemata_inval_test,
+ &mba_schemata_isolate_test,
};

static unsigned int detect_vendor(void)
--
2.43.0