Message ID | 20181220001959.12486-4-lyude@redhat.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | MST refcounting/atomic helpers cleanup | expand |
On 2018-12-19 7:19 p.m., Lyude Paul wrote: > 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. > > Signed-off-by: Lyude Paul <lyude@redhat.com> > Cc: Daniel Vetter <daniel@ffwll.ch> > Cc: David Airlie <airlied@redhat.com> > Cc: Jerry Zuo <Jerry.Zuo@amd.com> > Cc: Harry Wentland <harry.wentland@amd.com> > Cc: Juston Li <juston.li@intel.com> Reviewed-by: Harry Wentland <harry.wentland@amd.com> Harry > --- > drivers/gpu/drm/drm_dp_mst_topology.c | 55 +++++++++++++++++++++------ > 1 file changed, 44 insertions(+), 11 deletions(-) > > diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c > index b380ada09e90..356a95aba2d8 100644 > --- a/drivers/gpu/drm/drm_dp_mst_topology.c > +++ b/drivers/gpu/drm/drm_dp_mst_topology.c > @@ -2043,25 +2043,50 @@ 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) > +/** > + * drm_dp_get_last_connected_port_and_mstb() - Find the last living relatives > + * in a topology of a given branch device > + * @mgr: The topology manager to use > + * @mstb: The disconnected branch device > + * @port_num: Where to store the number of the last connected port > + * > + * 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. > + * > + * Returns: > + * The last connected &drm_dp_mst_branch in the topology that was a parent of > + * @mstb, if there is one. > + */ > +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; > } > @@ -2110,6 +2135,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) { >
diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c b/drivers/gpu/drm/drm_dp_mst_topology.c index b380ada09e90..356a95aba2d8 100644 --- a/drivers/gpu/drm/drm_dp_mst_topology.c +++ b/drivers/gpu/drm/drm_dp_mst_topology.c @@ -2043,25 +2043,50 @@ 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) +/** + * drm_dp_get_last_connected_port_and_mstb() - Find the last living relatives + * in a topology of a given branch device + * @mgr: The topology manager to use + * @mstb: The disconnected branch device + * @port_num: Where to store the number of the last connected port + * + * 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. + * + * Returns: + * The last connected &drm_dp_mst_branch in the topology that was a parent of + * @mstb, if there is one. + */ +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; } @@ -2110,6 +2135,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) {
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. Signed-off-by: Lyude Paul <lyude@redhat.com> Cc: Daniel Vetter <daniel@ffwll.ch> Cc: David Airlie <airlied@redhat.com> Cc: Jerry Zuo <Jerry.Zuo@amd.com> Cc: Harry Wentland <harry.wentland@amd.com> Cc: Juston Li <juston.li@intel.com> --- drivers/gpu/drm/drm_dp_mst_topology.c | 55 +++++++++++++++++++++------ 1 file changed, 44 insertions(+), 11 deletions(-)