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 <[email protected]> --- .../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

