drm/dp_mst: Restart last_connected_port_and_mstb() if topology ref fails
authorLyude Paul <lyude@redhat.com>
Fri, 11 Jan 2019 00:53:30 +0000 (19:53 -0500)
committerLyude Paul <lyude@redhat.com>
Fri, 11 Jan 2019 01:12:21 +0000 (20:12 -0500)
While this isn't a complete fix, this will improve the reliability of
drm_dp_get_last_connected_port_and_mstb() pretty significantly during
hotplug events, since there's a chance that the in-memory topology tree
may not be fully updated when drm_dp_get_last_connected_port_and_mstb()
is called and thus might end up causing our search to fail on an mstb
whose topology refcount has reached 0, but has not yet been removed from
it's parent.

Ideally, we should further fix this problem by ensuring that we deal
with the potential for racing with a hotplug event, which would look
like this:

* drm_dp_payload_send_msg() retrieves the last living relative of mstb
  with drm_dp_get_last_connected_port_and_mstb()
* drm_dp_payload_send_msg() starts building payload message
  At the same time, mstb gets unplugged from the topology and is no
  longer the actual last living relative of the original mstb
* drm_dp_payload_send_msg() tries sending the payload message, hub times
  out
* Hub timed out, we give up and run away-resulting in the payload being
  leaked

This could be fixed by restarting the
drm_dp_get_last_connected_port_and_mstb() search whenever we get a
timeout, sending the payload to the new mstb, then repeating until
either the entire topology is removed from the system or
drm_dp_get_last_connected_port_and_mstb() fails. But since the above
race condition is not terribly likely, we'll address that in a later
patch series once we've improved the recovery handling for VCPI
allocations in the rest of the DP MST helpers.

Changes since v1:
* Convert kerneldoc for drm_dp_get_last_connected_port_and_mstb to
  normal comment - danvet

Signed-off-by: Lyude Paul <lyude@redhat.com>
Reviewed-by: Harry Wentland <harry.wentland@amd.com>
Reviewed-by: Daniel Vetter <daniel@ffwll.ch>
Cc: David Airlie <airlied@redhat.com>
Cc: Jerry Zuo <Jerry.Zuo@amd.com>
Cc: Juston Li <juston.li@intel.com>
Link: https://patchwork.freedesktop.org/patch/msgid/20190111005343.17443-8-lyude@redhat.com
drivers/gpu/drm/drm_dp_mst_topology.c

index 79698560993306933cf12188390355ab2aca83f1..7a251905b3c490991882ca92367d5faad11627dc 100644 (file)
@@ -2048,24 +2048,40 @@ static struct drm_dp_mst_port *drm_dp_get_last_connected_port_to_mstb(struct drm
        return drm_dp_get_last_connected_port_to_mstb(mstb->port_parent->parent);
 }
 
-static struct drm_dp_mst_branch *drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr,
-                                                                        struct drm_dp_mst_branch *mstb,
-                                                                        int *port_num)
+/*
+ * Searches upwards in the topology starting from mstb to try to find the
+ * closest available parent of mstb that's still connected to the rest of the
+ * topology. This can be used in order to perform operations like releasing
+ * payloads, where the branch device which owned the payload may no longer be
+ * around and thus would require that the payload on the last living relative
+ * be freed instead.
+ */
+static struct drm_dp_mst_branch *
+drm_dp_get_last_connected_port_and_mstb(struct drm_dp_mst_topology_mgr *mgr,
+                                       struct drm_dp_mst_branch *mstb,
+                                       int *port_num)
 {
        struct drm_dp_mst_branch *rmstb = NULL;
        struct drm_dp_mst_port *found_port;
+
        mutex_lock(&mgr->lock);
-       if (mgr->mst_primary) {
+       if (!mgr->mst_primary)
+               goto out;
+
+       do {
                found_port = drm_dp_get_last_connected_port_to_mstb(mstb);
+               if (!found_port)
+                       break;
 
-               if (found_port) {
+               if (drm_dp_mst_topology_try_get_mstb(found_port->parent)) {
                        rmstb = found_port->parent;
-                       if (drm_dp_mst_topology_try_get_mstb(rmstb))
-                               *port_num = found_port->port_num;
-                       else
-                               rmstb = NULL;
+                       *port_num = found_port->port_num;
+               } else {
+                       /* Search again, starting from this parent */
+                       mstb = found_port->parent;
                }
-       }
+       } while (!rmstb);
+out:
        mutex_unlock(&mgr->lock);
        return rmstb;
 }
@@ -2114,6 +2130,14 @@ static int drm_dp_payload_send_msg(struct drm_dp_mst_topology_mgr *mgr,
 
        drm_dp_queue_down_tx(mgr, txmsg);
 
+       /*
+        * FIXME: there is a small chance that between getting the last
+        * connected mstb and sending the payload message, the last connected
+        * mstb could also be removed from the topology. In the future, this
+        * needs to be fixed by restarting the
+        * drm_dp_get_last_connected_port_and_mstb() search in the event of a
+        * timeout if the topology is still connected to the system.
+        */
        ret = drm_dp_mst_wait_tx_reply(mstb, txmsg);
        if (ret > 0) {
                if (txmsg->reply.reply_type == 1)