public inbox for igt-dev@lists.freedesktop.org
 help / color / mirror / Atom feed
From: Louis Chauvet <louis.chauvet@bootlin.com>
To: Mohammed Bilal <mohammed.bilal@intel.com>, igt-dev@lists.freedesktop.org
Cc: kunal1.joshi@intel.com
Subject: Re: [PATCH i-g-t v1 20/25] lib/chamelium/v3: Add extended API for Chamelium v3 HPD, EDID, Frames, Color & Audio
Date: Wed, 29 Apr 2026 12:44:10 +0200	[thread overview]
Message-ID: <1d5948a2-7573-48a6-a07c-3a05a9f1b8f6@bootlin.com> (raw)
In-Reply-To: <20260428044644.257001-21-mohammed.bilal@intel.com>



On 4/28/26 06:46, Mohammed Bilal wrote:
> This commit adds the extended Chamelium v3 wrapper API functions needed
> for HPD, EDID management, frame capture, color verification, and audio
> testing with the Chamelium v3 hardware.
> 
> Signed-off-by: Mohammed Bilal <mohammed.bilal@intel.com>
> ---
>   lib/chamelium/v3/igt_chamelium.c | 962 ++++++++++++++++++++++++++++++-
>   lib/chamelium/v3/igt_chamelium.h | 195 +++++++
>   2 files changed, 1155 insertions(+), 2 deletions(-)
> 
> diff --git a/lib/chamelium/v3/igt_chamelium.c b/lib/chamelium/v3/igt_chamelium.c
> index 8fad88e74..4d4eef1f7 100644
> --- a/lib/chamelium/v3/igt_chamelium.c
> +++ b/lib/chamelium/v3/igt_chamelium.c
> @@ -1,10 +1,16 @@
>   // SPDX-License-Identifier: MIT
>   
> +#include <string.h>
> +#include <time.h>
> +#include <unistd.h>
> +#include <xf86drm.h>
> +#include <xf86drmMode.h>
>   #include <xmlrpc-c/base.h>
>   #include <xmlrpc-c/client.h>
>   
>   #include "igt_chamelium.h"
>   #include "igt_core.h"
> +#include "igt_kms.h"
>   #include "igt_rc.h"
>   
>   #define CHAMELIUM_CONFIG_SECTION "Chameliumv3"
> @@ -70,13 +76,21 @@ struct igt_chamelium_v3 *chamelium_v3_init(char *url)
>   	memset(&clientparms, 0, sizeof(clientparms));
>   	memset(&curlparms, 0, sizeof(curlparms));
>   
> -	/* curl's timeout is in milliseconds */
> -	curlparms.timeout = 10 * 1000;
> +	/* curl's timeout is in milliseconds - 60s for large frame transfers */
> +	curlparms.timeout = 60 * 1000;
>   
>   	clientparms.transport = "curl";
>   	clientparms.transportparmsP = &curlparms;
>   	clientparms.transportparm_size = XMLRPC_CXPSIZE(timeout);
>   
> +	/*
> +	 * Increase the XML-RPC response size limit to 128MB to handle
> +	 * DumpPixels responses for high-resolution frames (e.g. 4K/8K).
> +	 * A 3840x2160 RGB frame is ~24MB raw, ~33MB base64 encoded.
> +	 * The default xmlrpc-c limit is 15MB which is too small.
> +	 */
> +	xmlrpc_limit_set(XMLRPC_XML_SIZE_LIMIT_ID, 128 * 1024 * 1024);
> +
>   	/* Setup the libxmlrpc context */
>   	xmlrpc_env_init(&chamelium->env);
>   	xmlrpc_client_setup_global_const(&chamelium->env);
> @@ -182,6 +196,44 @@ static xmlrpc_value *__chamelium_rpc(struct igt_chamelium_v3 *chamelium,
>   	return res;
>   }
>   
> +/**
> + * __chamelium_rpc_try - Call a remote function, returning NULL on failure
> + *
> + * Unlike __chamelium_rpc, this does NOT assert on RPC failure.
> + * Used for polling functions like GetVideoParams where transient
> + * failures (e.g. "Link has been severed") are expected during
> + * video stabilization.
> + *
> + * Returns a xmlrpc_value on success, or NULL on failure.
> + */
> +static xmlrpc_value *__chamelium_rpc_try(struct igt_chamelium_v3 *chamelium,
> +					 const char *method_name,
> +					 const char *format_str,
> +					 ...)
> +{
> +	xmlrpc_value *res;
> +	va_list va_args;
> +
> +	if (chamelium->env.fault_occurred) {
> +		xmlrpc_env_clean(&chamelium->env);
> +		xmlrpc_env_init(&chamelium->env);
> +	}
> +	va_start(va_args, format_str);
> +	xmlrpc_client_call2f_va(&chamelium->env, chamelium->client,
> +				chamelium->url, method_name, format_str, &res,
> +				va_args);
> +	va_end(va_args);
> +
> +	if (chamelium->env.fault_occurred) {
> +		igt_debug("Chamelium RPC call[%s] failed (non-fatal): %s\n",
> +			  method_name, chamelium->env.fault_string);
> +		xmlrpc_env_clean(&chamelium->env);
> +		xmlrpc_env_init(&chamelium->env);
> +		return NULL;
> +	}
> +	return res;
> +}
> +

Is it possible to reuse this __chamelium_rpc_try in __chamelium_rpc? It 
could avoid the code repetition.

