[igt-dev] [PATCH i-g-t v4 10/10] lib/igt_chamelium: autodiscover Chamelium port mappings

Simon Ser
2019-06-25T13:14:39Z

This removes the need for configuring Chamelium ports in .igtrc, making it both
easier and less error-prone to setup Chamelium boards.

A new function chamelium_autodiscover sets a different EDID on each Chamelium
port and plugs them. We then walk the list of DRM connectors, read back the
EDID and infer the mapping.

The EDID serial number is used to tell each port apart. In case a mapping is
already configured in the .igtrc file, we check that it's consistent.

MST is not handled yet (requires refactoring existing tests since connector IDs
aren't stable).

Signed-off-by: Simon Ser <simon.ser@intel.com>
---
lib/igt_chamelium.c | 213 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 211 insertions(+), 2 deletions(-)

diff --git a/lib/igt_chamelium.c b/lib/igt_chamelium.c
index 3cd2acafd385..05ef8bf99e67 100644
--- a/lib/igt_chamelium.c
+++ b/lib/igt_chamelium.c
@@ -360,7 +360,7 @@ static xmlrpc_value *chamelium_rpc(struct chamelium *chamelium,
*/
void chamelium_plug(struct chamelium *chamelium, struct chamelium_port *port)
{
- igt_debug("Plugging %s\n", port->name);
+ igt_debug("Plugging %s (Chamelium port ID %d)\n", port->name, port->id);
xmlrpc_DECREF(chamelium_rpc(chamelium, NULL, "Plug", "(i)", port->id));
}

@@ -1893,6 +1893,212 @@ out:
return ret;
}

+static int port_id_from_edid(int drm_fd, drmModeConnector *connector)
+{
+ int port_id = -1;
+ bool ok;
+ uint64_t edid_blob_id;
+ drmModePropertyBlobRes *edid_blob;
+ const struct edid *edid;
+ char mfg[3];
+
+ if (connector->connection != DRM_MODE_CONNECTED) {
+ igt_debug("Skipping auto-discovery for connector %s-%d: "
+ "connector status is not connected\n",
+ kmstest_connector_type_str(connector->connector_type),
+ connector->connector_type_id);
+ return -1;
+ }
+
+ ok = kmstest_get_property(drm_fd, connector->connector_id,
+ DRM_MODE_OBJECT_CONNECTOR, "EDID",
+ NULL, &edid_blob_id, NULL);
+ if (!ok || !edid_blob_id) {
+ igt_debug("Skipping auto-discovery for connector %s-%d: "
+ "missing the EDID property\n",
+ kmstest_connector_type_str(connector->connector_type),
+ connector->connector_type_id);
+ return -1;
+ }
+
+ edid_blob = drmModeGetPropertyBlob(drm_fd, edid_blob_id);
+ igt_assert(edid_blob);
+
+ edid = (const struct edid *) edid_blob->data;
+
+ edid_get_mfg(edid, mfg);
+ if (memcmp(mfg, "IGT", 3) != 0) {
+ igt_debug("Skipping connector %s-%d for auto-discovery: "
+ "manufacturer is %.3s, not IGT\n",
+ kmstest_connector_type_str(connector->connector_type),
+ connector->connector_type_id, mfg);
+ goto out;
+ }
+
+ if (edid->prod_code[0] != 'C' || edid->prod_code[1] != 'H') {
+ igt_warn("Invalid EDID for IGT connector %s-%d: "
+ "invalid product code\n",
+ kmstest_connector_type_str(connector->connector_type),
+ connector->connector_type_id);
+ goto out;
+ }
+
+ port_id = *(uint32_t *) &edid->serial;
+ igt_debug("Auto-discovery mapped connector %s-%d to Chamelium "
+ "port ID %d\n",
+ kmstest_connector_type_str(connector->connector_type),
+ connector->connector_type_id, port_id);
+
+out:
+ drmModeFreePropertyBlob(edid_blob);
+ return port_id;
+}
+
+/**
+ * chamelium_autodiscover: automagically discover the Chamelium port mapping
+ *
+ * The Chamelium API uses port IDs wheras the Device Under Test uses DRM
+ * connectors. We need to know which Chamelium port is plugged to a given DRM
+ * connector. This has typically been done via a configuration file in the
+ * past (see #chamelium_read_port_mappings), but this function provides an
+ * automatic way to do it.
+ *
+ * We will plug all Chamelium ports with a different EDID on each. Then we'll
+ * read the EDID on each DRM connector and infer the Chamelium port ID.
+ */
+static bool chamelium_autodiscover(struct chamelium *chamelium, int drm_fd)
+{
+ int candidate_ports[CHAMELIUM_MAX_PORTS];
+ size_t candidate_ports_len;
+ drmModeRes *res;
+ drmModeConnector *connector;
+ struct chamelium_port *port;
+ size_t i, j, port_count;
+ int port_id;
+ uint32_t conn_id;
+ struct chamelium_edid *edid;
+ bool found;
+ uint32_t discovered_conns[CHAMELIUM_MAX_PORTS] = {0};
+ char conn_name[64];
+ struct timespec start;
+ uint64_t elapsed_ns;
+
+ candidate_ports_len = chamelium_get_video_ports(chamelium,
+ candidate_ports);
+
+ igt_debug("Starting Chamelium port auto-discovery on %zu ports\n",
+ candidate_ports_len);
+ igt_gettime(&start);
+
+ edid = chamelium_new_edid(chamelium, igt_kms_get_base_edid());
+
+ /* Set EDID and plug ports we want to auto-discover */
+ port_count = chamelium->port_count;
+ for (i = 0; i < candidate_ports_len; i++) {
+ port_id = candidate_ports[i];
+
+ /* Get or add a chamelium_port slot */
+ port = NULL;
+ for (j = 0; j < chamelium->port_count; j++) {
+ if (chamelium->ports[j].id == port_id) {
+ port = &chamelium->ports[j];
+ break;
+ }
+ }
+ if (!port) {
+ igt_assert(port_count < CHAMELIUM_MAX_PORTS);
+ port = &chamelium->ports[port_count];
+ port_count++;
+
+ port->id = port_id;
+ }
+
+ chamelium_port_set_edid(chamelium, port, edid);
+ chamelium_plug(chamelium, port);
+ }
+
+ /* Reprobe connectors and build the mapping */
+ res = drmModeGetResources(drm_fd);
+ if (!res)
+ return false;
+
+ for (i = 0; i < res->count_connectors; i++) {
+ conn_id = res->connectors[i];
+
+ /* Read the EDID and parse the Chamelium port ID we stored
+ * there. */
+ connector = drmModeGetConnector(drm_fd, res->connectors[i]);
+ port_id = port_id_from_edid(drm_fd, connector);
+ drmModeFreeConnector(connector);
+ if (port_id < 0)
+ continue;
+
+ /* If we already have a mapping from the config file, check
+ * that it's consistent. */
+ found = false;
+ for (j = 0; j < chamelium->port_count; j++) {
+ port = &chamelium->ports[j];
+ if (port->connector_id == conn_id) {
+ found = true;
+ igt_assert_f(port->id == port_id,
+ "Inconsistency detected in .igtrc: "
+ "connector %s is configured with "
+ "Chamelium port %d, but is "
+ "connected to port %d\n",
+ port->name, port->id, port_id);
+ break;
+ }
+ }
+ if (found)
+ continue;
+
+ /* We got a new mapping */
+ found = false;
+ for (j = 0; j < candidate_ports_len; j++) {
+ if (port_id == candidate_ports[j]) {
+ found = true;
+ discovered_conns[j] = conn_id;
+ break;
+ }
+ }
+ igt_assert_f(found, "Auto-discovered a port (%d) we haven't "
+ "setup\n", port_id);
+ }
+
+ drmModeFreeResources(res);
+
+ /* We now have a Chamelium port ID ↔ DRM connector ID mapping:
+ * candidate_ports contains the Chamelium port IDs and
+ * discovered_conns contains the DRM connector IDs. */
+ for (i = 0; i < candidate_ports_len; i++) {
+ port_id = candidate_ports[i];
+ conn_id = discovered_conns[i];
+ if (!conn_id) {
+ continue;
+ }
+
+ port = &chamelium->ports[chamelium->port_count];
+ chamelium->port_count++;
+
+ port->id = port_id;
+ port->type = chamelium_get_port_type(chamelium, port);
+ port->connector_id = conn_id;
+
+ connector = drmModeGetConnectorCurrent(drm_fd, conn_id);
+ snprintf(conn_name, sizeof(conn_name), "%s-%u",
+ kmstest_connector_type_str(connector->connector_type),
+ connector->connector_type_id);
+ drmModeFreeConnector(connector);
+ port->name = strdup(conn_name);
+ }
+
+ elapsed_ns = igt_nsec_elapsed(&start);
+ igt_debug("Auto-discovery took %fms\n",
+ (float) elapsed_ns / (1000 * 1000));
+
+ return true;
+}
+
static bool chamelium_read_config(struct chamelium *chamelium, int drm_fd)
{
GError *error = NULL;
@@ -1910,7 +2116,10 @@ static bool chamelium_read_config(struct chamelium *chamelium, int drm_fd)
return false;
}

- return chamelium_read_port_mappings(chamelium, drm_fd);
+ if (!chamelium_read_port_mappings(chamelium, drm_fd)) {
+ return false;
+ }
+ return chamelium_autodiscover(chamelium, drm_fd);
}

/**
--
2.22.0