From: Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>
To: igt-dev@lists.freedesktop.org
Cc: adam.miszczak@linux.intel.com, jakub1.kolakowski@intel.com,
lukasz.laguna@intel.com, michal.wajdeczko@intel.com,
Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>,
Kamil Konieczny <kamil.konieczny@linux.intel.com>
Subject: [PATCH v3 i-g-t 01/10] lib/igt_sysfs_choice: Add helpers for sysfs enumerated choice attributes
Date: Wed, 28 Jan 2026 19:08:07 +0100 [thread overview]
Message-ID: <20260128180819.1373376-2-marcin.bernatowicz@linux.intel.com> (raw)
In-Reply-To: <20260128180819.1373376-1-marcin.bernatowicz@linux.intel.com>
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #1: Type: text/plain; charset=y, Size: 14865 bytes --]
Introduce igt_sysfs_choice, a lightweight, fixed-size, no-malloc helper
for parsing and formatting sysfs "choice" attributes of the form:
"low [normal] high\n"
The helper provides parsing, lookup, formatting, mask conversion, and
intersection utilities for consistent handling of enumerated sysfs values.
Suggested-by: Michal Wajdeczko <michal.wajdeczko@intel.com>
Signed-off-by: Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>
Cc: Adam Miszczak <adam.miszczak@linux.intel.com>
Cc: Jakub Kolakowski <jakub1.kolakowski@intel.com>
Cc: Kamil Konieczny <kamil.konieczny@linux.intel.com>
Cc: Lukasz Laguna <lukasz.laguna@intel.com>
Cc: Michal Wajdeczko <michal.wajdeczko@intel.com>
---
v2:
- Corrected date
- Fix documentation formatting/description.
- Make igt_sysfs_choice_to_string() return error code instead of NULL.
- Use names_sz consistently.
- Make igt_sysfs_choice_format_mask() return error code instead of NULL.
Signed-off-by: Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>
---
lib/igt_sysfs_choice.c | 439 +++++++++++++++++++++++++++++++++++++++++
lib/igt_sysfs_choice.h | 52 +++++
lib/meson.build | 1 +
3 files changed, 492 insertions(+)
create mode 100644 lib/igt_sysfs_choice.c
create mode 100644 lib/igt_sysfs_choice.h
diff --git a/lib/igt_sysfs_choice.c b/lib/igt_sysfs_choice.c
new file mode 100644
index 000000000..5ce2a2b70
--- /dev/null
+++ b/lib/igt_sysfs_choice.c
@@ -0,0 +1,439 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+#include "igt_sysfs_choice.h"
+#include <ctype.h>
+#include <errno.h>
+#include "igt_core.h"
+#include "igt_sysfs.h"
+
+#define IGT_SYSFS_CHOICE_MAX_LEN 256
+#define IGT_SYSFS_CHOICE_MAX_TOKENS 16
+
+/**
+ * igt_sysfs_choice_parse() - parse sysfs enumerated choice buffer
+ * @buf: NUL-terminated buffer with sysfs contents
+ * @choice: output descriptor, must be non-NULL (can be zeroed)
+ *
+ * Parses a sysfs enumerated choice buffer, e.g.:
+ *
+ * "low [normal] high\n"
+ *
+ * into a token list and the index of the selected token.
+ *
+ * Parsing rules:
+ * - tokens are separated by ASCII whitespace
+ * - exactly one token must be wrapped in '[' and ']'
+ * - surrounding '[' and ']' are stripped from the selected token
+ * - empty tokens are treated as malformed input
+ *
+ * On entry, any previous contents of @choice are freed.
+ *
+ * Returns:
+ * 0 on success,
+ * -EINVAL malformed format (no tokens, no selected token, multiple
+ * selected tokens, unterminated '[' or ']'),
+ * -E2BIG on too many tokens or too small choice buffer size.
+ */
+int igt_sysfs_choice_parse(const char *buf, struct igt_sysfs_choice *choice)
+{
+ char *p, *tok_start;
+ bool selected_seen = false;
+ size_t num_tokens = 0;
+ int n, selected = -1;
+ bool is_selected;
+
+ igt_assert(buf && choice);
+
+ memset(choice, 0, sizeof(*choice));
+ n = snprintf(choice->buf, sizeof(choice->buf), "%s", buf);
+ if (igt_debug_on(n < 0))
+ return -EINVAL;
+ if (igt_debug_on((size_t)n >= sizeof(choice->buf)))
+ return -E2BIG;
+
+ choice->num_tokens = 0;
+ choice->selected = -1;
+ p = choice->buf;
+
+ while (*p) {
+ /* skip leading whitespace */
+ while (*p && isspace((unsigned char)*p))
+ p++;
+ if (!*p)
+ break;
+
+ is_selected = false;
+ tok_start = p;
+
+ if (*p == '[') {
+ is_selected = true;
+ p++;
+ tok_start = p;
+
+ if (selected_seen) {
+ igt_debug("choice-parse: multiple [selected] tokens: \"%s\"\n",
+ choice->buf);
+ return -EINVAL;
+ }
+ selected_seen = true;
+ }
+
+ /* walk until ']' or whitespace */
+ while (*p && !isspace((unsigned char)*p) && *p != ']')
+ p++;
+
+ if (is_selected) {
+ if (*p != ']') {
+ igt_debug("choice-parse: unterminated '[' in: \"%s\"\n",
+ choice->buf);
+ return -EINVAL;
+ }
+ }
+
+ /* terminate token */
+ if (*p) {
+ *p = '\0';
+ p++;
+ }
+
+ if (!*tok_start) {
+ igt_debug("choice-parse: empty token in: \"%s\"\n",
+ choice->buf);
+ return -EINVAL;
+ }
+
+ if (num_tokens >= IGT_SYSFS_CHOICE_MAX_TOKENS) {
+ igt_debug("choice-parse: too many tokens (>%d) in: \"%s\"\n",
+ IGT_SYSFS_CHOICE_MAX_TOKENS, choice->buf);
+ return -E2BIG;
+ }
+
+ choice->tokens[num_tokens] = tok_start;
+ if (is_selected)
+ selected = (int)num_tokens;
+
+ num_tokens++;
+ }
+
+ if (!num_tokens) {
+ igt_debug("choice-parse: no tokens in string: \"%s\"\n",
+ choice->buf);
+ return -EINVAL;
+ }
+
+ if (selected < 0) {
+ igt_debug("choice-parse: missing selected token ([...]) in: \"%s\"\n",
+ choice->buf);
+ return -EINVAL;
+ }
+
+ choice->num_tokens = num_tokens;
+ choice->selected = selected;
+
+ return 0;
+}
+
+/**
+ * igt_sysfs_choice_read() - read and parse a sysfs enumerated choice attribute
+ * @dirfd: directory file descriptor of the sysfs node
+ * @attr: attribute name relative to @dirfd
+ * @choice: output descriptor, must be non-NULL
+ *
+ * Reads the given sysfs attribute into a temporary buffer and parses it.
+ *
+ * Returns:
+ * 0 on success,
+ * negative errno-style value on read or parse error.
+ */
+int igt_sysfs_choice_read(int dirfd, const char *attr,
+ struct igt_sysfs_choice *choice)
+{
+ char buf[IGT_SYSFS_CHOICE_MAX_LEN];
+ int len;
+
+ len = igt_sysfs_read(dirfd, attr, buf, sizeof(buf) - 1);
+ if (len < 0)
+ return len;
+
+ buf[len] = '\0';
+
+ return igt_sysfs_choice_parse(buf, choice);
+}
+
+/**
+ * igt_sysfs_choice_selected() - Return selected token string
+ * @choice: Parsed choice
+ *
+ * Returns:
+ * Pointer to the selected token string, or NULL if no valid selection.
+ */
+const char *igt_sysfs_choice_selected(const struct igt_sysfs_choice *choice)
+{
+ if (!choice || choice->selected < 0 ||
+ (size_t)choice->selected >= choice->num_tokens)
+ return NULL;
+
+ return choice->tokens[choice->selected];
+}
+
+/**
+ * igt_sysfs_choice_to_string() - Render a parsed choice into string
+ * @choice: Parsed choice (tokens[] + selected index)
+ * @buf: Output buffer for formatted string
+ * @buf_sz: Size of @buf in bytes
+ *
+ * Formats the given @choice into the string:
+ *
+ * "low [normal] high"
+ *
+ * Tokens are emitted in the order stored in @choice->tokens. The
+ * selected token (choice->selected) is wrapped in '[' and ']'.
+ *
+ * Returns:
+ * 0 on success,
+ * -EINVAL if arguments are invalid,
+ * -E2BIG if @buf_sz is too small.
+ */
+int igt_sysfs_choice_to_string(const struct igt_sysfs_choice *choice,
+ char *buf, size_t buf_sz)
+{
+ bool first = true;
+ size_t pos = 0;
+ int n;
+
+ if (!choice || !buf || !buf_sz)
+ return -EINVAL;
+
+ buf[0] = '\0';
+
+ for (size_t i = 0; i < choice->num_tokens; i++) {
+ const char *name = choice->tokens[i];
+ bool is_selected = (choice->selected == (int)i);
+
+ if (!name)
+ continue;
+
+ n = snprintf(buf + pos, buf_sz - pos,
+ "%s%s%s%s",
+ first ? "" : " ",
+ is_selected ? "[" : "",
+ name,
+ is_selected ? "]" : "");
+
+ if (n < 0)
+ return -EINVAL;
+ if ((size_t)n >= buf_sz - pos)
+ return -E2BIG;
+
+ pos += (size_t)n;
+ first = false;
+ }
+
+ return 0;
+}
+
+/**
+ * igt_sysfs_choice_find() - find token index by name
+ * @choice: parsed choice struct
+ * @token: token to look for (plain name, without '[' / ']')
+ *
+ * Performs a case-sensitive comparison of @token against entries in
+ * @choice->tokens.
+ *
+ * Returns:
+ * index in [0..choice->num_tokens-1] on match,
+ * -1 if @token is not present or @choice/@token is NULL.
+ */
+int igt_sysfs_choice_find(const struct igt_sysfs_choice *choice,
+ const char *token)
+{
+ if (!choice || !token)
+ return -1;
+
+ for (size_t i = 0; i < choice->num_tokens; i++)
+ if (!strcmp(choice->tokens[i], token))
+ return (int)i;
+
+ return -1;
+}
+
+/**
+ * igt_sysfs_choice_to_mask() - map parsed tokens to bitmask + selection
+ * @choice: parsed choice struct
+ * @names: array of known token names
+ * @names_sz: number of elements in @names
+ * @mask: output bitmask of supported names
+ * @selected_idx: output index of selected token in @names, or -1 if selected
+ * token is not among @names
+ *
+ * Builds a bitmask of known tokens present in @choice and identifies the
+ * selected token, if it matches one of @names.
+ *
+ * Unknown tokens do not cause an error; they are ignored and not
+ * reflected in @mask. This keeps the API "loose": tests can still
+ * validate required choices while tolerating additional values.
+ *
+ * Returns:
+ * 0 on success,
+ * -EINVAL on bad input parameters.
+ */
+int igt_sysfs_choice_to_mask(const struct igt_sysfs_choice *choice,
+ const char * const *names, size_t names_sz,
+ unsigned int *mask, int *selected_idx)
+{
+ unsigned int m = 0;
+ int sel = -1, idx;
+
+ if (!choice || !names || !mask)
+ return -EINVAL;
+
+ for (size_t i = 0; i < names_sz; i++) {
+ const char *name = names[i];
+
+ if (!name)
+ continue;
+
+ idx = igt_sysfs_choice_find(choice, name);
+ if (idx >= 0) {
+ m |= 1u << i;
+ if (idx == choice->selected)
+ sel = (int)i;
+ }
+ }
+
+ *mask = m;
+ if (selected_idx)
+ *selected_idx = sel;
+
+ return 0;
+}
+
+/**
+ * igt_sysfs_choice_format_mask() - Format a bitmask as a space-separated list of names
+ * @buf: Output buffer
+ * @buf_sz: Size of @buf in bytes
+ * @names: Array of token names indexed by bit position
+ * @names_sz: Number of elements in @names
+ * @mask: Bitmask of available tokens
+ * @selected_idx: Index to highlight with brackets, or <0 for none
+ *
+ * Builds a space-separated list of all bits set in @mask, mapping bit positions
+ * to names in @names. If @selected_idx >= 0 and that bit is set, the token is
+ * wrapped in brackets, e.g. "low [normal] high".
+ *
+ * This function is best-effort by design:
+ * - If names[i] is NULL, it is formatted as "?".
+ * - Bits beyond @names_sz are ignored.
+ * Empty @mask results in an empty string.
+ *
+ * Returns:
+ * 0 on success,
+ * -EINVAL on invalid arguments,
+ * -E2BIG if @buf_sz is too small.
+ */
+int igt_sysfs_choice_format_mask(char *buf, size_t buf_sz,
+ const char *const *names,
+ size_t names_sz,
+ unsigned int mask,
+ int selected_idx)
+{
+ bool first = true;
+ size_t pos = 0;
+
+ if (!buf || !buf_sz || !names || !names_sz)
+ return -EINVAL;
+
+ buf[0] = '\0';
+
+ for (size_t idx = 0; idx < names_sz && mask; idx++) {
+ int n;
+ const char *name;
+ bool highlight;
+
+ if (!(mask & 1u)) {
+ mask >>= 1;
+ continue;
+ }
+
+ name = names[idx] ?: "?";
+ highlight = ((int)idx == selected_idx);
+ n = snprintf(buf + pos, buf_sz - pos, "%s%s%s%s",
+ first ? "" : " ",
+ highlight ? "[" : "",
+ name,
+ highlight ? "]" : "");
+ if (n < 0)
+ return -EINVAL;
+ if ((size_t)n >= buf_sz - pos)
+ return -E2BIG;
+
+ pos += (size_t)n;
+ first = false;
+ mask >>= 1;
+ }
+
+ return 0;
+}
+
+/**
+ * igt_sysfs_choice_intersect() - Restrict a choice set to tokens common with another
+ * @dst: Choice to be updated in place
+ * @other: Choice providing the allowed tokens
+ *
+ * Computes the intersection of the token sets in @dst and @other.
+ * The resulting @dst contains only tokens that appear in both choices,
+ * preserving their original order from @dst.
+ *
+ * If the previously selected token in @dst is still present after
+ * intersection, its index is updated accordingly. If it is not present,
+ * @dst->selected is set to -1.
+ *
+ * Returns:
+ * * 0 - success
+ * * -EINVAL - invalid arguments
+ * * -ENOENT - no common tokens
+ */
+int igt_sysfs_choice_intersect(struct igt_sysfs_choice *dst,
+ const struct igt_sysfs_choice *other)
+{
+ char *new_tokens[IGT_SYSFS_CHOICE_MAX_TOKENS];
+ const char *selected_name;
+ int new_selected = -1;
+ size_t new_n = 0;
+
+ if (!dst || !other)
+ return -EINVAL;
+
+ selected_name = (dst->selected >= 0 && dst->selected < dst->num_tokens) ?
+ dst->tokens[dst->selected] : NULL;
+
+ for (size_t i = 0; i < dst->num_tokens; i++) {
+ char *tok = dst->tokens[i];
+
+ if (igt_sysfs_choice_find(other, tok) < 0)
+ continue;
+
+ new_tokens[new_n] = tok;
+
+ if (selected_name && !strcmp(tok, selected_name))
+ new_selected = (int)new_n;
+
+ new_n++;
+ }
+
+ if (!new_n) {
+ dst->num_tokens = 0;
+ dst->selected = -1;
+ return -ENOENT;
+ }
+
+ for (size_t i = 0; i < new_n; i++)
+ dst->tokens[i] = new_tokens[i];
+
+ dst->num_tokens = new_n;
+ dst->selected = new_selected;
+
+ return 0;
+}
diff --git a/lib/igt_sysfs_choice.h b/lib/igt_sysfs_choice.h
new file mode 100644
index 000000000..b354c774a
--- /dev/null
+++ b/lib/igt_sysfs_choice.h
@@ -0,0 +1,52 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+#ifndef __IGT_SYSFS_CHOICE_H__
+#define __IGT_SYSFS_CHOICE_H__
+
+#include <stddef.h>
+#include <stdbool.h>
+
+#define IGT_SYSFS_CHOICE_MAX_LEN 256
+#define IGT_SYSFS_CHOICE_MAX_TOKENS 16
+
+/**
+ * struct igt_sysfs_choice - parsed sysfs enumerated choice attribute
+ * @tokens: array of token strings
+ * @num_tokens: number of entries in @tokens
+ * @selected: index of the active token in @tokens, or -1 if invalid
+ *
+ * This struct represents a sysfs enumerated choice attribute, for example:
+ *
+ * "low [normal] high\n"
+ *
+ * After parsing, @tokens point to "low", "normal", "high" and
+ * @selected will be 1 (the index of "normal").
+ */
+struct igt_sysfs_choice {
+ char buf[IGT_SYSFS_CHOICE_MAX_LEN];
+ char *tokens[IGT_SYSFS_CHOICE_MAX_TOKENS];
+ size_t num_tokens;
+ int selected; /* index into tokens[], or -1 */
+};
+
+int igt_sysfs_choice_parse(const char *buf, struct igt_sysfs_choice *choice);
+int igt_sysfs_choice_read(int dirfd, const char *attr,
+ struct igt_sysfs_choice *choice);
+const char *igt_sysfs_choice_selected(const struct igt_sysfs_choice *choice);
+int igt_sysfs_choice_to_string(const struct igt_sysfs_choice *choice,
+ char *buf, size_t buf_sz);
+int igt_sysfs_choice_find(const struct igt_sysfs_choice *choice,
+ const char *token);
+int igt_sysfs_choice_to_mask(const struct igt_sysfs_choice *choice,
+ const char *const *names, size_t names_sz,
+ unsigned int *mask, int *selected_idx);
+int igt_sysfs_choice_format_mask(char *buf, size_t buf_sz,
+ const char *const *names,
+ size_t names_sz, unsigned int mask,
+ int selected_idx);
+int igt_sysfs_choice_intersect(struct igt_sysfs_choice *dst,
+ const struct igt_sysfs_choice *other);
+
+#endif /* __IGT_SYSFS_CHOICE_H__ */
diff --git a/lib/meson.build b/lib/meson.build
index 1a569ba52..83569e8d2 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -44,6 +44,7 @@ lib_sources = [
'igt_stats.c',
'igt_syncobj.c',
'igt_sysfs.c',
+ 'igt_sysfs_choice.c',
'igt_sysrq.c',
'igt_taints.c',
'igt_thread.c',
--
2.43.0
next prev parent reply other threads:[~2026-01-28 18:08 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-01-28 18:08 [PATCH v3 i-g-t 00/10] Xe SR-IOV admin scheduling helpers and test updates Marcin Bernatowicz
2026-01-28 18:08 ` Marcin Bernatowicz [this message]
2026-01-29 8:19 ` [PATCH v3 i-g-t 01/10] lib/igt_sysfs_choice: Add helpers for sysfs enumerated choice attributes Laguna, Lukasz
2026-01-28 18:08 ` [PATCH v3 i-g-t 02/10] lib/tests/igt_sysfs_choice: Add test coverage Marcin Bernatowicz
2026-01-29 8:19 ` Laguna, Lukasz
2026-01-28 18:08 ` [PATCH v3 i-g-t 03/10] lib/xe/xe_sriov_provisioning: Add string conversion helpers for scheduling priority Marcin Bernatowicz
2026-01-28 18:08 ` [PATCH v3 i-g-t 04/10] lib/xe/xe_sriov_provisioning: Add sched priority mask to string helper Marcin Bernatowicz
2026-01-29 8:19 ` Laguna, Lukasz
2026-01-28 18:08 ` [PATCH v3 i-g-t 05/10] lib/igt_sriov_device: Add helper for PF/VF sysfs path formatting Marcin Bernatowicz
2026-01-28 18:08 ` [PATCH v3 i-g-t 06/10] lib/xe/xe_sriov_admin: Add SR-IOV admin sysfs accessors Marcin Bernatowicz
2026-01-29 8:21 ` Laguna, Lukasz
2026-01-28 18:08 ` [PATCH v3 i-g-t 07/10] tests/intel/xe_sriov_scheduling: Avoid assert on scheduling params restore in cleanup Marcin Bernatowicz
2026-01-28 18:08 ` [PATCH v3 i-g-t 08/10] tests/intel/xe_sriov_scheduling: Prefer SR-IOV admin sysfs accessors Marcin Bernatowicz
2026-01-28 18:08 ` [PATCH v3 i-g-t 09/10] tests/intel/xe_pmu: " Marcin Bernatowicz
2026-01-28 18:08 ` [PATCH v3 i-g-t 10/10] tests/intel/xe_sriov_admin: Add SR-IOV admin sysfs scheduling attributes tests Marcin Bernatowicz
2026-01-29 8:21 ` Laguna, Lukasz
2026-01-28 20:32 ` ✓ Xe.CI.BAT: success for Xe SR-IOV admin scheduling helpers and test updates (rev3) Patchwork
2026-01-28 20:47 ` ✗ i915.CI.BAT: failure " Patchwork
2026-01-29 10:11 ` Bernatowicz, Marcin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260128180819.1373376-2-marcin.bernatowicz@linux.intel.com \
--to=marcin.bernatowicz@linux.intel.com \
--cc=adam.miszczak@linux.intel.com \
--cc=igt-dev@lists.freedesktop.org \
--cc=jakub1.kolakowski@intel.com \
--cc=kamil.konieczny@linux.intel.com \
--cc=lukasz.laguna@intel.com \
--cc=michal.wajdeczko@intel.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox