From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 26523FF8875 for ; Wed, 29 Apr 2026 10:45:17 +0000 (UTC) Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id C58BF10E072; Wed, 29 Apr 2026 10:45:16 +0000 (UTC) Authentication-Results: gabe.freedesktop.org; dkim=pass (2048-bit key; unprotected) header.d=bootlin.com header.i=@bootlin.com header.b="GVJxQPNy"; dkim-atps=neutral Received: from smtpout-03.galae.net (smtpout-03.galae.net [185.246.85.4]) by gabe.freedesktop.org (Postfix) with ESMTPS id DD8E110E072 for ; Wed, 29 Apr 2026 10:45:00 +0000 (UTC) Received: from smtpout-01.galae.net (smtpout-01.galae.net [212.83.139.233]) by smtpout-03.galae.net (Postfix) with ESMTPS id 4EE6F4E42AED; Wed, 29 Apr 2026 10:44:59 +0000 (UTC) Received: from mail.galae.net (mail.galae.net [212.83.136.155]) by smtpout-01.galae.net (Postfix) with ESMTPS id 13641601DF; Wed, 29 Apr 2026 10:44:59 +0000 (UTC) Received: from [127.0.0.1] (localhost [127.0.0.1]) by localhost (Mailerdaemon) with ESMTPSA id 9844810729B8A; Wed, 29 Apr 2026 12:44:52 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=bootlin.com; s=dkim; t=1777459494; h=from:subject:date:message-id:to:cc:mime-version:content-type: content-transfer-encoding:content-language:in-reply-to:references; bh=mc1yni2oXhbmlGryE2fPjtS/rWEKqgE/kj5XU5U1Vrs=; b=GVJxQPNy+Uetz1WhSzgqXydLGbrhCDnLTjDTmMycdqNuKSiIbd27/QYKoRVj0MW29xMNvN 3l93DIhRzJPLOM8wO5sveXOMv9x8Lmpikq+7RoBUXoQr3WXsYOKrOkzxri64bCTlb8sL5x RDO5SfW8H+DUi2u1CiznJ4GE3XT40F5myamx0d9M+IjQ1oDQUDev7Q5LnJts+pwi1cvndm vjOZ1NtJOTEdfDcnqK+n/YsNm+QwoGZ9QIKEv16xo8tgkpm+P9c1jKzZ0N68Zehxwp2xB7 9v5XeKj3lCzQw+n139sgrwIHYFuE1AhuLqusIsuS7yCh+6uAG27NtpMBoSqoGg== Message-ID: <1d5948a2-7573-48a6-a07c-3a05a9f1b8f6@bootlin.com> Date: Wed, 29 Apr 2026 12:44:10 +0200 MIME-Version: 1.0 User-Agent: Mozilla Thunderbird Subject: Re: [PATCH i-g-t v1 20/25] lib/chamelium/v3: Add extended API for Chamelium v3 HPD, EDID, Frames, Color & Audio To: Mohammed Bilal , igt-dev@lists.freedesktop.org Cc: kunal1.joshi@intel.com References: <20260428044644.257001-1-mohammed.bilal@intel.com> <20260428044644.257001-21-mohammed.bilal@intel.com> From: Louis Chauvet Content-Language: en-US In-Reply-To: <20260428044644.257001-21-mohammed.bilal@intel.com> Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Last-TLS-Session-Version: TLSv1.3 X-BeenThere: igt-dev@lists.freedesktop.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: Development mailing list for IGT GPU Tools List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: igt-dev-bounces@lists.freedesktop.org Sender: "igt-dev" 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 > --- > 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 > +#include > +#include > +#include > +#include > #include > #include > > #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:] 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:] 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, ¶ms->hactive); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "vactive", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->vactive); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "htotal", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->htotal); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "vtotal", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->vtotal); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "hsync_width", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->hsync_width); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "vsync_width", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->vsync_width); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "hfront_porch", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->hfront_porch); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "vfront_porch", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->vfront_porch); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "hback_porch", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->hback_porch); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "vback_porch", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->vback_porch); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "bpc", &val); > + if (val) { xmlrpc_read_int(&chamelium->env, val, ¶ms->bpc); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "clock", &val); > + if (val) { xmlrpc_read_double(&chamelium->env, val, ¶ms->clock); xmlrpc_DECREF(val); } > + > + xmlrpc_struct_find_value(&chamelium->env, res, "frame_rate", &val); > + if (val) { xmlrpc_read_double(&chamelium->env, val, ¶ms->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, ¶ms)) { > + 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:] 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:] 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