public inbox for igt-dev@lists.freedesktop.org
 help / color / mirror / Atom feed
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


  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