igt-dev.lists.freedesktop.org archive mirror
 help / color / mirror / Atom feed
From: Louis Chauvet <louis.chauvet@bootlin.com>
To: Mark Yacoub <markyacoub@google.com>, igt-dev@lists.freedesktop.org
Cc: kamil.konieczny@linux.intel.com
Subject: Re: [PATCH v2] lib/igt_rc: Introduce generic config parser
Date: Fri, 31 Jul 2026 19:48:26 +0200	[thread overview]
Message-ID: <2a7b89cc-977d-47f9-8b6b-1aa0fcc64ea7@bootlin.com> (raw)
In-Reply-To: <20260720185026.93121-1-markyacoub@google.com>



On 7/20/26 20:50, Mark Yacoub wrote:
> Currently, libraries like unigraf explicitly rely on GKeyFile
> for reading configuration from .igtrc. Android builds do not
> natively supply glib, meaning these tools cannot be cleanly
> compiled.
> 
> This patch abstracts config parsing into a dedicated `igt_rc` module.
> On standard Linux, `igt_rc` seamlessly wraps glib's native methods.
> On Android environments, `igt_rc` natively parses `.igtrc` using
> a stripped-down, thread-safe linked-list implementation (lib/android/igt_rc.c).

Can you add documentation to all public functions? I think you must at 
least mention who is responsible to free which pointer.

> v2:
>   - Drop the GKeyFile abstraction fakes from android/glib.h (Review feedback)
>   - Introduce a generic wrapper (igt_rc.h) instead of modifying glib.h.
>   - Provide a pure glib backend for Linux to avoid regressions.
>   - Isolate Android parsing implementation cleanly into lib/android/igt_rc.c.
> ---
>   lib/android/igt_rc.c         | 172 +++++++++++++++++++++++++++++++++++
>   lib/igt_rc.c                 |  61 +++++++++++++
>   lib/igt_rc.h                 |   5 +
>   lib/meson.build              |   1 +
>   lib/vendor/unigraf/unigraf.c |  92 ++++++++-----------
>   5 files changed, 279 insertions(+), 52 deletions(-)
>   create mode 100644 lib/android/igt_rc.c
>   create mode 100644 lib/igt_rc.c
> 
> diff --git a/lib/android/igt_rc.c b/lib/android/igt_rc.c
> new file mode 100644
> index 000000000..6d9b348f7
> --- /dev/null
> +++ b/lib/android/igt_rc.c

I think the goal is to avoid all android-specific implementation, those 
functions should be in the generic igt_rc.c I think.