>   /*
>    * For the RPC calls, please refer to the python code [1] for documentation.
>    *
> @@ -307,3 +359,909 @@ char *chamelium_v3_get_port_name(struct igt_chamelium_v3 *chamelium,
>   
>   	return port_name;
>   }
> +
> +/**
> + * chamelium_v3_reset - Reset the Chamelium device
> + *
> + * @chamelium: Chamelium to reset
> + *
> + * Resets all ports to their default state (unplugged).
> + */
> +void chamelium_v3_reset(struct igt_chamelium_v3 *chamelium)
> +{
> +	xmlrpc_value *res;
> +
> +	igt_debug("RPC Reset()\n");
> +	res = __chamelium_rpc(chamelium, "Reset", "()");
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_apply_edid - Apply an EDID to a port
> + *
> + * @chamelium: Chamelium to apply EDID on
> + * @port_id: Port to apply EDID to
> + * @edid_id: EDID ID to apply (0 for default)
> + *
> + * Chamelium V3 requires EDID to be applied before plugging a port.
> + */
> +void chamelium_v3_apply_edid(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id,
> +			     int edid_id)
> +{
> +	xmlrpc_value *res;
> +
> +	igt_debug("RPC ApplyEdid(%d, %d)\n", port_id, edid_id);
> +	res = __chamelium_rpc(chamelium, "ApplyEdid", "(ii)", port_id, edid_id);
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_plug - Plug a port on the Chamelium
> + *
> + * @chamelium: Chamelium to plug port on
> + * @port_id: Port to plug
> + *
> + * Note: Chamelium V3 requires ApplyEdid to be called before Plug.
> + */
> +void chamelium_v3_plug(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id)
> +{
> +	xmlrpc_value *res;
> +
> +	igt_debug("RPC Plug(%d)\n", port_id);
> +	res = __chamelium_rpc(chamelium, "Plug", "(i)", port_id);
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_unplug - Unplug a port on the Chamelium
> + *
> + * @chamelium: Chamelium to unplug port on
> + * @port_id: Port to unplug
> + */
> +void chamelium_v3_unplug(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id)
> +{
> +	xmlrpc_value *res;
> +
> +	igt_debug("RPC Unplug(%d)\n", port_id);
> +	res = __chamelium_rpc(chamelium, "Unplug", "(i)", port_id);
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_is_plugged - Check if a port is plugged
> + *
> + * @chamelium: Chamelium to check
> + * @port_id: Port to check
> + *
> + * Returns true if the port is plugged.
> + */
> +bool chamelium_v3_is_plugged(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id)
> +{
> +	xmlrpc_value *res;
> +	xmlrpc_bool is_plugged;
> +
> +	igt_debug("RPC IsPlugged(%d)\n", port_id);
> +	res = __chamelium_rpc(chamelium, "IsPlugged", "(i)", port_id);
> +
> +	xmlrpc_read_bool(&chamelium->env, res, &is_plugged);
> +	xmlrpc_DECREF(res);
> +
> +	return is_plugged;
> +}
> +
> +/**
> + * chamelium_v3_plug_with_children - Plug an MST port with its children
> + *
> + * @chamelium: Chamelium to plug port on
> + * @port_id: Parent MST port to plug
> + * @children_ids: Array of children port IDs to plug with the parent
> + * @children_count: Number of children ports
> + *
> + * For MST, the parent port (e.g. FPGA_DP2) controls the HPD line.
> + * This function plugs the parent along with the specified children.
> + * Using this with an empty children list is equivalent to Plug().
> + */
> +void chamelium_v3_plug_with_children(struct igt_chamelium_v3 *chamelium,
> +				    chamelium_v3_port_id port_id,
> +				    chamelium_v3_port_id *children_ids,
> +				    int children_count)
> +{
> +	xmlrpc_value *res;
> +	xmlrpc_value *children_array;
> +	xmlrpc_value *child_val;
> +	int i;
> +
> +	igt_debug("RPC PlugWithChildren(%d, [%d children])\n", port_id, children_count);
> +
> +	/* Build the children array */
> +	children_array = xmlrpc_array_new(&chamelium->env);
> +	for (i = 0; i < children_count; i++) {
> +		child_val = xmlrpc_int_new(&chamelium->env, children_ids[i]);
> +		xmlrpc_array_append_item(&chamelium->env, children_array, child_val);
> +		xmlrpc_DECREF(child_val);
> +	}
> +
> +	res = __chamelium_rpc(chamelium, "PlugWithChildren", "(iA)",
> +			     port_id, children_array);
> +	xmlrpc_DECREF(children_array);
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_is_conflict - Check if two ports conflict (can't be plugged simultaneously)
> + *
> + * @chamelium: Chamelium to check
> + * @port_id_a: First port ID
> + * @port_id_b: Second port ID
> + *
> + * Some ports on Chamelium V3 are mutually exclusive (e.g. ITE HDMI1 and ITE HDMI2).
> + * If port A is plugged and conflicts with port B, plugging B will raise an exception.
> + * Port A must be explicitly unplugged first.
> + *
> + * Returns true if the two ports conflict.
> + */
> +bool chamelium_v3_is_conflict(struct igt_chamelium_v3 *chamelium,
> +			    chamelium_v3_port_id port_id_a,
> +			    chamelium_v3_port_id port_id_b)
> +{
> +	xmlrpc_value *res;
> +	xmlrpc_bool conflict;
> +
> +	igt_debug("RPC IsConflict(%d, %d)\n", port_id_a, port_id_b);
> +	res = __chamelium_rpc(chamelium, "IsConflict", "(ii)", port_id_a, port_id_b);
> +
> +	xmlrpc_read_bool(&chamelium->env, res, &conflict);
> +	xmlrpc_DECREF(res);
> +
> +	return conflict;
> +}
> +
> +/**
> + * chamelium_v3_get_mutually_exclusive_ports - Get all ports that conflict with a given port
> + *
> + * @chamelium: Chamelium to check
> + * @port_id: Port to check conflicts for
> + * @port_ids: Out pointer for the list of conflicting port IDs
> + *
> + * Returns the number of conflicting ports stored in @port_ids.
> + * The caller must free this pointer.
> + */
> +int chamelium_v3_get_mutually_exclusive_ports(struct igt_chamelium_v3 *chamelium,
> +					     chamelium_v3_port_id port_id,
> +					     chamelium_v3_port_id **port_ids)
> +{
> +	xmlrpc_value *res, *res_port;
> +	int port_count, i;
> +
> +	igt_assert(port_ids);
> +
> +	igt_debug("RPC GetMutuallyExclusivePorts(%d)\n", port_id);
> +	res = __chamelium_rpc(chamelium, "GetMutuallyExclusivePorts", "(i)", port_id);
> +
> +	port_count = xmlrpc_array_size(&chamelium->env, res);
> +	*port_ids = calloc(port_count, sizeof(**port_ids));

To catch memory leaks, can you add igt_assert_eq(*port_ids, NULL)? And 
also update the documentation to explain it require *port_ids to be NULL.

> +
> +	for (i = 0; i < port_count; i++) {
> +		xmlrpc_array_read_item(&chamelium->env, res, i, &res_port);
> +		xmlrpc_read_int(&chamelium->env, res_port, (int *)&(*port_ids)[i]);
> +		xmlrpc_DECREF(res_port);
> +	}
> +	xmlrpc_DECREF(res);
> +
> +	return port_count;
> +}
> +
> +/**
> + * chamelium_v3_get_connector_type - Get the connector type string for a port
> + *
> + * @chamelium: Chamelium to check
> + * @port_id: Port to get the connector type for
> + *
> + * Returns a string with the connector type (e.g. "DP", "HDMI").
> + * The caller must free this pointer.
> + */
> +char *chamelium_v3_get_connector_type(struct igt_chamelium_v3 *chamelium,
> +				     chamelium_v3_port_id port_id)
> +{
> +	xmlrpc_value *res;
> +	char *connector_type;
> +
> +	igt_debug("RPC GetConnectorType(%d)\n", port_id);
> +	res = __chamelium_rpc(chamelium, "GetConnectorType", "(i)", port_id);
> +
> +	xmlrpc_read_string(&chamelium->env, res, (const char **)&connector_type);
> +	xmlrpc_DECREF(res);
> +
> +	return connector_type;
> +}
> +
> +/**
> + * chamelium_v3_get_port_type - Get the type of a port (DP, HDMI, etc.)
> + *
> + * @chamelium: Chamelium to check
> + * @port_id: Port to check
> + *
> + * Returns string containing port type. Caller must free.
> + */
> +char *chamelium_v3_get_port_type(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id)
> +{
> +	char *name = chamelium_v3_get_port_name(chamelium, port_id);
> +	char *type;
> +
> +	if (strstr(name, "DP") != NULL)
> +		type = strdup("DP");
> +	else if (strstr(name, "HDMI") != NULL)
> +		type = strdup("HDMI");
> +	else if (strstr(name, "VGA") != NULL)
> +		type = strdup("VGA");
> +	else
> +		type = strdup("Unknown");

Can you use the existing drm connector type enum [1]? It can avoid 
string manipulation for users.

[1]: 
https://elixir.bootlin.com/linux/v7.0.1/source/include/uapi/drm/drm_mode.h#L403-L423

> +
> +	free(name);
> +	return type;
> +}
> +
> +/**
> + * chamelium_v3_port_is_dp - Check if a port is a DisplayPort
> + *
> + * @chamelium: Chamelium to check
> + * @port_id: Port to check
> + *
> + * Returns true if the port is a DP port.
> + */
> +bool chamelium_v3_port_is_dp(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id)
> +{
> +	char *name = chamelium_v3_get_port_name(chamelium, port_id);
> +	bool is_dp = (strstr(name, "DP") != NULL);
> +	free(name);
> +	return is_dp;
> +}
> +
> +/**
> + * chamelium_v3_port_is_hdmi - Check if a port is HDMI
> + *
> + * @chamelium: Chamelium to check
> + * @port_id: Port to check
> + *
> + * Returns true if the port is an HDMI port.
> + */
> +bool chamelium_v3_port_is_hdmi(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id)
> +{
> +	char *name = chamelium_v3_get_port_name(chamelium, port_id);
> +	bool is_hdmi = (strstr(name, "HDMI") != NULL);
> +	free(name);
> +	return is_hdmi;
> +}

And it will make those function very simple / useless (IDK if it is 
really useful to have `port_is_hdmi(chamelium, port_id)` if you can 
write `get_port_type(chamelium, port_id) == DRM_MODE_CONNECTOR_HDMI')

> +
> +/**
> + * chamelium_v3_fire_hpd_pulse - Fire HPD pulses on a port
> + *
> + * @chamelium: Chamelium to fire pulses on
> + * @port_id: Port to fire pulses on
> + * @deassert_interval_usec: Time in microseconds for deasserted (unplugged) state
> + * @assert_interval_usec: Time in microseconds for asserted (plugged) state
> + * @repeat_count: Number of pulse cycles
> + * @end_level: Final HPD level (0 = deasserted/unplugged, 1 = asserted/plugged)
> + *
> + * Generates rapid HPD pulses for testing HPD storm detection and
> + * signal integrity.
> + */
> +void chamelium_v3_fire_hpd_pulse(struct igt_chamelium_v3 *chamelium,
> +				 chamelium_v3_port_id port_id,
> +				 int deassert_interval_usec,
> +				 int assert_interval_usec,
> +				 int repeat_count,
> +				 int end_level)
> +{
> +	xmlrpc_value *res;
> +
> +	igt_debug("RPC FireHpdPulse(%d, %d, %d, %d, %d)\n",
> +		  port_id, deassert_interval_usec, assert_interval_usec,
> +		  repeat_count, end_level);
> +	res = __chamelium_rpc(chamelium, "FireHpdPulse", "(iiiii)",
> +			     port_id, deassert_interval_usec,
> +			     assert_interval_usec, repeat_count, end_level);
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_create_edid - Upload a custom EDID to the Chamelium
> + *
> + * @chamelium: Chamelium to upload EDID to
> + * @edid_data: Raw EDID binary data
> + * @edid_size: Size of the EDID data in bytes
> + *
> + * Uploads raw EDID binary data to the Chamelium V3 device. The returned
> + * EDID ID can be used with chamelium_v3_apply_edid() to set the EDID
> + * on a specific port before plugging.
> + *
> + * Returns the EDID ID assigned by the Chamelium.
> + */
> +int chamelium_v3_create_edid(struct igt_chamelium_v3 *chamelium,
> +			     const unsigned char *edid_data,
> +			     size_t edid_size)
> +{
> +	xmlrpc_value *res;
> +	int edid_id;
> +
> +	igt_debug("RPC CreateEdid(%zu bytes)\n", edid_size);
> +	res = __chamelium_rpc(chamelium, "CreateEdid", "(6)",
> +			     edid_data, edid_size);
> +
> +	xmlrpc_read_int(&chamelium->env, res, &edid_id);
> +	xmlrpc_DECREF(res);
> +
> +	return edid_id;
> +}
> +
> +/**
> + * chamelium_v3_read_port_mappings - Read port mappings from .igtrc config
> + *
> + * Reads [Chamelium:<connector-name>] sections from the IGT config file
> + * and matches each connector name to a real DRM connector on the system.
> + * This is how V2 maps Chamelium ports to DRM connectors.
> + *
> + * @drm_fd: Open DRM file descriptor
> + * @ports: Output array of mapped ports (caller must free)
> + * @count: Output number of mapped ports
> + *
> + * Returns true on success, false if no mappings found or error.
> + */
> +bool chamelium_v3_read_port_mappings(int drm_fd,
> +				     struct chamelium_v3_drm_port **ports,
> +				     int *count)
> +{
> +	GError *error = NULL;
> +	char **group_list;
> +	drmModeRes *res;
> +	int mapping_count = 0;
> +	int port_i = 0;
> +	int i, j;
> +
> +	*ports = NULL;
> +	*count = 0;
> +
> +	if (!igt_key_file) {
> +		igt_debug("No config file for port mappings\n");
> +		return false;
> +	}
> +
> +	res = drmModeGetResources(drm_fd);
> +	if (!res) {
> +		igt_debug("Cannot get DRM resources\n");
> +		return false;
> +	}
> +
> +	group_list = g_key_file_get_groups(igt_key_file, NULL);
> +
> +	/* Count [Chamelium:*] sections */
> +	for (i = 0; group_list[i] != NULL; i++) {
> +		if (strstr(group_list[i], "Chamelium:"))

Can you use define for Chamelium: and update documentation to explain 
the format?

> +			mapping_count++;
> +	}
> +
> +	if (mapping_count == 0) {
> +		igt_debug("No [Chamelium:<connector>] sections in config\n");
> +		g_strfreev(group_list);
> +		drmModeFreeResources(res);
> +		return false;
> +	}
> +
> +	*ports = calloc(mapping_count, sizeof(struct chamelium_v3_drm_port));
> +	igt_assert(*ports);
> +
> +	for (i = 0; group_list[i] != NULL; i++) {
> +		char *group = group_list[i];
> +		char *map_name;
> +		int chamelium_port_id;
> +
> +		if (!strstr(group, "Chamelium:"))
> +			continue;
> +
> +		/* Extract connector name after "Chamelium:" */
> +		map_name = group + strlen("Chamelium:");
> +
> +		chamelium_port_id = g_key_file_get_integer(igt_key_file, group,
> +							   "ChameliumPortID",

ditto, can you use define for ChameliumPortID?

> +							   &error);
> +		if (error) {
> +			igt_debug("Failed to read ChameliumPortID for %s: %s\n",
> +				  map_name, error->message);

If you have a Chamelium: section, it means you really want to use the 
chamelium, so I think this log should be at least igt_warn, or maybe 
igt_error + crash?

> +			g_error_free(error);
> +			error = NULL;
> +			continue;
> +		}
> +
> +		/* Find the DRM connector matching this name */
> +		for (j = 0; j < res->count_connectors; j++) {
> +			drmModeConnector *conn;
> +			char name[64];
> +
> +			conn = drmModeGetConnectorCurrent(drm_fd, res->connectors[j]);
> +			if (!conn)
> +				continue;
> +
> +			snprintf(name, sizeof(name), "%s-%u",
> +				 kmstest_connector_type_str(conn->connector_type),
> +				 conn->connector_type_id);

This pattern is common in igt, I think it could be useful to add a pair 
of macro:

#define IGT_CONNECTOR_FMT "%s-%s"
#define IGT_CONNECTOR_FMT_ARGS \
	kmstest_connector_type_str((conn)->connector_type), \
	(conn)->connector_type_id

And use it:
	snprintf(name, sizeof(name), IGT_CONNECTOR_FMT, 
IGT_CONNECTOR_FMT_ARGS(conn);

But that not a blocker for this serie, it is just a suggestion if you 
have the time.

> +
> +			if (strcmp(name, map_name) == 0) {
> +				struct chamelium_v3_drm_port *p = &(*ports)[port_i];
> +
> +				p->port_id = chamelium_port_id;
> +				p->connector_id = conn->connector_id;
> +				p->connector_type = conn->connector_type;
> +				snprintf(p->connector_name, sizeof(p->connector_name),
> +					 "%s", name);
> +				port_i++;
> +
> +				igt_info("Mapped Chamelium port %d -> DRM %s (connector_id=%u)\n",
> +					 chamelium_port_id, name, conn->connector_id);
> +
> +				drmModeFreeConnector(conn);
> +				break;
> +			}
> +
> +			drmModeFreeConnector(conn);
> +		}
> +	}
> +
> +	*count = port_i;
> +	g_strfreev(group_list);
> +	drmModeFreeResources(res);
> +
> +	if (port_i == 0) {
> +		free(*ports);
> +		*ports = NULL;
> +		igt_warn("No connectors matched the Chamelium port mappings\n");
> +		return false;
> +	}
> +
> +	return true;
> +}
> +
> +/**
> + * chamelium_v3_reprobe_connector - Force-reprobe a DRM connector
> + *
> + * Uses drmModeGetConnector() which triggers a kernel reprobe of the
> + * actual hardware state. This is how we verify whether a connector
> + * is truly connected or disconnected.
> + *
> + * @drm_fd: Open DRM file descriptor
> + * @connector_id: DRM connector ID to reprobe
> + *
> + * Returns the actual connection status.
> + */
> +drmModeConnection chamelium_v3_reprobe_connector(int drm_fd,
> +						 uint32_t connector_id)
> +{
> +	drmModeConnector *conn;
> +	drmModeConnection status;
> +
> +	conn = drmModeGetConnector(drm_fd, connector_id);
> +	if (!conn)
> +		return DRM_MODE_UNKNOWNCONNECTION;
> +
> +	status = conn->connection;
> +	drmModeFreeConnector(conn);
> +
> +	return status;
> +}

I think this should be in igt_kms, this is a generic function.

> +
> +/**
> + * chamelium_v3_wait_for_conn_status_change - Poll connector until expected status
> + *
> + * Mirrors V2 chamelium_wait_for_conn_status_change. Polls the real DRM
> + * connector using drmModeGetConnector() (which triggers kernel reprobe)
> + * with 50ms intervals until the status matches or timeout.
> + *
> + * @drm_fd: Open DRM file descriptor
> + * @connector_id: DRM connector ID
> + * @expected: Expected connection status
> + * @timeout_secs: Max seconds to wait
> + *
> + * Returns true if the expected status was reached.
> + */
> +bool chamelium_v3_wait_for_conn_status_change(int drm_fd,
> +					      uint32_t connector_id,
> +					      drmModeConnection expected,
> +					      int timeout_secs)
> +{
> +	struct timespec start, now;
> +
> +	clock_gettime(CLOCK_MONOTONIC, &start);
> +
> +	while (1) {
> +		if (chamelium_v3_reprobe_connector(drm_fd, connector_id) == expected)
> +			return true;
> +
> +		clock_gettime(CLOCK_MONOTONIC, &now);
> +		if (now.tv_sec - start.tv_sec >= timeout_secs)
> +			break;
> +
> +		usleep(50000); /* 50ms poll, same as V2 */
> +	}
> +
> +	return false;
> +}

This function is implemented in [2] (your series) and [3] (my series, 
with a 10ms sleep, but it can be changed if you think 10ms is too fast), 
I think you should reuse one of the existing implementations.

[2]:https://lore.kernel.org/all/20260428044644.257001-3-mohammed.bilal@intel.com/
[3]:https://lore.kernel.org/all/20260424-unigraf-integration-v11-2-0c5f5346ae60@bootlin.com/

> +/**
> + * chamelium_v3_get_video_params - Get video timing parameters from port
> + *
> + * Calls the GetVideoParams RPC to retrieve current video timing info.
> + * Uses non-asserting RPC to allow callers (e.g. polling/wait functions)
> + * to handle transient failures gracefully.
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port ID to query
> + * @params: Output video parameters structure
> + *
> + * Returns true on success, false on RPC failure (e.g. link not ready).
> + */
> +bool chamelium_v3_get_video_params(struct igt_chamelium_v3 *chamelium,
> +				   chamelium_v3_port_id port_id,
> +				   struct chamelium_v3_video_params *params)
> +{
> +	xmlrpc_value *res;
> +	xmlrpc_value *val;
> +
> +	igt_assert(params);
> +	memset(params, 0, sizeof(*params));
> +
> +	igt_debug("RPC GetVideoParams(%d)\n", port_id);
> +	res = __chamelium_rpc_try(chamelium, "GetVideoParams", "(i)", port_id);
> +	if (!res)
> +		return false;
> +
> +	/* Parse the returned struct/dict */
> +	xmlrpc_struct_find_value(&chamelium->env, res, "hactive", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->hactive); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "vactive", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->vactive); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "htotal", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->htotal); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "vtotal", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->vtotal); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "hsync_width", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->hsync_width); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "vsync_width", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->vsync_width); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "hfront_porch", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->hfront_porch); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "vfront_porch", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->vfront_porch); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "hback_porch", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->hback_porch); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "vback_porch", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->vback_porch); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "bpc", &val);
> +	if (val) { xmlrpc_read_int(&chamelium->env, val, &params->bpc); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "clock", &val);
> +	if (val) { xmlrpc_read_double(&chamelium->env, val, &params->clock); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "frame_rate", &val);
> +	if (val) { xmlrpc_read_double(&chamelium->env, val, &params->frame_rate); xmlrpc_DECREF(val); }
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "hsync_polarity", &val);
> +	if (val) {
> +		xmlrpc_bool b;
> +		xmlrpc_read_bool(&chamelium->env, val, &b);
> +		params->hsync_polarity = b;
> +		xmlrpc_DECREF(val);
> +	}
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "vsync_polarity", &val);
> +	if (val) {
> +		xmlrpc_bool b;
> +		xmlrpc_read_bool(&chamelium->env, val, &b);
> +		params->vsync_polarity = b;
> +		xmlrpc_DECREF(val);
> +	}
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "interlaced", &val);
> +	if (val) {
> +		xmlrpc_bool b;
> +		xmlrpc_read_bool(&chamelium->env, val, &b);
> +		params->interlaced = b;
> +		xmlrpc_DECREF(val);
> +	}
> +
> +	xmlrpc_DECREF(res);
> +
> +	igt_debug("Video params: %dx%d @ %.2f Hz, %d bpc\n",
> +		  params->hactive, params->vactive,
> +		  params->frame_rate, params->bpc);

Two questions:
- is it possible to only have "partial result" (only frame_rate but not 
interlaced for example)? If not, I think you should add an assert if 
there is a success + failure.
- if "partial result" is possible, how do I know which field is 
populated? In particular booleans, they can only be true/false, so the 
user don't know if interlaced=false means "interlaced not present" or 
"interlaced is false".

> +	return true;
> +}
> +
> +/**
> + * chamelium_v3_has_audio_support - Check if a port supports audio
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port ID to check
> + *
> + * Returns true if the port has audio support.
> + */
> +bool chamelium_v3_has_audio_support(struct igt_chamelium_v3 *chamelium,
> +				    chamelium_v3_port_id port_id)
> +{
> +	xmlrpc_value *res;
> +	xmlrpc_bool has_support;
> +
> +	igt_debug("RPC HasAudioSupport(%d)\n", port_id);
> +	res = __chamelium_rpc_try(chamelium, "HasAudioSupport", "(i)", port_id);
> +	if (!res)
> +		return false;
> +
> +	xmlrpc_read_bool(&chamelium->env, res, &has_support);
> +	xmlrpc_DECREF(res);
> +
> +	return has_support;
> +}
> +
> +/**
> + * chamelium_v3_start_capturing_audio - Start capturing audio from a port
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port to capture audio from
> + * @save_to_file: Whether to save captured audio to a file on the Chamelium
> + */
> +bool chamelium_v3_start_capturing_audio(struct igt_chamelium_v3 *chamelium,
> +					chamelium_v3_port_id port_id,
> +					bool save_to_file)
> +{
> +	xmlrpc_value *res;
> +
> +	igt_debug("RPC StartCapturingAudio(%d, %s)\n",
> +		  port_id, save_to_file ? "true" : "false");
> +	res = __chamelium_rpc_try(chamelium, "StartCapturingAudio", "(ib)",
> +				 port_id, save_to_file);

I think this can use __chamelium_rpc no? If the audio is supported, 
capturing should never fail?

> +	if (!res)
> +		return false;
> +	xmlrpc_DECREF(res);
> +	return true;
> +}
> +
> +/**
> + * chamelium_v3_stop_capturing_audio - Stop capturing audio from a port
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port to stop capturing audio from
> + */
> +void chamelium_v3_stop_capturing_audio(struct igt_chamelium_v3 *chamelium,
> +				       chamelium_v3_port_id port_id)
> +{
> +	xmlrpc_value *res;
> +
> +	igt_debug("RPC StopCapturingAudio(%d)\n", port_id);
> +	res = __chamelium_rpc_try(chamelium, "StopCapturingAudio", "(i)",
> +				  port_id);

ditto?

> +	if (res)
> +		xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_get_audio_format - Get the audio capture format
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port to query
> + * @rate: Output sampling rate in Hz (or 0 if unknown)
> + * @channels: Output number of channels
> + */
> +void chamelium_v3_get_audio_format(struct igt_chamelium_v3 *chamelium,
> +				   chamelium_v3_port_id port_id,
> +				   int *rate, int *channels)
> +{
> +	xmlrpc_value *res, *val;
> +
> +	igt_debug("RPC GetAudioFormat(%d)\n", port_id);
> +	res = __chamelium_rpc(chamelium, "GetAudioFormat", "(i)", port_id);
> +
> +	if (rate) {
> +		xmlrpc_struct_find_value(&chamelium->env, res, "rate", &val);
> +		if (val) {
> +			xmlrpc_read_int(&chamelium->env, val, rate);
> +			xmlrpc_DECREF(val);
> +		} else {
> +			*rate = 0;
> +		}
> +	}
> +
> +	if (channels) {
> +		xmlrpc_struct_find_value(&chamelium->env, res, "channel", &val);
> +		if (val) {
> +			xmlrpc_read_int(&chamelium->env, val, channels);
> +			xmlrpc_DECREF(val);
> +		} else {
> +			*channels = 0;
> +		}
> +	}
> +
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_get_audio_channel_mapping - Get audio channel mapping
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port to query
> + * @mapping: Array of CHAMELIUM_V3_MAX_AUDIO_CHANNELS ints.
> + *           Each element indicates which input channel the capture channel
> + *           is mapped to. -1 means unmapped.
> + */
> +void chamelium_v3_get_audio_channel_mapping(struct igt_chamelium_v3 *chamelium,
> +					    chamelium_v3_port_id port_id,
> +					    int mapping[static CHAMELIUM_V3_MAX_AUDIO_CHANNELS])
> +{
> +	xmlrpc_value *res, *res_channel;
> +	int res_len, i;
> +
> +	igt_debug("RPC GetAudioChannelMapping(%d)\n", port_id);
> +	res = __chamelium_rpc(chamelium, "GetAudioChannelMapping", "(i)", port_id);
> +
> +	res_len = xmlrpc_array_size(&chamelium->env, res);
> +	igt_assert_f(res_len == CHAMELIUM_V3_MAX_AUDIO_CHANNELS,
> +		     "Expected %d audio channels, got %d\n",
> +		     CHAMELIUM_V3_MAX_AUDIO_CHANNELS, res_len);
> +
> +	for (i = 0; i < res_len; i++) {
> +		xmlrpc_array_read_item(&chamelium->env, res, i, &res_channel);
> +		xmlrpc_read_int(&chamelium->env, res_channel, &mapping[i]);
> +		xmlrpc_DECREF(res_channel);
> +	}
> +	xmlrpc_DECREF(res);
> +}
> +
> +/**
> + * chamelium_v3_get_last_infoframe - Get the last received InfoFrame
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port to query
> + * @infoframe_type: Type string: "avi", "audio", "mpeg", "vendor", "spd"
> + *
> + * Returns: InfoFrame structure (caller must free with chamelium_v3_infoframe_destroy),
> + *          or NULL if no InfoFrame was received.
> + */
> +struct chamelium_v3_infoframe *chamelium_v3_get_last_infoframe(
> +	struct igt_chamelium_v3 *chamelium,
> +	chamelium_v3_port_id port_id,
> +	const char *infoframe_type)
> +{
> +	xmlrpc_value *res, *val;
> +	struct chamelium_v3_infoframe *infoframe;
> +	const unsigned char *payload_data;
> +	size_t payload_len;
> +
> +	igt_debug("RPC GetLastInfoFrame(%d, \"%s\")\n", port_id, infoframe_type);
> +	res = __chamelium_rpc_try(chamelium, "GetLastInfoFrame", "(is)",
> +				  port_id, infoframe_type);
> +	if (!res)
> +		return NULL;
> +
> +	infoframe = calloc(1, sizeof(*infoframe));
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "version", &val);
> +	if (val) {
> +		xmlrpc_read_int(&chamelium->env, val, &infoframe->version);
> +		xmlrpc_DECREF(val);
> +	}
> +
> +	xmlrpc_struct_find_value(&chamelium->env, res, "payload", &val);
> +	if (val) {
> +		xmlrpc_read_base64(&chamelium->env, val, &payload_len,
> +				   &payload_data);
> +		infoframe->payload = malloc(payload_len);
> +		memcpy(infoframe->payload, payload_data, payload_len);
> +		infoframe->payload_size = payload_len;
> +		xmlrpc_DECREF(val);
> +	}
> +
> +	xmlrpc_DECREF(res);
> +
> +	return infoframe;
> +}
> +
> +/**
> + * chamelium_v3_infoframe_destroy - Free an InfoFrame structure
> + */
> +void chamelium_v3_infoframe_destroy(struct chamelium_v3_infoframe *infoframe)
> +{
> +	if (infoframe) {
> +		free(infoframe->payload);
> +		free(infoframe);
> +	}
> +}
> +
> +/**
> + * chamelium_v3_capture_frame - Capture current frame from port
> + *
> + * Captures the current displayed frame using DumpPixels API.
> + * Returns RGB data (3 bytes per pixel).
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port ID to capture from
> + *
> + * Returns: Frame structure with captured data (caller must free with chamelium_v3_free_frame)

Add documentation that explains that return can be null if there is no 
signal/invalid video param

> + */
> +struct chamelium_v3_frame *chamelium_v3_capture_frame(struct igt_chamelium_v3 *chamelium,
> +						      chamelium_v3_port_id port_id)
> +{
> +	struct chamelium_v3_frame *frame;
> +	struct chamelium_v3_video_params params;
> +	xmlrpc_value *res;
> +	const unsigned char *data;
> +	size_t data_len;
> +
> +	/* First get video params to know resolution */
> +	if (!chamelium_v3_get_video_params(chamelium, port_id, &params)) {
> +		igt_warn("Failed to get video params for port %d\n", port_id);
> +		return NULL;
> +	}
> +
> +	if (params.hactive <= 0 || params.vactive <= 0) {
> +		igt_warn("Invalid video params: %dx%d\n", params.hactive, params.vactive);
> +		return NULL;
> +	}
> +
> +	igt_debug("RPC DumpPixels(%d)\n", port_id);
> +	res = __chamelium_rpc_try(chamelium, "DumpPixels", "(i)", port_id);
> +	if (!res) {
> +		igt_warn("DumpPixels RPC failed for port %d\n", port_id);
> +		return NULL;
> +	}
> +
> +	/* Extract binary data from XML-RPC response */
> +	xmlrpc_read_base64(&chamelium->env, res, &data_len, &data);
> +
> +	if (chamelium->env.fault_occurred) {
> +		igt_warn("Failed to read DumpPixels data: %s\n",
> +			 chamelium->env.fault_string);
> +		xmlrpc_DECREF(res);
> +		return NULL;
> +	}
> +
> +	/* Allocate frame structure */
> +	frame = calloc(1, sizeof(*frame));
> +	frame->width = params.hactive;
> +	frame->height = params.vactive;
> +	frame->size = data_len;
> +	frame->data = malloc(data_len);
> +	memcpy(frame->data, data, data_len);
> +
> +	xmlrpc_DECREF(res);
> +
> +	igt_debug("Captured frame: %dx%d, %zu bytes\n",
> +		  frame->width, frame->height, frame->size);
> +
> +	/* Verify data size matches expected RGB (3 bytes per pixel) */
> +	size_t expected = (size_t)frame->width * frame->height * 3;
> +	if (frame->size != expected) {
> +		igt_warn("Frame size mismatch: got %zu, expected %zu\n",
> +			 frame->size, expected);
> +	}
> +
> +	return frame;
> +}
> +
> +/**
> + * chamelium_v3_free_frame - Free captured frame memory
> + */
> +void chamelium_v3_free_frame(struct chamelium_v3_frame *frame)
> +{
> +	if (frame) {
> +		free(frame->data);
> +		free(frame);
> +	}
> +}
> diff --git a/lib/chamelium/v3/igt_chamelium.h b/lib/chamelium/v3/igt_chamelium.h
> index 71351e579..a0da1bb6a 100644
> --- a/lib/chamelium/v3/igt_chamelium.h
> +++ b/lib/chamelium/v3/igt_chamelium.h
> @@ -49,4 +49,199 @@ int chamelium_v3_get_children(struct igt_chamelium_v3 *chamelium, chamelium_v3_p
>   bool chamelium_v3_is_mst(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
>   char *chamelium_v3_get_port_name(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
>   
> +/* Port control functions */
> +void chamelium_v3_reset(struct igt_chamelium_v3 *chamelium);
> +void chamelium_v3_plug(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
> +void chamelium_v3_unplug(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
> +bool chamelium_v3_is_plugged(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
> +void chamelium_v3_apply_edid(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id,
> +			     int edid_id);
> +
> +/* MST (Multi-Stream Transport) functions */
> +void chamelium_v3_plug_with_children(struct igt_chamelium_v3 *chamelium,
> +				    chamelium_v3_port_id port_id,
> +				    chamelium_v3_port_id *children_ids,
> +				    int children_count);
> +bool chamelium_v3_is_conflict(struct igt_chamelium_v3 *chamelium,
> +			    chamelium_v3_port_id port_id_a,
> +			    chamelium_v3_port_id port_id_b);
> +int chamelium_v3_get_mutually_exclusive_ports(struct igt_chamelium_v3 *chamelium,
> +					     chamelium_v3_port_id port_id,
> +					     chamelium_v3_port_id **port_ids);
> +char *chamelium_v3_get_connector_type(struct igt_chamelium_v3 *chamelium,
> +				     chamelium_v3_port_id port_id);
> +
> +/* Port type detection */
> +char *chamelium_v3_get_port_type(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
> +bool chamelium_v3_port_is_dp(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
> +bool chamelium_v3_port_is_hdmi(struct igt_chamelium_v3 *chamelium, chamelium_v3_port_id port_id);
> +
> +/* HPD pulse generation */
> +void chamelium_v3_fire_hpd_pulse(struct igt_chamelium_v3 *chamelium,
> +				 chamelium_v3_port_id port_id,
> +				 int deassert_interval_usec,
> +				 int assert_interval_usec,
> +				 int repeat_count,
> +				 int end_level);
> +
> +/* EDID creation - returns EDID ID for use with chamelium_v3_apply_edid */
> +int chamelium_v3_create_edid(struct igt_chamelium_v3 *chamelium,
> +			     const unsigned char *edid_data,
> +			     size_t edid_size);
> +
> +/* Video parameters structure */
> +struct chamelium_v3_video_params {
> +	int hactive;
> +	int vactive;
> +	int htotal;
> +	int vtotal;
> +	int hsync_width;
> +	int vsync_width;
> +	int hfront_porch;
> +	int vfront_porch;
> +	int hback_porch;
> +	int vback_porch;
> +	bool hsync_polarity;
> +	bool vsync_polarity;
> +	double clock;
> +	double frame_rate;
> +	int bpc;
> +	bool interlaced;
> +};
> +
> +/* Frame capture structure */
> +struct chamelium_v3_frame {
> +	uint8_t *data;      /* RGB pixel data (3 bytes per pixel) */
> +	int width;
> +	int height;
> +	size_t size;        /* Total data size in bytes */

Maybe explains that the size is the data buffer size and may be 
different from width*height*3.

> +};
> +
> +/**
> + * chamelium_v3_get_video_params - Get video timing parameters from port
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port ID to query
> + * @params: Output video parameters structure
> + *
> + * Returns true on success.
> + */

I don't think you need to duplicate the documentation

> +bool chamelium_v3_get_video_params(struct igt_chamelium_v3 *chamelium,
> +				   chamelium_v3_port_id port_id,
> +				   struct chamelium_v3_video_params *params);
> +
> +/**
> + * chamelium_v3_capture_frame - Capture current frame from port
> + *
> + * Captures the current displayed frame using DumpPixels API.
> + * Returns RGB data (3 bytes per pixel).
> + *
> + * @chamelium: Chamelium instance
> + * @port_id: Port ID to capture from
> + *
> + * Returns: Frame structure with captured data (caller must free with chamelium_v3_free_frame)
> + */

ditto

> +struct chamelium_v3_frame *chamelium_v3_capture_frame(struct igt_chamelium_v3 *chamelium,
> +						      chamelium_v3_port_id port_id);
> +
> +/**
> + * chamelium_v3_free_frame - Free captured frame memory
> + */

ditto

> +void chamelium_v3_free_frame(struct chamelium_v3_frame *frame);
> +
> +/*
> + * Port-to-DRM-connector mapping.
> + *
> + * Reads [Chamelium:<connector-name>] sections from .igtrc to map
> + * Chamelium port IDs to DRM connector IDs on the DUT.
> + * This is essential for verifying that plug/unplug actually changes
> + * the real DRM connector state.
> + */
> +
> +/** struct chamelium_v3_drm_port - A Chamelium port mapped to a DRM connector */
> +struct chamelium_v3_drm_port {
> +	chamelium_v3_port_id port_id;   /* Chamelium port ID (e.g. 4) */
> +	uint32_t connector_id;          /* DRM connector_id from kernel */
> +	uint32_t connector_type;        /* DRM_MODE_CONNECTOR_* type */

connector_type is from the kernel or from the chamelium? There can be an 
adapter in between.

> +	char connector_name[64];        /* DRM connector name e.g. "DP-1" */
> +};
> +
> +/**
> + * chamelium_v3_read_port_mappings - Read port mappings from .igtrc config
> + *
> + * Reads [Chamelium:<name>] sections with ChameliumPortID keys, then finds
> + * the matching DRM connector on the system.
> + *
> + * @drm_fd: Open DRM file descriptor
> + * @ports: Output array of mapped ports (caller must free)
> + * @count: Output number of mapped ports
> + *
> + * Returns true on success.
> + */
> +bool chamelium_v3_read_port_mappings(int drm_fd,
> +				     struct chamelium_v3_drm_port **ports,
> +				     int *count);
> +
> +/**
> + * chamelium_v3_reprobe_connector - Force-reprobe a DRM connector and return status
> + *
> + * Uses drmModeGetConnector() which triggers a kernel reprobe of the
> + * actual hardware state. This is how V2 verifies real connector status.
> + *
> + * @drm_fd: Open DRM file descriptor
> + * @connector_id: DRM connector ID to reprobe
> + *
> + * Returns: drmModeConnection (DRM_MODE_CONNECTED, DRM_MODE_DISCONNECTED, etc.)
> + */
> +drmModeConnection chamelium_v3_reprobe_connector(int drm_fd,
> +						 uint32_t connector_id);
> +
> +/**
> + * chamelium_v3_wait_for_conn_status_change - Poll connector until expected status or timeout
> + *
> + * Mirrors V2 chamelium_wait_for_conn_status_change. Polls with 50ms
> + * interval until the connector reports the expected status.
> + *
> + * @drm_fd: Open DRM file descriptor
> + * @connector_id: DRM connector ID
> + * @expected: Expected connection status
> + * @timeout_secs: Max seconds to wait
> + *
> + * Returns true if the expected status was reached.
> + */
> +bool chamelium_v3_wait_for_conn_status_change(int drm_fd,
> +					      uint32_t connector_id,
> +					      drmModeConnection expected,
> +					      int timeout_secs);
> +
> +/* Audio support */
> +#define CHAMELIUM_V3_MAX_AUDIO_CHANNELS 8
> +
> +bool chamelium_v3_has_audio_support(struct igt_chamelium_v3 *chamelium,
> +				    chamelium_v3_port_id port_id);
> +bool chamelium_v3_start_capturing_audio(struct igt_chamelium_v3 *chamelium,
> +					chamelium_v3_port_id port_id,
> +					bool save_to_file);
> +void chamelium_v3_stop_capturing_audio(struct igt_chamelium_v3 *chamelium,
> +				       chamelium_v3_port_id port_id);
> +void chamelium_v3_get_audio_format(struct igt_chamelium_v3 *chamelium,
> +				   chamelium_v3_port_id port_id,
> +				   int *rate, int *channels);
> +void chamelium_v3_get_audio_channel_mapping(struct igt_chamelium_v3 *chamelium,
> +					    chamelium_v3_port_id port_id,
> +					    int mapping[static CHAMELIUM_V3_MAX_AUDIO_CHANNELS]);
> +
> +/* InfoFrame support */
> +struct chamelium_v3_infoframe {
> +	int version;
> +	uint8_t *payload;
> +	size_t payload_size;
> +};
> +
> +struct chamelium_v3_infoframe *chamelium_v3_get_last_infoframe(
> +	struct igt_chamelium_v3 *chamelium,
> +	chamelium_v3_port_id port_id,
> +	const char *infoframe_type);
> +void chamelium_v3_infoframe_destroy(struct chamelium_v3_infoframe *infoframe);
> +
>   #endif //V3_IGT_CHAMELIUM_H


  reply	other threads:[~2026-04-29 10:45 UTC|newest]

Thread overview: 37+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-04-28  4:46 [PATCH i-g-t v1 00/25] Chamelium v3 Integration and Test Execution Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 01/25] lib/igt_kms: Add a detect timeout value Mohammed Bilal
2026-04-28  7:11   ` Jani Nikula
2026-04-28  7:16   ` Jani Nikula
2026-04-28  7:17   ` Jani Nikula
2026-04-28  4:46 ` [PATCH i-g-t v1 02/25] lib/igt_kms: Add helper to wait for a specific status on a connector Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 03/25] lib/igt_kms: Add function to list connected connectors Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 04/25] lib/igt_kms: Add helper to obtain a connector by its name or MST path Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 05/25] lib/igt_kms: Add function to get valid pipe for specific output Mohammed Bilal
2026-04-28  7:21   ` Jani Nikula
2026-04-28  4:46 ` [PATCH i-g-t v1 06/25] lib/monitor_edids: Add helper functions for using monitor_edid objects Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 07/25] lib/monitor_edids: Add helper to get an EDID by its name Mohammed Bilal
2026-04-28  7:23   ` Jani Nikula
2026-04-28  4:46 ` [PATCH i-g-t v1 08/25] lib/monitor_edids: Add helper to print all available EDID names Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 09/25] lib/monitor_edids: Fix missing names in some monitor EDID Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 10/25] lib/monitor_edids: Add new EDID for HDMI 4k Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 11/25] tests/chamelium: Extract Chamelium v2 tests into a separate directory Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 12/25] lib/chamelium/v2: Extract chamelium v2 wrapper into its own directory Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 13/25] lib/chamelium/v2: Rename chamelium to chamelium_v2 Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 14/25] lib/chamelium/v2: Rename HAVE_CHAMELIUM to HAVE_CHAMELIUM_V2 Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 15/25] lib/chamelium/v3: Introduce the foundation for the Chamelium v3 wrapper Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 16/25] lib/chamelium/v3: Introduce initialization and cleanup of Chamelium-related structures Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 17/25] lib/chamelium/v3: Add method to discover Chamelium ports Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 18/25] lib/chamelium/v3: Implement method to retrieve Chamelium port names Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 19/25] tests/chamelium/v3: Implement a basic Chamelium v3 accessibility test Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 20/25] lib/chamelium/v3: Add extended API for Chamelium v3 HPD, EDID, Frames, Color & Audio Mohammed Bilal
2026-04-29 10:44   ` Louis Chauvet [this message]
2026-04-28  4:46 ` [PATCH i-g-t v1 21/25] tests/chamelium/v3: Add HPD (Hot Plug Detect) tests for Chamelium v3 Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 22/25] tests/chamelium/v3: Add EDID " Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 23/25] tests/chamelium/v3: Add frame capture and CRC " Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 24/25] tests/chamelium/v3: Add color verification " Mohammed Bilal
2026-04-28  4:46 ` [PATCH i-g-t v1 25/25] tests/chamelium/v3: Add audio " Mohammed Bilal
2026-04-28  5:52 ` ✓ Xe.CI.BAT: success for Chamelium v3 Integration and Test Execution Patchwork
2026-04-28  6:09 ` ✗ i915.CI.BAT: failure " Patchwork
2026-04-28 12:36 ` ✗ Xe.CI.FULL: " Patchwork
2026-04-28 14:02 ` [PATCH i-g-t v1 00/25] " Louis Chauvet
2026-04-29  3:36   ` Bilal, Mohammed

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=1d5948a2-7573-48a6-a07c-3a05a9f1b8f6@bootlin.com \
    --to=louis.chauvet@bootlin.com \
    --cc=igt-dev@lists.freedesktop.org \
    --cc=kunal1.joshi@intel.com \
    --cc=mohammed.bilal@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