public inbox for igt-dev@lists.freedesktop.org
 help / color / mirror / Atom feed
From: "Laguna, Lukasz" <lukasz.laguna@intel.com>
To: Marcin Bernatowicz <marcin.bernatowicz@linux.intel.com>,
	<igt-dev@lists.freedesktop.org>
Cc: <adam.miszczak@linux.intel.com>, <jakub1.kolakowski@intel.com>,
	<michal.wajdeczko@intel.com>,
	Kamil Konieczny <kamil.konieczny@linux.intel.com>
Subject: Re: [PATCH v3 i-g-t 01/10] lib/igt_sysfs_choice: Add helpers for sysfs enumerated choice attributes
Date: Thu, 29 Jan 2026 09:19:08 +0100	[thread overview]
Message-ID: <3df22e2b-1c12-41b1-a89f-e25f6c962061@intel.com> (raw)
In-Reply-To: <20260128180819.1373376-2-marcin.bernatowicz@linux.intel.com>


On 1/28/2026 19:08, Marcin Bernatowicz wrote:
> 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>

Reviewed-by: 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',

  reply	other threads:[~2026-01-29  8:19 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 ` [PATCH v3 i-g-t 01/10] lib/igt_sysfs_choice: Add helpers for sysfs enumerated choice attributes Marcin Bernatowicz
2026-01-29  8:19   ` Laguna, Lukasz [this message]
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=3df22e2b-1c12-41b1-a89f-e25f6c962061@intel.com \
    --to=lukasz.laguna@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=marcin.bernatowicz@linux.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