> @@ -0,0 +1,172 @@
> +#include "igt_rc.h"
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <strings.h>
> +#include <ctype.h>
> +#include <pthread.h>
> +
> +struct igt_key_entry {
> +	char *group;
> +	char *key;
> +	char *value;
> +	struct igt_key_entry *next;
> +};
> +
> +static struct igt_key_entry *rc_entries = NULL;
> +static pthread_once_t rc_once_control = PTHREAD_ONCE_INIT;
> +
> +static char *trim_whitespace(char *str)
> +{
> +	char *end;
> +
> +	while (isspace((unsigned char)*str))
> +		str++;
> +
> +	if (*str == 0)
> +		return str;
> +
> +	end = str + strlen(str) - 1;
> +	while (end > str && isspace((unsigned char)*end))
> +		end--;
> +
> +	end[1] = '\0';
> +	return str;
> +}
> +
> +static void load_igtrc_once(void)
> +{
> +	FILE *fp;
> +	char *line = NULL;
> +	size_t len = 0;
> +	ssize_t read;
> +	char *current_group = NULL;
> +	struct igt_key_entry *tail = NULL;
> +	char path[512];
> +
> +	snprintf(path, sizeof(path), "%s/.igtrc", "/data/local/tmp/igt");

I think you must keep the behavior of igt_load_igtrc to choose the file 
and at least keep a way to override this hardcoded path with an 
environment variable.

> +	fp = fopen(path, "r");
> +	if (!fp)
> +		return;
> +
> +	while ((read = getline(&line, &len, fp)) != -1) {
> +		char *trimmed = trim_whitespace(line);
> +
> +		if (*trimmed == '\0' || *trimmed == '#' || *trimmed == ';')

Can you use [0] instead of dereference? It is easier to read.

> +			continue;
> +
> +		if (trimmed[0] == '[' && trimmed[strlen(trimmed) - 1] == ']') {
> +			free(current_group);
> +			trimmed[strlen(trimmed) - 1] = '\0';
> +			current_group = strdup(trimmed + 1);
> +			continue;
> +		}
> +
> +		if (current_group) {
> +			char *eq = strchr(trimmed, '=');
> +
> +			if (eq) {
> +				char *key;
> +				char *value;
> +				struct igt_key_entry *entry;
> +
> +				*eq = '\0';

Can you store here the "value" pointer?

value = eq+1;

> +				key = trim_whitespace(trimmed);
> +				value = trim_whitespace(eq + 1);

This way it is easier to understand here that you are trimming a valid data.

> +
> +				entry = calloc(1, sizeof(*entry));
> +				entry->group = strdup(current_group);
> +				entry->key = strdup(key);
> +				entry->value = strdup(value);
> +				entry->next = NULL;
> +
> +				if (!rc_entries) {
> +					rc_entries = entry;
> +					tail = entry;
> +				} else {
> +					tail->next = entry;
> +					tail = entry;
> +				}

I think you could use igt_list.h to avoid this manual manipulation.

> +			}
> +		}
> +	}
> +
> +	free(current_group);
> +	free(line);
> +	fclose(fp);
> +}
> +
> +__attribute__((destructor))
> +static void free_igtrc(void)
> +{
> +	struct igt_key_entry *curr, *next;
> +
> +	curr = rc_entries;
> +	while (curr) {
> +		next = curr->next;
> +		free(curr->group);
> +		free(curr->key);
> +		free(curr->value);
> +		free(curr);
> +		curr = next;
> +	}

Same thing, I think you can use igt_list_for_each_entry_safe.

> +	rc_entries = NULL;
> +}
> +
> +char *igt_rc_get_string(const char *group_name, const char *key)
> +{
> +	char *last_match = NULL;
> +	struct igt_key_entry *curr;
> +
> +	pthread_once(&rc_once_control, load_igtrc_once);
> +
> +	curr = rc_entries;
> +	while (curr) {

igt_list_for_each_entry

> +		if (strcmp(curr->group, group_name) == 0 && strcmp(curr->key, key) == 0)
> +			last_match = curr->value;
> +		curr = curr->next;
> +	}
> +
> +	return last_match ? strdup(last_match) : NULL;
> +}
> +
> +bool igt_rc_get_boolean(const char *group_name, const char *key, bool *out)
> +{
> +	char *val = igt_rc_get_string(group_name, key);
> +
> +	if (!val)
> +		return false;
> +
> +	if (strcasecmp(val, "true") == 0 || strcmp(val, "1") == 0) {
> +		*out = true;
> +	} else if (strcasecmp(val, "false") == 0 || strcmp(val, "0") == 0) {
> +		*out = false;
> +	} else {
> +		free(val);
> +		return false;
> +	}
> +
> +	free(val);
> +	return true;
> +}
> +
> +int igt_rc_get_integer(const char *group_name, const char *key, int *out)
> +{
> +	char *val = igt_rc_get_string(group_name, key);
> +	char *endptr;
> +	long lval;
> +
> +	if (!val)
> +		return false;
> +
> +	lval = strtol(val, &endptr, 10);
> +	if (*endptr != '\0') {
> +		free(val);
> +		return false;
> +	}
> +
> +	*out = (int)lval;
> +	free(val);
> +	return true;

I think you should change the return type of the function to be a boolean?

> +}
> diff --git a/lib/igt_rc.c b/lib/igt_rc.c
> new file mode 100644
> index 000000000..31eb07bc0
> --- /dev/null
> +++ b/lib/igt_rc.c
> @@ -0,0 +1,61 @@
> +#include "igt_rc.h"
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <string.h>
> +#include <strings.h>
> +#include <ctype.h>
> +
> +#include <glib.h>
> +
> +char *igt_rc_get_string(const char *group_name, const char *key)
> +{
> +	GError *error = NULL;

The goal is to completly replace rc parsing from glib by a custom parser 
(please correct me if I am wrong @Kamil).

So I think here you can just copy/paste the code you created for android.

And I think there are more places where you should replace 
g_key_file_get_* functions.

> +	char *val;
> +
> +	if (!igt_key_file)
> +		return NULL;
> +
> +	val = g_key_file_get_string(igt_key_file, group_name, key, &error);
> +	if (error) {
> +		g_error_free(error);
> +		return NULL;
> +	}
> +
> +	return val;
> +}
> +
> +bool igt_rc_get_boolean(const char *group_name, const char *key, bool *out)
> +{
> +	GError *error = NULL;
> +	gboolean val;
> +
> +	if (!igt_key_file)
> +		return false;
> +
> +	val = g_key_file_get_boolean(igt_key_file, group_name, key, &error);
> +	if (error) {
> +		g_error_free(error);
> +		return false;
> +	}
> +
> +	*out = val;
> +	return true;
> +}
> +
> +int igt_rc_get_integer(const char *group_name, const char *key, int *out)
> +{
> +	GError *error = NULL;
> +	int val;
> +
> +	if (!igt_key_file)
> +		return false;
> +
> +	val = g_key_file_get_integer(igt_key_file, group_name, key, &error);
> +	if (error) {
> +		g_error_free(error);
> +		return false;
> +	}
> +
> +	*out = val;
> +	return true;
> +}
> diff --git a/lib/igt_rc.h b/lib/igt_rc.h
> index d871b3b26..a0942f8d0 100644
> --- a/lib/igt_rc.h
> +++ b/lib/igt_rc.h
> @@ -1,3 +1,4 @@
> +#include <stdbool.h>
>   /*
>    * Copyright © 2017 Intel Corporation
>    *
> @@ -33,4 +34,8 @@
>   
>   extern GKeyFile *igt_key_file;
>   
> +char *igt_rc_get_string(const char *group_name, const char *key);
> +bool igt_rc_get_boolean(const char *group_name, const char *key, bool *out);
> +int igt_rc_get_integer(const char *group_name, const char *key, int *out);
> +
>   #endif /* IGT_RC_H */
> diff --git a/lib/meson.build b/lib/meson.build
> index 8db9fffde..20c2f34d7 100644
> --- a/lib/meson.build
> +++ b/lib/meson.build
> @@ -97,6 +97,7 @@ lib_sources = [
>   	'igt_kms.c',
>   	'igt_fb.c',
>   	'igt_core.c',
> +	'igt_rc.c',
>   	'igt_dir.c',
>   	'igt_draw.c',
>   	'igt_list.c',
> diff --git a/lib/vendor/unigraf/unigraf.c b/lib/vendor/unigraf/unigraf.c
> index 30ee3c72b..64268db34 100644
> --- a/lib/vendor/unigraf/unigraf.c
> +++ b/lib/vendor/unigraf/unigraf.c
> @@ -364,7 +364,6 @@ int unigraf_get_connector_id_by_stream(int drm_fd, int stream_id)
>   bool unigraf_open_device(int drm_fd)
>   {
>   	TSI_RESULT r;
> -	GError *cfg_error = NULL;
>   	char *cfg_device = NULL;
>   	char *cfg_role = NULL;
>   	char *cfg_input = NULL;
> @@ -382,63 +381,52 @@ bool unigraf_open_device(int drm_fd)
>   
>   	unigraf_init();
>   
> -	if (igt_key_file) {
> -		cfg_device = g_key_file_get_string(igt_key_file, UNIGRAF_CONFIG_GROUP,
> -						   UNIGRAF_CONFIG_DEVICE_NAME, &cfg_error);
> -		if (cfg_error) {
> -			unigraf_debug("No device name configured, uses first device available.\n");
> -			cfg_device = NULL;
> -		}
> +	cfg_device = igt_rc_get_string(UNIGRAF_CONFIG_GROUP,
> +				       UNIGRAF_CONFIG_DEVICE_NAME);
> +	if (!cfg_device) {
> +		unigraf_debug("No device name configured, uses first device available.\n");
> +		cfg_device = NULL;
> +	}
>   
> -		cfg_error = NULL;
> -		cfg_role = g_key_file_get_string(igt_key_file, UNIGRAF_CONFIG_GROUP,
> -						 UNIGRAF_CONFIG_DEVICE_ROLE, &cfg_error);
> -		if (cfg_error) {
> -			unigraf_debug("No device role configured.\n");
> -			cfg_role = NULL;
> -		}
> +	cfg_role = igt_rc_get_string(UNIGRAF_CONFIG_GROUP,
> +				     UNIGRAF_CONFIG_DEVICE_ROLE);
> +	if (!cfg_role) {
> +		unigraf_debug("No device role configured.\n");
> +		cfg_role = NULL;
> +	}
>   
> -		cfg_error = NULL;
> -		cfg_input = g_key_file_get_string(igt_key_file, UNIGRAF_CONFIG_GROUP,
> -						  UNIGRAF_CONFIG_INPUT_NAME, &cfg_error);
> -		if (cfg_error) {
> -			unigraf_debug("No input name configured.\n");
> -			cfg_input = NULL;
> -		}
> +	cfg_input = igt_rc_get_string(UNIGRAF_CONFIG_GROUP,
> +				      UNIGRAF_CONFIG_INPUT_NAME);
> +	if (!cfg_input) {
> +		unigraf_debug("No input name configured.\n");
> +		cfg_input = NULL;
> +	}
>   
> -		cfg_error = NULL;
> -		unigraf_connector_name = g_key_file_get_string(igt_key_file, UNIGRAF_CONFIG_GROUP,
> -							       UNIGRAF_CONFIG_CONNECTOR_NAME,
> -							       &cfg_error);
> -		if (cfg_error) {
> -			unigraf_debug("No connector name configured, will autodetect.\n");
> -			unigraf_connector_name = NULL;
> -		}
> +	unigraf_connector_name = igt_rc_get_string(UNIGRAF_CONFIG_GROUP,
> +						   UNIGRAF_CONFIG_CONNECTOR_NAME);
> +	if (!unigraf_connector_name) {
> +		unigraf_debug("No connector name configured, will autodetect.\n");
> +		unigraf_connector_name = NULL;
> +	}
>   
> -		cfg_error = NULL;
> -		cfg_edid_name = g_key_file_get_string(igt_key_file, UNIGRAF_CONFIG_GROUP,
> -						      UNIGRAF_CONFIG_EDID_NAME, &cfg_error);
> -		if (cfg_error) {
> -			unigraf_debug("No default EDID set, use IGT default.\n");
> -			cfg_edid_name = NULL;
> -		}
> +	cfg_edid_name = igt_rc_get_string(UNIGRAF_CONFIG_GROUP,
> +					  UNIGRAF_CONFIG_EDID_NAME);
> +	if (!cfg_edid_name) {
> +		unigraf_debug("No default EDID set, use IGT default.\n");

While on it, can you correct the typo `use` => `using`?

> +		cfg_edid_name = NULL;
> +	}
>   
> -		cfg_error = NULL;
> -		unigraf_crc = g_key_file_get_boolean(igt_key_file, UNIGRAF_CONFIG_GROUP,
> -						     UNIGRAF_CONFIG_USE_CRC_NAME, &cfg_error);
> -		if (cfg_error) {
> -			unigraf_debug("CRC usage not configured, using unigraf CRC.\n");
> -			unigraf_crc = true;
> -		}
> +	if (!igt_rc_get_boolean(UNIGRAF_CONFIG_GROUP,
> +				UNIGRAF_CONFIG_USE_CRC_NAME, &unigraf_crc)) {
> +		unigraf_debug("CRC usage not configured, using unigraf CRC.\n");
> +		unigraf_crc = true;
> +	}
>   
> -		cfg_error = NULL;
> -		unigraf_stream_count = g_key_file_get_integer(igt_key_file, UNIGRAF_CONFIG_GROUP,
> -							      UNIGRAF_CONFIG_MST_STREAM_COUNT,
> -							      &cfg_error);
> -		if (cfg_error) {
> -			unigraf_debug("MST usage not configured, using SST.\n");
> -			unigraf_stream_count = 0;
> -		}
> +	if (!igt_rc_get_integer(UNIGRAF_CONFIG_GROUP,
> +				UNIGRAF_CONFIG_MST_STREAM_COUNT,
> +				&unigraf_stream_count)) {
> +		unigraf_debug("MST usage not configured, using SST.\n");
> +		unigraf_stream_count = 0;
>   	}
>   
>   	unigraf_assert(TSIX_DEV_RescanDevices(0, TSI_DEVCAP_VIDEO_CAPTURE, 0));


  parent reply	other threads:[~2026-07-31 17:49 UTC|newest]

Thread overview: 24+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-15 19:29 [PATCH 1/3] android: Implement lightweight GKeyFile INI string parser in glib shim Mark Yacoub
2026-07-15 19:29 ` [PATCH 2/3] tests/unigraf: Fix DRM master and heap memory leaks in tests Mark Yacoub
2026-07-16 14:54   ` Louis Chauvet
2026-07-15 19:29 ` [PATCH 3/3] tests/unigraf: Enhance link rate support checking and hardware retrain timing Mark Yacoub
2026-07-17  8:13   ` Louis Chauvet
2026-07-20 20:53   ` [PATCH v2] " Mark Yacoub
2026-07-24 14:06     ` Mark Yacoub
2026-07-31 17:48     ` Louis Chauvet
2026-07-15 20:27 ` ✓ Xe.CI.BAT: success for series starting with [1/3] android: Implement lightweight GKeyFile INI string parser in glib shim Patchwork
2026-07-15 20:54 ` ✓ i915.CI.BAT: " Patchwork
2026-07-15 23:40 ` ✓ Xe.CI.FULL: " Patchwork
2026-07-16  3:27 ` ✓ i915.CI.Full: " Patchwork
2026-07-17  8:20 ` [PATCH 1/3] " Louis Chauvet
2026-07-17 10:18   ` Kamil Konieczny
2026-07-17 19:23     ` Louis Chauvet
2026-07-20 12:25       ` Kamil Konieczny
2026-07-17 10:27 ` Kamil Konieczny
2026-07-20 18:50 ` [PATCH v2] lib/igt_rc: Introduce generic config parser Mark Yacoub
2026-07-24 14:06   ` Mark Yacoub
2026-07-31 17:48   ` Louis Chauvet [this message]
2026-07-21  3:25 ` ✓ Xe.CI.BAT: success for series starting with [v2] lib/igt_rc: Introduce generic config parser (rev3) Patchwork
2026-07-21  3:53 ` ✓ i915.CI.BAT: " Patchwork
2026-07-21 11:48 ` ✓ Xe.CI.FULL: " Patchwork
2026-07-21 17:40 ` ✓ i915.CI.Full: " Patchwork

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=2a7b89cc-977d-47f9-8b6b-1aa0fcc64ea7@bootlin.com \
    --to=louis.chauvet@bootlin.com \
    --cc=igt-dev@lists.freedesktop.org \
    --cc=kamil.konieczny@linux.intel.com \
    --cc=markyacoub@google.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;
as well as URLs for NNTP newsgroup(s).