All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/4] selftests/resctrl: MBA schemata ABI selftests for MPAM platforms
@ 2026-07-22  2:10 Richard Cheng
  2026-07-22  2:10 ` [PATCH 1/4] selftests/resctrl: Add MBA schemata info selftest Richard Cheng
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: Richard Cheng @ 2026-07-22  2:10 UTC (permalink / raw)
  To: tony.luck, reinette.chatre, x86
  Cc: Dave.Martin, james.morse, babu.moger, shuah, linux-kernel,
	linux-kselftest, newtonl, kristinc, kobak, kaihengf, fenghuay,
	ltrager, Richard Cheng

The MPAM MBA enablement carried in this tree exposes memory-bandwidth
allocation through the resctrl "MB" schemata, but none of it is covered
by the in-tree resctrl selftests: the existing mba_test/mbm_test are
ARCH_INTEL-gated throughput tests that validate against the Intel iMC
PMU and map domains via L3 cache ids, so they skip (and cannot work) on
aarch64/MPAM, where MB domains are NUMA proximity ids. Any regression
in the MB control plane currently ships unnoticed.

Add four deterministic, workload-free ABI tests in a shared
MBA_SCHEMATA group, one resctrl_test entry each:

  1. MBA_SCHEMATA_INFO    - sane control contract (schema_format, gran,
                            min_bandwidth, num_closids) and default
                            schemata at 100% on every domain
  2. MBA_SCHEMATA_RW      - percentages take effect and read back
                            exactly, per domain, including restore
  3. MBA_SCHEMATA_INVAL   - out-of-range/malformed/bad-domain writes
                            are rejected with the documented
                            last_cmd_status diagnostics
  4. MBA_SCHEMATA_ISOLATE - two groups keep independent caps; the
                            default group is never perturbed

All cases are feature-gated (schema_format == percentage) and skip
cleanly elsewhere; no workload or measurement is involved, so they are
CI-safe and flake-free. The Intel tests are left untouched. The tests
are written upstream-shaped for eventual submission.

This series applies on top of the 26.04_linux-nvidia.glue.others.
cpu_less.hardlimit branch of
https://github.com/fyu1/NV-Kernels.fenghuay.baseos

Tested on an NVIDIA Grace (Vera) system with MPAM MBA enabled (two MB
domains): sudo ./resctrl_tests -t MBA_SCHEMATA -> 4/4 pass.

The newly added test file fires a warning when using checkpatch.pl .

Richard Cheng (4):
  selftests/resctrl: Add MBA schemata info selftest
  selftests/resctrl: Add MBA schemata write/read-back selftest
  selftests/resctrl: Add MBA schemata invalid-write selftest
  selftests/resctrl: Add MBA schemata per-group isolation selftest

 .../selftests/resctrl/mba_schemata_test.c     | 521 ++++++++++++++++++
 tools/testing/selftests/resctrl/resctrl.h     |   7 +
 .../testing/selftests/resctrl/resctrl_tests.c |   4 +
 tools/testing/selftests/resctrl/resctrlfs.c   | 157 ++++++
 4 files changed, 689 insertions(+)
 create mode 100644 tools/testing/selftests/resctrl/mba_schemata_test.c


base-commit: 475f355d0408698d9f632ba3b9a5dfb1f91e928b
-- 
2.43.0


^ permalink raw reply	[flat|nested] 5+ messages in thread

* [PATCH 1/4] selftests/resctrl: Add MBA schemata info selftest
  2026-07-22  2:10 [PATCH 0/4] selftests/resctrl: MBA schemata ABI selftests for MPAM platforms Richard Cheng
@ 2026-07-22  2:10 ` Richard Cheng
  2026-07-22  2:10 ` [PATCH 2/4] selftests/resctrl: Add MBA schemata write/read-back selftest Richard Cheng
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: Richard Cheng @ 2026-07-22  2:10 UTC (permalink / raw)
  To: tony.luck, reinette.chatre, x86
  Cc: Dave.Martin, james.morse, babu.moger, shuah, linux-kernel,
	linux-kselftest, newtonl, kristinc, kobak, kaihengf, fenghuay,
	ltrager, Richard Cheng

The existing MBA test is Intel-only: it validates the MB monitor against
the iMC PMU, which doesn't exist on ARM MPAM, so the MB allocation control
interface goes untested there.

Add a feature-gated MBA_SCHEMATA group. Its first case, MBA_SCHEMATA_INFO,
reads info/MB including bandwidth_gran, min_bandwidth, num_closids and the
default schemata through the resctrl filesystem and every domain resets to
the maximum.

resource_info_str_get() and resctrl_get_schemata() helpers are
implemented to assist the test.

Signed-off-by: Richard Cheng <icheng@nvidia.com>
---
 .../selftests/resctrl/mba_schemata_test.c     | 152 ++++++++++++++++++
 tools/testing/selftests/resctrl/resctrl.h     |   3 +
 .../testing/selftests/resctrl/resctrl_tests.c |   1 +
 tools/testing/selftests/resctrl/resctrlfs.c   | 121 ++++++++++++++
 4 files changed, 277 insertions(+)
 create mode 100644 tools/testing/selftests/resctrl/mba_schemata_test.c

diff --git a/tools/testing/selftests/resctrl/mba_schemata_test.c b/tools/testing/selftests/resctrl/mba_schemata_test.c
new file mode 100644
index 000000000000..b92011e42726
--- /dev/null
+++ b/tools/testing/selftests/resctrl/mba_schemata_test.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Memory Bandwidth Allocation (MB) schemata / control-interface tests.
+ *
+ * These are deterministic, userspace-driven ABI integration tests for the
+ * resctrl "MB" control plane as exposed by ARM MPAM.
+ *
+ * Each test case is a separate struct resctrl_test sharing the "MBA_SCHEMATA" group.
+ */
+#include "resctrl.h"
+
+#define MB_MAX_DOMAINS		64
+
+/*
+ * Worst-case length of an MB schemata value string ("<id>=<pct>;" per domain),
+ * sized for MB_MAX_DOMAINS so a full line is never silently truncated. If a
+ * platform exceeds this, resctrl_get_schemata() returns -ENOBUFS (we fail, not
+ * silently verify a prefix).
+ */
+#define MB_SCHEMATA_LEN		(MB_MAX_DOMAINS * 16)
+#define MB_PERCENT_MAX		100
+
+/*
+ * parse_mb_schemata - Parse a percentage MB schemata value string.
+ * @vals:	"id=pct;id=pct;..." (modified in place by strtok_r)
+ * @ids:	output array of domain ids
+ * @pct:	output array of percentages
+ * @max:	capacity of @ids / @pct
+ *
+ * Return: number of domains parsed, or -1 on overflow / parse error.
+ */
+static int parse_mb_schemata(char *vals, int ids[], unsigned int pct[], int max)
+{
+	char *save = NULL, *tok;
+	int n = 0;
+
+	for (tok = strtok_r(vals, ";", &save); tok;
+	     tok = strtok_r(NULL, ";", &save)) {
+		int id, consumed = 0;
+		unsigned int p;
+
+		if (n >= max)
+			return -1;
+		/*
+		 * Strictly validate the whole token as "<id>=<pct>": %n records
+		 * how far sscanf advanced, so a non-NUL byte there means trailing
+		 * garbage (e.g. "0=100junk"). Domain ids are non-negative.
+		 */
+		if (sscanf(tok, "%d=%u%n", &id, &p, &consumed) != 2 ||
+		    tok[consumed] != '\0' || id < 0)
+			return -1;
+		ids[n] = id;
+		pct[n] = p;
+		n++;
+	}
+
+	return n;
+}
+
+/*
+ * the MB resource advertises a sane, self-consistent
+ * control contract and the default group resets to the maximum on every domain.
+ */
+static int mba_schemata_info_run_test(const struct resctrl_test *test,
+				 const struct user_params *uparams)
+{
+	unsigned int gran, min_bw, num_closids, pct[MB_MAX_DOMAINS];
+	int ids[MB_MAX_DOMAINS], n, i, ret, fail = 0;
+	char vals[MB_SCHEMATA_LEN];
+
+	/* bandwidth_gran must be in [1,100]. */
+	if (resource_info_unsigned_get(test->resource, "bandwidth_gran", &gran))
+		return 1;
+	if (gran < 1 || gran > MB_PERCENT_MAX) {
+		ksft_print_msg("Fail: %s bandwidth_gran=%u out of [1,100]\n",
+			       test->resource, gran);
+		fail = 1;
+	}
+
+	/* min_bandwidth must be in [0,100]. */
+	if (resource_info_unsigned_get(test->resource, "min_bandwidth", &min_bw))
+		return 1;
+	if (min_bw > MB_PERCENT_MAX) {
+		ksft_print_msg("Fail: %s min_bandwidth=%u > 100\n",
+			       test->resource, min_bw);
+		fail = 1;
+	}
+
+	/* num_closids must allow at least the default group plus one. */
+	if (resource_info_unsigned_get(test->resource, "num_closids", &num_closids))
+		return 1;
+	if (num_closids < 2) {
+		ksft_print_msg("Fail: %s num_closids=%u < 2\n",
+			       test->resource, num_closids);
+		fail = 1;
+	}
+
+	/*
+	 * Default group: the schemata must carry an MB line with one entry per
+	 * domain, and every domain must reset to the percentage maximum (100).
+	 */
+	ret = resctrl_get_schemata("", test->resource, vals, sizeof(vals));
+	if (ret) {
+		ksft_print_msg("Fail: could not read %s schemata line (ret=%d%s)\n",
+			       test->resource, ret,
+			       ret == -ENOBUFS ? " - line truncated, buffer too small" : "");
+		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;
+	}
+	ksft_print_msg("%s default schemata has %d domain(s)\n", test->resource, n);
+	for (i = 0; i < n; i++) {
+		if (pct[i] != MB_PERCENT_MAX) {
+			ksft_print_msg("Fail: %s domain %d default=%u, expected %d (max)\n",
+				       test->resource, ids[i], pct[i], MB_PERCENT_MAX);
+			fail = 1;
+		}
+	}
+	if (!fail)
+		ksft_print_msg("Pass: all %d %s domain(s) default to %d%%\n",
+			       n, test->resource, MB_PERCENT_MAX);
+
+	return fail;
+}
+
+/*
+ * MBA_SCHEMATA group feature check: the MB resource exists and uses the percentage
+ * schema. Non-percentage MB (e.g. x86 mba_MBps) and platforms without MB skip.
+ */
+static bool mba_schemata_feature_check(const struct resctrl_test *test)
+{
+	char fmt[64];
+
+	if (!resctrl_resource_exists(test->resource))
+		return false;
+	if (resource_info_str_get(test->resource, "schema_format", fmt, sizeof(fmt)))
+		return false;
+
+	return !strcmp(fmt, "percentage");
+}
+
+struct resctrl_test mba_schemata_info_test = {
+	.name = "MBA_SCHEMATA_INFO",
+	.group = "MBA_SCHEMATA",
+	.resource = "MB",
+	.feature_check = mba_schemata_feature_check,
+	.run_test = mba_schemata_info_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index afe635b6e48d..a6ad25a08ae5 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -201,6 +201,8 @@ int get_full_cbm(const char *cache_type, unsigned long *mask);
 int get_mask_no_shareable(const char *cache_type, unsigned long *mask);
 int get_cache_size(int cpu_no, const char *cache_type, unsigned long *cache_size);
 int resource_info_unsigned_get(const char *resource, const char *filename, unsigned int *val);
+int resource_info_str_get(const char *resource, const char *filename, char *val, size_t len);
+int resctrl_get_schemata(const char *ctrlgrp, const char *resource, char *buf, size_t len);
 void ctrlc_handler(int signum, siginfo_t *info, void *ptr);
 int signal_handler_register(const struct resctrl_test *test);
 void signal_handler_unregister(void);
@@ -246,5 +248,6 @@ extern struct resctrl_test cmt_test;
 extern struct resctrl_test l3_cat_test;
 extern struct resctrl_test l3_noncont_cat_test;
 extern struct resctrl_test l2_noncont_cat_test;
+extern struct resctrl_test mba_schemata_info_test;
 
 #endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index dbcd5eea9fbc..5d45ac95d988 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_noncont_cat_test,
 	&l2_noncont_cat_test,
+	&mba_schemata_info_test,
 };
 
 static unsigned int detect_vendor(void)
diff --git a/tools/testing/selftests/resctrl/resctrlfs.c b/tools/testing/selftests/resctrl/resctrlfs.c
index b9c1bfb6cc02..8b428a22496d 100644
--- a/tools/testing/selftests/resctrl/resctrlfs.c
+++ b/tools/testing/selftests/resctrl/resctrlfs.c
@@ -400,6 +400,127 @@ int resource_info_unsigned_get(const char *resource, const char *filename,
 	return 0;
 }
 
+/*
+ * resource_info_str_get - Read a string from
+ * /sys/fs/resctrl/info/@resource/@filename
+ * @resource:	Resource name that matches directory name in
+ *		/sys/fs/resctrl/info
+ * @filename:	File in /sys/fs/resctrl/info/@resource
+ * @val:	Buffer that receives the first line on success
+ * @len:	Size of @val
+ *
+ * Reads the first line of the file, stripping a trailing newline.
+ *
+ * Return: = 0 on success, < 0 on failure.
+ */
+int resource_info_str_get(const char *resource, const char *filename,
+			  char *val, size_t len)
+{
+	char file_path[PATH_MAX];
+	char *end;
+	FILE *fp;
+
+	snprintf(file_path, sizeof(file_path), "%s/%s/%s", INFO_PATH, resource,
+		 filename);
+
+	fp = fopen(file_path, "r");
+	if (!fp) {
+		ksft_print_msg("Error opening %s: %m\n", file_path);
+		return -1;
+	}
+
+	if (!fgets(val, len, fp)) {
+		ksft_print_msg("Could not get contents of %s: %m\n", file_path);
+		fclose(fp);
+		return -1;
+	}
+
+	fclose(fp);
+
+	end = strchr(val, '\n');
+	if (end)
+		*end = '\0';
+
+	return 0;
+}
+
+/*
+ * resctrl_get_schemata - Read the value portion of a resource's schemata line
+ * @ctrlgrp:	Control group name, or "" / NULL for the default group
+ * @resource:	Schema name (e.g. "MB", "L3")
+ * @buf:	Receives the text after "<resource>:" on success
+ * @len:	Size of @buf
+ *
+ * The schemata file holds one line per schema, e.g. "    MB:0=100;1=100".
+ * On success @buf holds the value part, e.g. "0=100;1=100".
+ *
+ * Return: = 0 on success, < 0 on failure (including the line not being found).
+ */
+int resctrl_get_schemata(const char *ctrlgrp, const char *resource,
+			 char *buf, size_t len)
+{
+	char schema_path[PATH_MAX];
+	char line[4096];
+	size_t rlen;
+	FILE *fp;
+	int ret = -ENOENT;
+
+	if (ctrlgrp && *ctrlgrp)
+		snprintf(schema_path, sizeof(schema_path), "%s/%s/schemata",
+			 RESCTRL_PATH, ctrlgrp);
+	else
+		snprintf(schema_path, sizeof(schema_path), "%s/schemata",
+			 RESCTRL_PATH);
+
+	fp = fopen(schema_path, "r");
+	if (!fp) {
+		ksft_print_msg("Error opening %s: %m\n", schema_path);
+		return -1;
+	}
+
+	rlen = strlen(resource);
+	while (fgets(line, sizeof(line), fp)) {
+		size_t linelen = strlen(line);
+		char *p = line;
+		char *nl;
+		int w;
+
+		while (*p == ' ' || *p == '\t')
+			p++;
+		/* Match "<resource>:" exactly so "MB" != "MB_HLIM"/"MB_MON". */
+		if (strncmp(p, resource, rlen) || p[rlen] != ':')
+			continue;
+
+		/*
+		 * fgets filled the whole buffer without reaching a newline: the
+		 * schemata line is longer than @line and was truncated, so we
+		 * would only see a prefix of this resource's domains. Fail
+		 * loudly rather than silently verifying part of the line.
+		 */
+		if (linelen == sizeof(line) - 1 && line[linelen - 1] != '\n') {
+			ret = -ENOBUFS;
+			break;
+		}
+
+		p += rlen + 1;
+		nl = strchr(p, '\n');
+		if (nl)
+			*nl = '\0';
+		/* Likewise refuse to return a truncated value into @buf. */
+		w = snprintf(buf, len, "%s", p);
+		if (w < 0 || (size_t)w >= len) {
+			ret = -ENOBUFS;
+			break;
+		}
+		ret = 0;
+		break;
+	}
+
+	fclose(fp);
+
+	return ret;
+}
+
 /*
  * create_bit_mask- Create bit mask from start, len pair
  * @start:	LSB of the mask
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 2/4] selftests/resctrl: Add MBA schemata write/read-back selftest
  2026-07-22  2:10 [PATCH 0/4] selftests/resctrl: MBA schemata ABI selftests for MPAM platforms Richard Cheng
  2026-07-22  2:10 ` [PATCH 1/4] selftests/resctrl: Add MBA schemata info selftest Richard Cheng
