[PATCH i-g-t 2/6] lib/igt_connector_helper: Add generic connector helpers

Kunal Joshi
2026-04-09T04:16:11Z

Add a lightweight helper library for DRM connector operations used
by USB4 switch and other dock/undock tests:

- igt_connector_get_connected(): enumerate connected connectors
- igt_connector_get_info(): query name, EDID serial, and MST path
- igt_connector_find_by_path(): PATH-based connector lookup (MST)
- igt_connector_find_by_name(): name-based connector lookup
- igt_connector_reprobe_all(): force reprobe all connectors by
iterating each connector to trigger DRM core re-detection

These helpers abstract common DRM connector patterns that are needed
by multiple test suites for display enumeration and verification.

Signed-off-by: Kunal Joshi <kunal1.joshi@intel.com>
---
lib/igt_connector_helper.c | 299 +++++++++++++++++++++++++++++++++++++
lib/igt_connector_helper.h | 27 ++++
lib/meson.build | 1 +
3 files changed, 327 insertions(+)
create mode 100644 lib/igt_connector_helper.c
create mode 100644 lib/igt_connector_helper.h

diff --git a/lib/igt_connector_helper.c b/lib/igt_connector_helper.c
new file mode 100644
index 000000000..6be849b7a
--- /dev/null
+++ b/lib/igt_connector_helper.c
@@ -0,0 +1,299 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+/**
+ * SECTION:igt_connector_helper
+ * @short_description: Generic connector helper functions for DRM/KMS
+ * @title: Connector Helper
+ * @include: igt_connector_helper.h
+ *
+ * Helper functions for DRM connector operations including enumeration,
+ * lookup by name/path, and EDID serial extraction.
+ */
+
+#include <string.h>
+#include <xf86drm.h>
+#include <xf86drmMode.h>
+
+#include "igt_core.h"
+#include "igt_edid.h"
+#include "igt_kms.h"
+#include "igt_connector_helper.h"
+
+/**
+ * igt_connector_get_connected:
+ * @drm_fd: DRM file descriptor
+ * @connectors: array to store connector IDs; must be valid if @max_connectors > 0
+ * @max_connectors: maximum number of connectors to store
+ *
+ * Walks all DRM connectors and stores the IDs of those currently connected.
+ * Scanning stops once @max_connectors IDs have been collected; later
+ * connectors are not examined.
+ *
+ * Returns: number of connector IDs written to @connectors, capped by
+ * @max_connectors.
+ */
+int igt_connector_get_connected(int drm_fd, uint32_t *connectors,
+ int max_connectors)
+{
+ drmModeRes *res;
+ drmModeConnector *conn;
+ int count = 0;
+ int i;
+
+ igt_assert(max_connectors >= 0);
+ if (max_connectors > 0)
+ igt_assert(connectors);
+
+ res = drmModeGetResources(drm_fd);
+ if (!res)
+ return 0;
+
+ for (i = 0; i < res->count_connectors && count < max_connectors; i++) {
+ conn = drmModeGetConnector(drm_fd, res->connectors[i]);
+ if (!conn)
+ continue;
+
+ if (conn->connection == DRM_MODE_CONNECTED)
+ connectors[count++] = conn->connector_id;
+
+ drmModeFreeConnector(conn);
+ }
+
+ drmModeFreeResources(res);
+ return count;
+}
+
+/**
+ * igt_connector_get_info:
+ * @drm_fd: DRM file descriptor
+ * @connector_id: connector ID
+ * @name: buffer for connector name (mandatory)
+ * @name_size: size of @name; must be > 0
+ * @serial: buffer for EDID-derived serial (mandatory)
+ * @serial_size: size of @serial; must be > 1
+ * @path: buffer for MST PATH property (optional, may be NULL)
+ * @path_size: size of @path buffer
+ *
+ * Fills connector name, MST PATH, and EDID-derived serial for a connector.
+ * @name is always filled when the connector is found. @serial is filled from
+ * the ASCII serial string descriptor (0xFF) if present, else from the 32-bit
+ * header serial number; left empty if no EDID is available. @path is filled
+ * only if requested and the PATH property exists.
+ *
+ * Success means the connector object was found and @name was filled.
+ * @serial and @path are best-effort and may still be empty on success.
+ *
+ * Returns: true if the connector was found, false otherwise.
+ */
+bool igt_connector_get_info(int drm_fd, uint32_t connector_id,
+ char *name, size_t name_size,
+ char *serial, size_t serial_size,
+ char *path, size_t path_size)
+{
+ drmModeConnector *conn;
+ drmModePropertyBlobPtr edid_blob = NULL;
+ uint64_t blob_id = 0;
+ drmModePropertyPtr prop;
+ const struct edid *edid;
+ int i;
+
+ igt_assert(name && name_size > 0);
+ igt_assert(serial && serial_size > 1);
+ igt_assert(!path || path_size > 0);
+
+ name[0] = '\0';
+ serial[0] = '\0';
+ if (path)
+ path[0] = '\0';
+
+ conn = drmModeGetConnector(drm_fd, connector_id);
+ if (!conn)
+ return false;
+
+ snprintf(name, name_size, "%s-%d",
+ kmstest_connector_type_str(conn->connector_type),
+ conn->connector_type_id);
+
+ for (i = 0; i < conn->count_props; i++) {
+ prop = drmModeGetProperty(drm_fd, conn->props[i]);
+ if (!prop)
+ continue;
+
+ if (strcmp(prop->name, "EDID") == 0)
+ blob_id = conn->prop_values[i];
+
+ drmModeFreeProperty(prop);
+ if (blob_id)
+ break;
+ }
+
+ /* Extract MST PATH if requested */
+ if (path) {
+ drmModePropertyBlobPtr path_blob;
+
+ /* PATH blob payload is a NUL-terminated string */
+ path_blob = kmstest_get_path_blob(drm_fd, connector_id);
+ if (path_blob) {
+ snprintf(path, path_size, "%s",
+ (const char *)path_blob->data);
+ drmModeFreePropertyBlob(path_blob);
+ }
+ }
+
+ drmModeFreeConnector(conn);
+
+ if (blob_id) {
+ edid_blob = drmModeGetPropertyBlob(drm_fd, blob_id);
+ if (edid_blob) {
+ if (edid_blob->length >= sizeof(struct edid)) {
+ edid = edid_blob->data;
+ edid_get_any_serial(edid, serial, serial_size);
+ }
+ drmModeFreePropertyBlob(edid_blob);
+ }
+ }
+
+ return true;
+}
+
+/**
+ * igt_connector_find_by_path:
+ * @drm_fd: DRM file descriptor
+ * @path: MST PATH property value to match
+ * @connector_id: output for connector ID (filled on success)
+ *
+ * Finds the currently connected connector whose PATH property matches @path
+ * (exact string comparison). PATH is the preferred stable identity for MST
+ * connectors; connector IDs may change across hotplug events.
+ *
+ * Returns: true if a matching connected connector was found.
+ */
+bool igt_connector_find_by_path(int drm_fd, const char *path,
+ uint32_t *connector_id)
+{
+ drmModeRes *res;
+ drmModeConnector *conn;
+ drmModePropertyBlobPtr path_blob;
+ bool found = false;
+ int i;
+
+ if (!path || !connector_id)
+ return false;
+
+ res = drmModeGetResources(drm_fd);
+ if (!res)
+ return false;
+
+ for (i = 0; i < res->count_connectors && !found; i++) {
+ conn = drmModeGetConnector(drm_fd, res->connectors[i]);
+ if (!conn)
+ continue;
+
+ if (conn->connection == DRM_MODE_CONNECTED) {
+ path_blob = kmstest_get_path_blob(drm_fd,
+ conn->connector_id);
+ if (path_blob) {
+ if (strcmp(path_blob->data, path) == 0) {
+ *connector_id = conn->connector_id;
+ found = true;
+ }
+ drmModeFreePropertyBlob(path_blob);
+ }
+ }
+
+ drmModeFreeConnector(conn);
+ }
+
+ drmModeFreeResources(res);
+
+ if (found)
+ igt_debug("igt_connector: Found connector %u for path '%s'\n",
+ *connector_id, path);
+
+ return found;
+}
+
+/**
+ * igt_connector_find_by_name:
+ * @drm_fd: DRM file descriptor
+ * @name: connector name to match (e.g., "DP-6")
+ * @connector_id: output for connector ID (filled on success)
+ *
+ * Name-based lookup helper. Prefer igt_connector_find_by_path() for MST
+ * connectors, since connector names and IDs may change across hotplug events.
+ *
+ * Returns: true if a matching connected connector was found.
+ */
+bool igt_connector_find_by_name(int drm_fd, const char *name,
+ uint32_t *connector_id)
+{
+ drmModeRes *res;
+ drmModeConnector *conn;
+ char conn_name[IGT_CONNECTOR_NAME_MAX];
+ bool found = false;
+ int i;
+
+ if (!name || !connector_id)
+ return false;
+
+ res = drmModeGetResources(drm_fd);
+ if (!res)
+ return false;
+
+ for (i = 0; i < res->count_connectors && !found; i++) {
+ conn = drmModeGetConnector(drm_fd, res->connectors[i]);
+ if (!conn)
+ continue;
+
+ snprintf(conn_name, sizeof(conn_name), "%s-%d",
+ kmstest_connector_type_str(conn->connector_type),
+ conn->connector_type_id);
+
+ if (strcmp(conn_name, name) == 0 &&
+ conn->connection == DRM_MODE_CONNECTED) {
+ *connector_id = conn->connector_id;
+ found = true;
+ }
+
+ drmModeFreeConnector(conn);
+ }
+
+ drmModeFreeResources(res);
+
+ if (found)
+ igt_debug("igt_connector: Found connector %u for name '%s'\n",
+ *connector_id, name);
+
+ return found;
+}
+
+/**
+ * igt_connector_reprobe_all:
+ * @drm_fd: DRM file descriptor
+ *
+ * Issues a GETCONNECTOR ioctl for every connector, requesting fresh state
+ * from the kernel. This is often sufficient to refresh connector status and
+ * MST topology visibility after hotplug events, but does not guarantee a
+ * full hardware reprobe in all driver paths.
+ */
+void igt_connector_reprobe_all(int drm_fd)
+{
+ drmModeRes *res;
+ drmModeConnector *conn;
+ int i;
+
+ res = drmModeGetResources(drm_fd);
+ if (!res)
+ return;
+
+ for (i = 0; i < res->count_connectors; i++) {
+ conn = drmModeGetConnector(drm_fd, res->connectors[i]);
+ if (conn)
+ drmModeFreeConnector(conn);
+ }
+
+ drmModeFreeResources(res);
+}
diff --git a/lib/igt_connector_helper.h b/lib/igt_connector_helper.h
new file mode 100644
index 000000000..fffb2661b
--- /dev/null
+++ b/lib/igt_connector_helper.h
@@ -0,0 +1,27 @@
+/* SPDX-License-Identifier: MIT */
+/*
+ * Copyright © 2026 Intel Corporation
+ */
+
+#ifndef IGT_CONNECTOR_HELPER_H
+#define IGT_CONNECTOR_HELPER_H
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define IGT_CONNECTOR_NAME_MAX 32
+
+int igt_connector_get_connected(int drm_fd, uint32_t *connectors,
+ int max_connectors);
+bool igt_connector_get_info(int drm_fd, uint32_t connector_id,
+ char *name, size_t name_size,
+ char *serial, size_t serial_size,
+ char *path, size_t path_size);
+bool igt_connector_find_by_path(int drm_fd, const char *path,
+ uint32_t *connector_id);
+bool igt_connector_find_by_name(int drm_fd, const char *name,
+ uint32_t *connector_id);
+void igt_connector_reprobe_all(int drm_fd);
+
+#endif /* IGT_CONNECTOR_HELPER_H */
diff --git a/lib/meson.build b/lib/meson.build
index 5c4829345..5e34ce631 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -112,6 +112,7 @@ lib_sources = [
'igt_vmwgfx.c',
'igt_psr.c',
'igt_amd.c',
+ 'igt_connector_helper.c',
'igt_edid.c',
'igt_eld.c',
'igt_infoframe.c',
--
2.25.1