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',
next prev parent 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