@ 2026-07-22  2:10 ` Richard Cheng
  2026-07-22  2:10 ` [PATCH 3/4] selftests/resctrl: Add MBA schemata invalid-write selftest Richard Cheng
  2026-07-22  2:10 ` [PATCH 4/4] selftests/resctrl: Add MBA schemata per-group isolation selftest Richard Cheng
  3 siblings, 0 replies; 5+ messages in thread
From: Richard Cheng @ 2026-07-22  2:10 UTC (permalink / raw)
  To: tony.luck, reinette.chatre, x86
  Cc: Dave.Martin, james.morse, babu.moger, shuah, linux-kernel,
	linux-kselftest, newtonl, kristinc, kobak, kaihengf, fenghuay,
	ltrager, Richard Cheng

Implement MBA schemata rw test, in a temporary control group,
write a few legal percentages to each MB domain, read each
back through the schemata then restore the maximum and remove the group.
Domain ids are taken from the schemata.

Add a resctrl_write_schemata() helper for the raw write.

Signed-off-by: Richard Cheng <icheng@nvidia.com>
---
 .../selftests/resctrl/mba_schemata_test.c     | 115 ++++++++++++++++++
 tools/testing/selftests/resctrl/resctrl.h     |   2 +
 .../testing/selftests/resctrl/resctrl_tests.c |   1 +
 tools/testing/selftests/resctrl/resctrlfs.c   |  36 ++++++
 4 files changed, 154 insertions(+)

diff --git a/tools/testing/selftests/resctrl/mba_schemata_test.c b/tools/testing/selftests/resctrl/mba_schemata_test.c
index b92011e42726..ff101b814982 100644
--- a/tools/testing/selftests/resctrl/mba_schemata_test.c
+++ b/tools/testing/selftests/resctrl/mba_schemata_test.c
@@ -150,3 +150,118 @@ struct resctrl_test mba_schemata_info_test = {
 	.feature_check = mba_schemata_feature_check,
 	.run_test = mba_schemata_info_run_test,
 };
+
+/* Read back the percentage for one MB domain from a group's schemata. */
+static int mb_read_domain(const char *ctrlgrp, const char *resource, int dom,
+			  unsigned int *out)
+{
+	unsigned int pct[MB_MAX_DOMAINS];
+	int ids[MB_MAX_DOMAINS], n, i;
+	char vals[MB_SCHEMATA_LEN];
+
+	if (resctrl_get_schemata(ctrlgrp, resource, vals, sizeof(vals)))
+		return -1;
+	n = parse_mb_schemata(vals, ids, pct, MB_MAX_DOMAINS);
+	for (i = 0; i < n; i++) {
+		if (ids[i] == dom) {
+			*out = pct[i];
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * writing a legal percentage to a domain takes effect
+ * and reads back, and restoring the maximum works.
+ * Domain ids come from the schemata directly.
+ */
+static int mba_schemata_rw_run_test(const struct resctrl_test *test,
+				    const struct user_params *uparams)
+{
+	static const unsigned int cand[] = { 10, 50, 90 };
+	unsigned int gran, pct[MB_MAX_DOMAINS], receive = 0;
+	int ids[MB_MAX_DOMAINS], n, i, j, ret, fail = 0;
+	char vals[MB_SCHEMATA_LEN], grp_path[256], line[64];
+	const char *grp = "mba_schemata_rw";
+
+	if (resource_info_unsigned_get(test->resource, "bandwidth_gran", &gran))
+		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;
+	}
+
+	snprintf(grp_path, sizeof(grp_path), "%s/%s", RESCTRL_PATH, grp);
+	if (mkdir(grp_path, 0755) && errno != EEXIST) {
+		ksft_print_msg("Fail: mkdir %s: %m\n", grp_path);
+		return 1;
+	}
+
+	for (i = 0; i < n; i++) {
+		for (j = 0; j < (int)ARRAY_SIZE(cand); j++) {
+			snprintf(line, sizeof(line), "%s:%d=%u",
+				 test->resource, ids[i], cand[j]);
+			ret = resctrl_write_schemata(grp, line);
+			if (ret) {
+				ksft_print_msg("Fail: write \"%s\" (ret=%d)\n", line, ret);
+				fail = 1;
+				continue;
+			}
+			if (mb_read_domain(grp, test->resource, ids[i], &receive)) {
+				ksft_print_msg("Fail: read back %s domain %d\n",
+					       test->resource, ids[i]);
+				fail = 1;
+				continue;
+			}
+			if (abs((int)receive - (int)cand[j]) > (int)gran) {
+				ksft_print_msg("Fail: %s domain %d: %u%% -> %u%% (gran %u)\n",
+					       test->resource, ids[i], cand[j], receive, gran);
+				fail = 1;
+			} else {
+				ksft_print_msg("Pass: %s domain %d %u%% -> read %u%%\n",
+					       test->resource, ids[i], cand[j], receive);
+			}
+		}
+
+		/* Restore the maximum and confirm. */
+		snprintf(line, sizeof(line), "%s:%d=%u",
+			 test->resource, ids[i], MB_PERCENT_MAX);
+		ret = resctrl_write_schemata(grp, line);
+		if (ret) {
+			ksft_print_msg("Fail: restore write \"%s\" (ret=%d)\n", line, ret);
+			fail = 1;
+		} else if (mb_read_domain(grp, test->resource, ids[i], &receive) ||
+			   receive != MB_PERCENT_MAX) {
+			ksft_print_msg("Fail: %s domain %d not restored to %u%% (received %u)\n",
+				       test->resource, ids[i], MB_PERCENT_MAX, receive);
+			fail = 1;
+		} else {
+			ksft_print_msg("Pass: %s domain %d restored to %u%%\n",
+				       test->resource, ids[i], MB_PERCENT_MAX);
+		}
+	}
+
+	rmdir(grp_path);
+
+	return fail;
+}
+
+struct resctrl_test mba_schemata_rw_test = {
+	.name = "MBA_SCHEMATA_RW",
+	.group = "MBA_SCHEMATA",
+	.resource = "MB",
+	.feature_check = mba_schemata_feature_check,
+	.run_test = mba_schemata_rw_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index a6ad25a08ae5..75d1ad5c6968 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -203,6 +203,7 @@ int get_cache_size(int cpu_no, const char *cache_type, unsigned long *cache_size
 int resource_info_unsigned_get(const char *resource, const char *filename, unsigned int *val);
 int resource_info_str_get(const char *resource, const char *filename, char *val, size_t len);
 int resctrl_get_schemata(const char *ctrlgrp, const char *resource, char *buf, size_t len);
+int resctrl_write_schemata(const char *ctrlgrp, const char *line);
 void ctrlc_handler(int signum, siginfo_t *info, void *ptr);
 int signal_handler_register(const struct resctrl_test *test);
 void signal_handler_unregister(void);
@@ -249,5 +250,6 @@ extern struct resctrl_test l3_cat_test;
 extern struct resctrl_test l3_noncont_cat_test;
 extern struct resctrl_test l2_noncont_cat_test;
 extern struct resctrl_test mba_schemata_info_test;
+extern struct resctrl_test mba_schemata_rw_test;
 
 #endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index 5d45ac95d988..9b3ac0437bf9 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -22,6 +22,7 @@ static struct resctrl_test *resctrl_tests[] = {
 	&l3_noncont_cat_test,
 	&l2_noncont_cat_test,
 	&mba_schemata_info_test,
+	&mba_schemata_rw_test,
 };
 
 static unsigned int detect_vendor(void)
diff --git a/tools/testing/selftests/resctrl/resctrlfs.c b/tools/testing/selftests/resctrl/resctrlfs.c
index 8b428a22496d..1196418461b5 100644
--- a/tools/testing/selftests/resctrl/resctrlfs.c
+++ b/tools/testing/selftests/resctrl/resctrlfs.c
@@ -521,6 +521,42 @@ int resctrl_get_schemata(const char *ctrlgrp, const char *resource,
 	return ret;
 }
 
+/*
+ * resctrl_write_schemata - Write a raw schemata line to a group's schemata file
+ * @ctrlgrp:	Control group name, or "" / NULL for the default group
+ * @line:	Full schemata line without newline, e.g. "MB:0=50"
+ *
+ * Unlike write_schemata(), the caller supplies the exact
+ * "<resource>:<domain>=<value>", so this works where the domain id is not the
+ * CPU's L3 cache id (e.g. ARM MPAM, whose MB domains are NUMA proximity ids).
+ *
+ * Return: 0 on success, or -errno on failure (e.g. -EINVAL for a value the
+ * kernel rejects).
+ */
+int resctrl_write_schemata(const char *ctrlgrp, const char *line)
+{
+	char path[1024], buf[1024];
+	int fd, len, ret = 0;
+
+	if (ctrlgrp && *ctrlgrp)
+		snprintf(path, sizeof(path), "%s/%s/schemata", RESCTRL_PATH, ctrlgrp);
+	else
+		snprintf(path, sizeof(path), "%s/schemata", RESCTRL_PATH);
+
+	len = snprintf(buf, sizeof(buf), "%s\n", line);
+	if (len < 0 || (size_t)len >= sizeof(buf))
+		return -EOVERFLOW;
+
+	fd = open(path, O_WRONLY);
+	if (fd < 0)
+		return -errno;
+	if (write(fd, buf, len) < 0)
+		ret = -errno;
+	close(fd);
+
+	return ret;
+}
+
 /*
  * create_bit_mask- Create bit mask from start, len pair
  * @start:	LSB of the mask
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 3/4] selftests/resctrl: Add MBA schemata invalid-write selftest
  2026-07-22  2:10 [PATCH 0/4] selftests/resctrl: MBA schemata ABI selftests for MPAM platforms Richard Cheng
  2026-07-22  2:10 ` [PATCH 1/4] selftests/resctrl: Add MBA schemata info selftest Richard Cheng
  2026-07-22  2:10 ` [PATCH 2/4] selftests/resctrl: Add MBA schemata write/read-back selftest Richard Cheng
@ 2026-07-22  2:10 ` Richard Cheng
  2026-07-22  2:10 ` [PATCH 4/4] selftests/resctrl: Add MBA schemata per-group isolation selftest Richard Cheng
  3 siblings, 0 replies; 5+ messages in thread
From: Richard Cheng @ 2026-07-22  2:10 UTC (permalink / raw)
  To: tony.luck, reinette.chatre, x86
  Cc: Dave.Martin, james.morse, babu.moger, shuah, linux-kernel,
	linux-kselftest, newtonl, kristinc, kobak, kaihengf, fenghuay,
	ltrager, Richard Cheng

The resctrl ABI requires invalid MB schemata writes to fail and report a
diagnostic in info/last_cmd_status.

Add MBA_SCHEMATA_INVAL. In a temporary control group it writes an
out-of-range value 101, malformed values abc, -5, and a non-existent
domain id, asserting each write fails and last_cmd_status contains the
expected substring. The bad-domain case checks errno only, as the kernel
does not update last_cmd_status there. A final write of the maximum
percentage must succeed and report "ok".

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

diff --git a/tools/testing/selftests/resctrl/mba_schemata_test.c b/tools/testing/selftests/resctrl/mba_schemata_test.c
index ff101b814982..0cd257dcec91 100644
--- a/tools/testing/selftests/resctrl/mba_schemata_test.c
+++ b/tools/testing/selftests/resctrl/mba_schemata_test.c
@@ -265,3 +265,126 @@ struct resctrl_test mba_schemata_rw_test = {
 	.feature_check = mba_schemata_feature_check,
 	.run_test = mba_schemata_rw_run_test,
 };
+
+/*
+ * Read the first line of info/last_cmd_status.
+ */
+static int read_last_cmd_status(char *status, size_t len)
+{
+	return resource_info_str_get("", "last_cmd_status", status, len);
+}
+
+/*
+ * invalid schemata writes (out-of-range value, malformed value, nonexistent
+ * domain) must be rejected with a diagnostic in info/last_cmd_status, and a
+ * subsequent valid write must still succeed and report "ok".
+ */
+static int mba_schemata_inval_run_test(const struct resctrl_test *test,
+				       const struct user_params *uparams)
+{
+	/* Rejected values and the stable last_cmd_status fragment expected. */
+	static const struct {
+		const char *val;
+		const char *substr;
+	} bad[] = {
+		{ "101",	"out of range" },
+		{ "abc",	"Invalid MB value" },
+		{ "-5",		"Invalid MB value" },
+	};
+	unsigned int pct[MB_MAX_DOMAINS];
+	int ids[MB_MAX_DOMAINS], n, i, ret, bad_dom, fail = 0;
+	char vals[MB_SCHEMATA_LEN], grp_path[256], line[64], status[256];
+	const char *grp = "mba_schemata_inval";
+
+	/* 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;
+	}
+
+	/* A domain id one past the largest real one cannot exist. */
+	bad_dom = ids[0];
+	for (i = 1; i < n; i++)
+		if (ids[i] > bad_dom)
+			bad_dom = ids[i];
+	bad_dom++;
+
+	snprintf(grp_path, sizeof(grp_path), "%s/%s", RESCTRL_PATH, grp);
+	if (mkdir(grp_path, 0755) && errno != EEXIST) {
+		ksft_print_msg("Fail: mkdir %s: %m\n", grp_path);
+		return 1;
+	}
+
+	for (i = 0; i < (int)ARRAY_SIZE(bad); i++) {
+		snprintf(line, sizeof(line), "%s:%d=%s",
+			 test->resource, ids[0], bad[i].val);
+		ret = resctrl_write_schemata(grp, line);
+		if (!ret) {
+			ksft_print_msg("Fail: invalid write \"%s\" was accepted\n",
+				       line);
+			fail = 1;
+			continue;
+		}
+		if (read_last_cmd_status(status, sizeof(status))) {
+			fail = 1;
+			continue;
+		}
+		if (!strstr(status, bad[i].substr)) {
+			ksft_print_msg("Fail: \"%s\": last_cmd_status \"%s\" lacks \"%s\"\n",
+				       line, status, bad[i].substr);
+			fail = 1;
+		}
+	}
+
+	/*
+	 * A write to a nonexistent domain also fails, but the kernel does not
+	 * record it in last_cmd_status, so assert on the write error only.
+	 * Use the maximum percentage so the rejection can only be due to the
+	 * domain id.
+	 */
+	snprintf(line, sizeof(line), "%s:%d=%u",
+		 test->resource, bad_dom, MB_PERCENT_MAX);
+	ret = resctrl_write_schemata(grp, line);
+	if (!ret) {
+		ksft_print_msg("Fail: write \"%s\" to nonexistent domain was accepted\n",
+			       line);
+		fail = 1;
+	}
+
+	/*
+	 * The rejected writes must not break a following valid write.
+	 */
+	snprintf(line, sizeof(line), "%s:%d=%u",
+		 test->resource, ids[0], MB_PERCENT_MAX);
+	ret = resctrl_write_schemata(grp, line);
+	if (ret) {
+		ksft_print_msg("Fail: valid write \"%s\" (ret=%d)\n", line, ret);
+		fail = 1;
+	} else if (read_last_cmd_status(status, sizeof(status))) {
+		fail = 1;
+	} else if (strcmp(status, "ok")) {
+		ksft_print_msg("Fail: valid write \"%s\": last_cmd_status \"%s\" != \"ok\"\n",
+			       line, status);
+		fail = 1;
+	}
+
+	rmdir(grp_path);
+
+	return fail;
+}
+
+struct resctrl_test mba_schemata_inval_test = {
+	.name = "MBA_SCHEMATA_INVAL",
+	.group = "MBA_SCHEMATA",
+	.resource = "MB",
+	.feature_check = mba_schemata_feature_check,
+	.run_test = mba_schemata_inval_run_test,
+};
diff --git a/tools/testing/selftests/resctrl/resctrl.h b/tools/testing/selftests/resctrl/resctrl.h
index 75d1ad5c6968..c32ab5c2cc56 100644
--- a/tools/testing/selftests/resctrl/resctrl.h
+++ b/tools/testing/selftests/resctrl/resctrl.h
@@ -251,5 +251,6 @@ extern struct resctrl_test l3_noncont_cat_test;
 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;
 
 #endif /* RESCTRL_H */
diff --git a/tools/testing/selftests/resctrl/resctrl_tests.c b/tools/testing/selftests/resctrl/resctrl_tests.c
index 9b3ac0437bf9..7a7184783e6a 100644
--- a/tools/testing/selftests/resctrl/resctrl_tests.c
+++ b/tools/testing/selftests/resctrl/resctrl_tests.c
@@ -23,6 +23,7 @@ static struct resctrl_test *resctrl_tests[] = {
 	&l2_noncont_cat_test,
 	&mba_schemata_info_test,
 	&mba_schemata_rw_test,
+	&mba_schemata_inval_test,
 };
 
 static unsigned int detect_vendor(void)
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH 4/4] selftests/resctrl: Add MBA schemata per-group isolation selftest
  2026-07-22  2:10 [PATCH 0/4] selftests/resctrl: MBA schemata ABI selftests for MPAM platforms Richard Cheng
                   ` (2 preceding siblings ...)
  2026-07-22  2:10 ` [PATCH 3/4] selftests/resctrl: Add MBA schemata invalid-write selftest Richard Cheng
@ 2026-07-22  2:10 ` Richard Cheng
  3 siblings, 0 replies; 5+ messages in thread
From: Richard Cheng @ 2026-07-22  2:10 UTC (permalink / raw)
  To: tony.luck, reinette.chatre, x86
  Cc: Dave.Martin, james.morse, babu.moger, shuah, linux-kernel,
	linux-kselftest, newtonl, kristinc, kobak, kaihengf, fenghuay,
	ltrager, Richard Cheng

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@nvidia.com>
---
 .../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


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-07-22  2:11 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-22  2:10 [PATCH 0/4] selftests/resctrl: MBA schemata ABI selftests for MPAM platforms Richard Cheng
2026-07-22  2:10 ` [PATCH 1/4] selftests/resctrl: Add MBA schemata info selftest Richard Cheng
2026-07-22  2:10 ` [PATCH 2/4] selftests/resctrl: Add MBA schemata write/read-back selftest Richard Cheng
2026-07-22  2:10 ` [PATCH 3/4] selftests/resctrl: Add MBA schemata invalid-write selftest Richard Cheng
2026-07-22  2:10 ` [PATCH 4/4] selftests/resctrl: Add MBA schemata per-group isolation selftest Richard Cheng

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.