Re: [Nouveau] [PATCH v2 03/27] drm/dp_mst: Destroy MSTBs asynchronously

2019-10-08 Thread Daniel Vetter
On Fri, Sep 27, 2019 at 09:31:07AM -0400, Sean Paul wrote:
> On Wed, Sep 25, 2019 at 04:08:22PM -0400, Lyude Paul wrote:
> > On Wed, 2019-09-25 at 14:16 -0400, Sean Paul wrote:
> > > On Tue, Sep 03, 2019 at 04:45:41PM -0400, Lyude Paul wrote:
> > > > When reprobing an MST topology during resume, we have to account for the
> > > > fact that while we were suspended it's possible that mstbs may have been
> > > > removed from any ports in the topology. Since iterating downwards in the
> > > > topology requires that we hold >lock, destroying MSTBs from this
> > > > context would result in attempting to lock >lock a second time and
> > > > deadlocking.
> > > > 
> > > > So, fix this by first moving destruction of MSTBs into
> > > > destroy_connector_work, then rename destroy_connector_work and friends
> > > > to reflect that they now destroy both ports and mstbs.
> > > > 
> > > > Changes since v1:
> > > > * s/destroy_connector_list/destroy_port_list/
> > > >   s/connector_destroy_lock/delayed_destroy_lock/
> > > >   s/connector_destroy_work/delayed_destroy_work/
> > > >   s/drm_dp_finish_destroy_branch_device/drm_dp_delayed_destroy_mstb/
> > > >   s/drm_dp_finish_destroy_port/drm_dp_delayed_destroy_port/
> > > >   - danvet
> > > > * Use two loops in drm_dp_delayed_destroy_work() - danvet
> > > > * Better explain why we need to do this - danvet
> > > > * Use cancel_work_sync() instead of flush_work() - flush_work() doesn't
> > > >   account for work requeing
> > > > 
> > > > Cc: Juston Li 
> > > > Cc: Imre Deak 
> > > > Cc: Ville Syrjälä 
> > > > Cc: Harry Wentland 
> > > > Cc: Daniel Vetter 
> > > > Signed-off-by: Lyude Paul 
> > > 
> > > Took me a while to grok this, and I'm still not 100% confident my mental
> > > model
> > > is correct, so please bear with me while I ask silly questions :)
> > > 
> > > Now that the destroy is delayed, and the port remains in the topology, is 
> > > it
> > > possible we will underflow the topology kref by calling put_mstb multiple
> > > times?
> > > It looks like that would result in a WARN from refcount.c, and wouldn't 
> > > call
> > > the
> > > destroy function multiple times, so that's nice :)
> > > 
> > > Similarly, is there any defense against calling get_mstb() between 
> > > destroy()
> > > and
> > > the delayed destroy worker running?
> > > 
> > Good question! There's only one instance where we unconditionally grab an 
> > MSTB
> > ref, drm_dp_mst_topology_mgr_set_mst(), and in that location we're 
> > guaranteed
> > to be the only one with access to that mstb until we drop >lock.
> > Everywhere else we use drm_dp_mst_topology_try_get_mstb(), which uses
> > kref_get_unless_zero() to protect against that kind of situation (and forces
> > the caller to check with __must_check)

Imo would be good to add this to the commit message when merging as a Q,
just for the record. At least I like to do that with the
silly-but-no-so-silly questions that come up in review.
-Daniel

> 
> Awesome, thanks for the breakdown!
> 
> 
> Reviewed-by: Sean Paul 
> 
> 
> > 
> > > Sean
> > > 
> > > > ---
> > > >  drivers/gpu/drm/drm_dp_mst_topology.c | 162 +-
> > > >  include/drm/drm_dp_mst_helper.h   |  26 +++--
> > > >  2 files changed, 127 insertions(+), 61 deletions(-)
> > > > 
> > > > diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c
> > > > b/drivers/gpu/drm/drm_dp_mst_topology.c
> > > > index 3054ec622506..738f260d4b15 100644
> > > > --- a/drivers/gpu/drm/drm_dp_mst_topology.c
> > > > +++ b/drivers/gpu/drm/drm_dp_mst_topology.c
> > > > @@ -1113,34 +1113,17 @@ static void
> > > > drm_dp_destroy_mst_branch_device(struct kref *kref)
> > > > struct drm_dp_mst_branch *mstb =
> > > > container_of(kref, struct drm_dp_mst_branch, 
> > > > topology_kref);
> > > > struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
> > > > -   struct drm_dp_mst_port *port, *tmp;
> > > > -   bool wake_tx = false;
> > > >  
> > > > -   mutex_lock(>lock);
> > > > -   list_for_each_entry_safe(port, tmp, >ports, next) {
> > > > -   list_del(>next);
> > > > -   drm_dp_mst_topology_put_port(port);
> > > > -   }
> > > > -   mutex_unlock(>lock);
> > > > -
> > > > -   /* drop any tx slots msg */
> > > > -   mutex_lock(>mgr->qlock);
> > > > -   if (mstb->tx_slots[0]) {
> > > > -   mstb->tx_slots[0]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> > > > -   mstb->tx_slots[0] = NULL;
> > > > -   wake_tx = true;
> > > > -   }
> > > > -   if (mstb->tx_slots[1]) {
> > > > -   mstb->tx_slots[1]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> > > > -   mstb->tx_slots[1] = NULL;
> > > > -   wake_tx = true;
> > > > -   }
> > > > -   mutex_unlock(>mgr->qlock);
> > > > +   INIT_LIST_HEAD(>destroy_next);
> > > >  
> > > > -   if (wake_tx)
> > > > -   wake_up_all(>mgr->tx_waitq);
> > > > -
> > > > -   

Re: [Nouveau] [PATCH v2 03/27] drm/dp_mst: Destroy MSTBs asynchronously

2019-09-27 Thread Sean Paul
On Wed, Sep 25, 2019 at 04:08:22PM -0400, Lyude Paul wrote:
> On Wed, 2019-09-25 at 14:16 -0400, Sean Paul wrote:
> > On Tue, Sep 03, 2019 at 04:45:41PM -0400, Lyude Paul wrote:
> > > When reprobing an MST topology during resume, we have to account for the
> > > fact that while we were suspended it's possible that mstbs may have been
> > > removed from any ports in the topology. Since iterating downwards in the
> > > topology requires that we hold >lock, destroying MSTBs from this
> > > context would result in attempting to lock >lock a second time and
> > > deadlocking.
> > > 
> > > So, fix this by first moving destruction of MSTBs into
> > > destroy_connector_work, then rename destroy_connector_work and friends
> > > to reflect that they now destroy both ports and mstbs.
> > > 
> > > Changes since v1:
> > > * s/destroy_connector_list/destroy_port_list/
> > >   s/connector_destroy_lock/delayed_destroy_lock/
> > >   s/connector_destroy_work/delayed_destroy_work/
> > >   s/drm_dp_finish_destroy_branch_device/drm_dp_delayed_destroy_mstb/
> > >   s/drm_dp_finish_destroy_port/drm_dp_delayed_destroy_port/
> > >   - danvet
> > > * Use two loops in drm_dp_delayed_destroy_work() - danvet
> > > * Better explain why we need to do this - danvet
> > > * Use cancel_work_sync() instead of flush_work() - flush_work() doesn't
> > >   account for work requeing
> > > 
> > > Cc: Juston Li 
> > > Cc: Imre Deak 
> > > Cc: Ville Syrjälä 
> > > Cc: Harry Wentland 
> > > Cc: Daniel Vetter 
> > > Signed-off-by: Lyude Paul 
> > 
> > Took me a while to grok this, and I'm still not 100% confident my mental
> > model
> > is correct, so please bear with me while I ask silly questions :)
> > 
> > Now that the destroy is delayed, and the port remains in the topology, is it
> > possible we will underflow the topology kref by calling put_mstb multiple
> > times?
> > It looks like that would result in a WARN from refcount.c, and wouldn't call
> > the
> > destroy function multiple times, so that's nice :)
> > 
> > Similarly, is there any defense against calling get_mstb() between destroy()
> > and
> > the delayed destroy worker running?
> > 
> Good question! There's only one instance where we unconditionally grab an MSTB
> ref, drm_dp_mst_topology_mgr_set_mst(), and in that location we're guaranteed
> to be the only one with access to that mstb until we drop >lock.
> Everywhere else we use drm_dp_mst_topology_try_get_mstb(), which uses
> kref_get_unless_zero() to protect against that kind of situation (and forces
> the caller to check with __must_check)

Awesome, thanks for the breakdown!


Reviewed-by: Sean Paul 


> 
> > Sean
> > 
> > > ---
> > >  drivers/gpu/drm/drm_dp_mst_topology.c | 162 +-
> > >  include/drm/drm_dp_mst_helper.h   |  26 +++--
> > >  2 files changed, 127 insertions(+), 61 deletions(-)
> > > 
> > > diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c
> > > b/drivers/gpu/drm/drm_dp_mst_topology.c
> > > index 3054ec622506..738f260d4b15 100644
> > > --- a/drivers/gpu/drm/drm_dp_mst_topology.c
> > > +++ b/drivers/gpu/drm/drm_dp_mst_topology.c
> > > @@ -1113,34 +1113,17 @@ static void
> > > drm_dp_destroy_mst_branch_device(struct kref *kref)
> > >   struct drm_dp_mst_branch *mstb =
> > >   container_of(kref, struct drm_dp_mst_branch, topology_kref);
> > >   struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
> > > - struct drm_dp_mst_port *port, *tmp;
> > > - bool wake_tx = false;
> > >  
> > > - mutex_lock(>lock);
> > > - list_for_each_entry_safe(port, tmp, >ports, next) {
> > > - list_del(>next);
> > > - drm_dp_mst_topology_put_port(port);
> > > - }
> > > - mutex_unlock(>lock);
> > > -
> > > - /* drop any tx slots msg */
> > > - mutex_lock(>mgr->qlock);
> > > - if (mstb->tx_slots[0]) {
> > > - mstb->tx_slots[0]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> > > - mstb->tx_slots[0] = NULL;
> > > - wake_tx = true;
> > > - }
> > > - if (mstb->tx_slots[1]) {
> > > - mstb->tx_slots[1]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> > > - mstb->tx_slots[1] = NULL;
> > > - wake_tx = true;
> > > - }
> > > - mutex_unlock(>mgr->qlock);
> > > + INIT_LIST_HEAD(>destroy_next);
> > >  
> > > - if (wake_tx)
> > > - wake_up_all(>mgr->tx_waitq);
> > > -
> > > - drm_dp_mst_put_mstb_malloc(mstb);
> > > + /*
> > > +  * This can get called under mgr->mutex, so we need to perform the
> > > +  * actual destruction of the mstb in another worker
> > > +  */
> > > + mutex_lock(>delayed_destroy_lock);
> > > + list_add(>destroy_next, >destroy_branch_device_list);
> > > + mutex_unlock(>delayed_destroy_lock);
> > > + schedule_work(>delayed_destroy_work);
> > >  }
> > >  
> > >  /**
> > > @@ -1255,10 +1238,10 @@ static void drm_dp_destroy_port(struct kref *kref)
> > >* we might be holding the mode_config.mutex
> > >* from an EDID retrieval */
> > >  
> > > - mutex_lock(>destroy_connector_lock);
> > > -  

Re: [Nouveau] [PATCH v2 03/27] drm/dp_mst: Destroy MSTBs asynchronously

2019-09-25 Thread Lyude Paul
On Wed, 2019-09-25 at 14:16 -0400, Sean Paul wrote:
> On Tue, Sep 03, 2019 at 04:45:41PM -0400, Lyude Paul wrote:
> > When reprobing an MST topology during resume, we have to account for the
> > fact that while we were suspended it's possible that mstbs may have been
> > removed from any ports in the topology. Since iterating downwards in the
> > topology requires that we hold >lock, destroying MSTBs from this
> > context would result in attempting to lock >lock a second time and
> > deadlocking.
> > 
> > So, fix this by first moving destruction of MSTBs into
> > destroy_connector_work, then rename destroy_connector_work and friends
> > to reflect that they now destroy both ports and mstbs.
> > 
> > Changes since v1:
> > * s/destroy_connector_list/destroy_port_list/
> >   s/connector_destroy_lock/delayed_destroy_lock/
> >   s/connector_destroy_work/delayed_destroy_work/
> >   s/drm_dp_finish_destroy_branch_device/drm_dp_delayed_destroy_mstb/
> >   s/drm_dp_finish_destroy_port/drm_dp_delayed_destroy_port/
> >   - danvet
> > * Use two loops in drm_dp_delayed_destroy_work() - danvet
> > * Better explain why we need to do this - danvet
> > * Use cancel_work_sync() instead of flush_work() - flush_work() doesn't
> >   account for work requeing
> > 
> > Cc: Juston Li 
> > Cc: Imre Deak 
> > Cc: Ville Syrjälä 
> > Cc: Harry Wentland 
> > Cc: Daniel Vetter 
> > Signed-off-by: Lyude Paul 
> 
> Took me a while to grok this, and I'm still not 100% confident my mental
> model
> is correct, so please bear with me while I ask silly questions :)
> 
> Now that the destroy is delayed, and the port remains in the topology, is it
> possible we will underflow the topology kref by calling put_mstb multiple
> times?
> It looks like that would result in a WARN from refcount.c, and wouldn't call
> the
> destroy function multiple times, so that's nice :)
> 
> Similarly, is there any defense against calling get_mstb() between destroy()
> and
> the delayed destroy worker running?
> 
Good question! There's only one instance where we unconditionally grab an MSTB
ref, drm_dp_mst_topology_mgr_set_mst(), and in that location we're guaranteed
to be the only one with access to that mstb until we drop >lock.
Everywhere else we use drm_dp_mst_topology_try_get_mstb(), which uses
kref_get_unless_zero() to protect against that kind of situation (and forces
the caller to check with __must_check)

> Sean
> 
> > ---
> >  drivers/gpu/drm/drm_dp_mst_topology.c | 162 +-
> >  include/drm/drm_dp_mst_helper.h   |  26 +++--
> >  2 files changed, 127 insertions(+), 61 deletions(-)
> > 
> > diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c
> > b/drivers/gpu/drm/drm_dp_mst_topology.c
> > index 3054ec622506..738f260d4b15 100644
> > --- a/drivers/gpu/drm/drm_dp_mst_topology.c
> > +++ b/drivers/gpu/drm/drm_dp_mst_topology.c
> > @@ -1113,34 +1113,17 @@ static void
> > drm_dp_destroy_mst_branch_device(struct kref *kref)
> > struct drm_dp_mst_branch *mstb =
> > container_of(kref, struct drm_dp_mst_branch, topology_kref);
> > struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
> > -   struct drm_dp_mst_port *port, *tmp;
> > -   bool wake_tx = false;
> >  
> > -   mutex_lock(>lock);
> > -   list_for_each_entry_safe(port, tmp, >ports, next) {
> > -   list_del(>next);
> > -   drm_dp_mst_topology_put_port(port);
> > -   }
> > -   mutex_unlock(>lock);
> > -
> > -   /* drop any tx slots msg */
> > -   mutex_lock(>mgr->qlock);
> > -   if (mstb->tx_slots[0]) {
> > -   mstb->tx_slots[0]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> > -   mstb->tx_slots[0] = NULL;
> > -   wake_tx = true;
> > -   }
> > -   if (mstb->tx_slots[1]) {
> > -   mstb->tx_slots[1]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> > -   mstb->tx_slots[1] = NULL;
> > -   wake_tx = true;
> > -   }
> > -   mutex_unlock(>mgr->qlock);
> > +   INIT_LIST_HEAD(>destroy_next);
> >  
> > -   if (wake_tx)
> > -   wake_up_all(>mgr->tx_waitq);
> > -
> > -   drm_dp_mst_put_mstb_malloc(mstb);
> > +   /*
> > +* This can get called under mgr->mutex, so we need to perform the
> > +* actual destruction of the mstb in another worker
> > +*/
> > +   mutex_lock(>delayed_destroy_lock);
> > +   list_add(>destroy_next, >destroy_branch_device_list);
> > +   mutex_unlock(>delayed_destroy_lock);
> > +   schedule_work(>delayed_destroy_work);
> >  }
> >  
> >  /**
> > @@ -1255,10 +1238,10 @@ static void drm_dp_destroy_port(struct kref *kref)
> >  * we might be holding the mode_config.mutex
> >  * from an EDID retrieval */
> >  
> > -   mutex_lock(>destroy_connector_lock);
> > -   list_add(>next, >destroy_connector_list);
> > -   mutex_unlock(>destroy_connector_lock);
> > -   schedule_work(>destroy_connector_work);
> > +   mutex_lock(>delayed_destroy_lock);
> > +   list_add(>next, 

Re: [Nouveau] [PATCH v2 03/27] drm/dp_mst: Destroy MSTBs asynchronously

2019-09-25 Thread Sean Paul
On Tue, Sep 03, 2019 at 04:45:41PM -0400, Lyude Paul wrote:
> When reprobing an MST topology during resume, we have to account for the
> fact that while we were suspended it's possible that mstbs may have been
> removed from any ports in the topology. Since iterating downwards in the
> topology requires that we hold >lock, destroying MSTBs from this
> context would result in attempting to lock >lock a second time and
> deadlocking.
> 
> So, fix this by first moving destruction of MSTBs into
> destroy_connector_work, then rename destroy_connector_work and friends
> to reflect that they now destroy both ports and mstbs.
> 
> Changes since v1:
> * s/destroy_connector_list/destroy_port_list/
>   s/connector_destroy_lock/delayed_destroy_lock/
>   s/connector_destroy_work/delayed_destroy_work/
>   s/drm_dp_finish_destroy_branch_device/drm_dp_delayed_destroy_mstb/
>   s/drm_dp_finish_destroy_port/drm_dp_delayed_destroy_port/
>   - danvet
> * Use two loops in drm_dp_delayed_destroy_work() - danvet
> * Better explain why we need to do this - danvet
> * Use cancel_work_sync() instead of flush_work() - flush_work() doesn't
>   account for work requeing
> 
> Cc: Juston Li 
> Cc: Imre Deak 
> Cc: Ville Syrjälä 
> Cc: Harry Wentland 
> Cc: Daniel Vetter 
> Signed-off-by: Lyude Paul 

Took me a while to grok this, and I'm still not 100% confident my mental model
is correct, so please bear with me while I ask silly questions :)

Now that the destroy is delayed, and the port remains in the topology, is it
possible we will underflow the topology kref by calling put_mstb multiple times?
It looks like that would result in a WARN from refcount.c, and wouldn't call the
destroy function multiple times, so that's nice :)

Similarly, is there any defense against calling get_mstb() between destroy() and
the delayed destroy worker running?

Sean

> ---
>  drivers/gpu/drm/drm_dp_mst_topology.c | 162 +-
>  include/drm/drm_dp_mst_helper.h   |  26 +++--
>  2 files changed, 127 insertions(+), 61 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c 
> b/drivers/gpu/drm/drm_dp_mst_topology.c
> index 3054ec622506..738f260d4b15 100644
> --- a/drivers/gpu/drm/drm_dp_mst_topology.c
> +++ b/drivers/gpu/drm/drm_dp_mst_topology.c
> @@ -1113,34 +1113,17 @@ static void drm_dp_destroy_mst_branch_device(struct 
> kref *kref)
>   struct drm_dp_mst_branch *mstb =
>   container_of(kref, struct drm_dp_mst_branch, topology_kref);
>   struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
> - struct drm_dp_mst_port *port, *tmp;
> - bool wake_tx = false;
>  
> - mutex_lock(>lock);
> - list_for_each_entry_safe(port, tmp, >ports, next) {
> - list_del(>next);
> - drm_dp_mst_topology_put_port(port);
> - }
> - mutex_unlock(>lock);
> -
> - /* drop any tx slots msg */
> - mutex_lock(>mgr->qlock);
> - if (mstb->tx_slots[0]) {
> - mstb->tx_slots[0]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> - mstb->tx_slots[0] = NULL;
> - wake_tx = true;
> - }
> - if (mstb->tx_slots[1]) {
> - mstb->tx_slots[1]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
> - mstb->tx_slots[1] = NULL;
> - wake_tx = true;
> - }
> - mutex_unlock(>mgr->qlock);
> + INIT_LIST_HEAD(>destroy_next);
>  
> - if (wake_tx)
> - wake_up_all(>mgr->tx_waitq);
> -
> - drm_dp_mst_put_mstb_malloc(mstb);
> + /*
> +  * This can get called under mgr->mutex, so we need to perform the
> +  * actual destruction of the mstb in another worker
> +  */
> + mutex_lock(>delayed_destroy_lock);
> + list_add(>destroy_next, >destroy_branch_device_list);
> + mutex_unlock(>delayed_destroy_lock);
> + schedule_work(>delayed_destroy_work);
>  }
>  
>  /**
> @@ -1255,10 +1238,10 @@ static void drm_dp_destroy_port(struct kref *kref)
>* we might be holding the mode_config.mutex
>* from an EDID retrieval */
>  
> - mutex_lock(>destroy_connector_lock);
> - list_add(>next, >destroy_connector_list);
> - mutex_unlock(>destroy_connector_lock);
> - schedule_work(>destroy_connector_work);
> + mutex_lock(>delayed_destroy_lock);
> + list_add(>next, >destroy_port_list);
> + mutex_unlock(>delayed_destroy_lock);
> + schedule_work(>delayed_destroy_work);
>   return;
>   }
>   /* no need to clean up vcpi
> @@ -2792,7 +2775,7 @@ void drm_dp_mst_topology_mgr_suspend(struct 
> drm_dp_mst_topology_mgr *mgr)
>  DP_MST_EN | DP_UPSTREAM_IS_SRC);
>   mutex_unlock(>lock);
>   flush_work(>work);
> - flush_work(>destroy_connector_work);
> + flush_work(>delayed_destroy_work);
>  }
>  EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend);
>  

[Nouveau] [PATCH v2 03/27] drm/dp_mst: Destroy MSTBs asynchronously

2019-09-03 Thread Lyude Paul
When reprobing an MST topology during resume, we have to account for the
fact that while we were suspended it's possible that mstbs may have been
removed from any ports in the topology. Since iterating downwards in the
topology requires that we hold >lock, destroying MSTBs from this
context would result in attempting to lock >lock a second time and
deadlocking.

So, fix this by first moving destruction of MSTBs into
destroy_connector_work, then rename destroy_connector_work and friends
to reflect that they now destroy both ports and mstbs.

Changes since v1:
* s/destroy_connector_list/destroy_port_list/
  s/connector_destroy_lock/delayed_destroy_lock/
  s/connector_destroy_work/delayed_destroy_work/
  s/drm_dp_finish_destroy_branch_device/drm_dp_delayed_destroy_mstb/
  s/drm_dp_finish_destroy_port/drm_dp_delayed_destroy_port/
  - danvet
* Use two loops in drm_dp_delayed_destroy_work() - danvet
* Better explain why we need to do this - danvet
* Use cancel_work_sync() instead of flush_work() - flush_work() doesn't
  account for work requeing

Cc: Juston Li 
Cc: Imre Deak 
Cc: Ville Syrjälä 
Cc: Harry Wentland 
Cc: Daniel Vetter 
Signed-off-by: Lyude Paul 
---
 drivers/gpu/drm/drm_dp_mst_topology.c | 162 +-
 include/drm/drm_dp_mst_helper.h   |  26 +++--
 2 files changed, 127 insertions(+), 61 deletions(-)

diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c 
b/drivers/gpu/drm/drm_dp_mst_topology.c
index 3054ec622506..738f260d4b15 100644
--- a/drivers/gpu/drm/drm_dp_mst_topology.c
+++ b/drivers/gpu/drm/drm_dp_mst_topology.c
@@ -1113,34 +1113,17 @@ static void drm_dp_destroy_mst_branch_device(struct 
kref *kref)
struct drm_dp_mst_branch *mstb =
container_of(kref, struct drm_dp_mst_branch, topology_kref);
struct drm_dp_mst_topology_mgr *mgr = mstb->mgr;
-   struct drm_dp_mst_port *port, *tmp;
-   bool wake_tx = false;
 
-   mutex_lock(>lock);
-   list_for_each_entry_safe(port, tmp, >ports, next) {
-   list_del(>next);
-   drm_dp_mst_topology_put_port(port);
-   }
-   mutex_unlock(>lock);
-
-   /* drop any tx slots msg */
-   mutex_lock(>mgr->qlock);
-   if (mstb->tx_slots[0]) {
-   mstb->tx_slots[0]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
-   mstb->tx_slots[0] = NULL;
-   wake_tx = true;
-   }
-   if (mstb->tx_slots[1]) {
-   mstb->tx_slots[1]->state = DRM_DP_SIDEBAND_TX_TIMEOUT;
-   mstb->tx_slots[1] = NULL;
-   wake_tx = true;
-   }
-   mutex_unlock(>mgr->qlock);
+   INIT_LIST_HEAD(>destroy_next);
 
-   if (wake_tx)
-   wake_up_all(>mgr->tx_waitq);
-
-   drm_dp_mst_put_mstb_malloc(mstb);
+   /*
+* This can get called under mgr->mutex, so we need to perform the
+* actual destruction of the mstb in another worker
+*/
+   mutex_lock(>delayed_destroy_lock);
+   list_add(>destroy_next, >destroy_branch_device_list);
+   mutex_unlock(>delayed_destroy_lock);
+   schedule_work(>delayed_destroy_work);
 }
 
 /**
@@ -1255,10 +1238,10 @@ static void drm_dp_destroy_port(struct kref *kref)
 * we might be holding the mode_config.mutex
 * from an EDID retrieval */
 
-   mutex_lock(>destroy_connector_lock);
-   list_add(>next, >destroy_connector_list);
-   mutex_unlock(>destroy_connector_lock);
-   schedule_work(>destroy_connector_work);
+   mutex_lock(>delayed_destroy_lock);
+   list_add(>next, >destroy_port_list);
+   mutex_unlock(>delayed_destroy_lock);
+   schedule_work(>delayed_destroy_work);
return;
}
/* no need to clean up vcpi
@@ -2792,7 +2775,7 @@ void drm_dp_mst_topology_mgr_suspend(struct 
drm_dp_mst_topology_mgr *mgr)
   DP_MST_EN | DP_UPSTREAM_IS_SRC);
mutex_unlock(>lock);
flush_work(>work);
-   flush_work(>destroy_connector_work);
+   flush_work(>delayed_destroy_work);
 }
 EXPORT_SYMBOL(drm_dp_mst_topology_mgr_suspend);
 
@@ -3740,34 +3723,104 @@ static void drm_dp_tx_work(struct work_struct *work)
mutex_unlock(>qlock);
 }
 
-static void drm_dp_destroy_connector_work(struct work_struct *work)
+static inline void
+drm_dp_delayed_destroy_port(struct drm_dp_mst_port *port)
 {
-   struct drm_dp_mst_topology_mgr *mgr = container_of(work, struct 
drm_dp_mst_topology_mgr, destroy_connector_work);
-   struct drm_dp_mst_port *port;
-   bool send_hotplug = false;
+   port->mgr->cbs->destroy_connector(port->mgr, port->connector);
+
+   drm_dp_port_teardown_pdt(port, port->pdt);
+   port->pdt = DP_PEER_DEVICE_NONE;
+
+   drm_dp_mst_put_port_malloc(port);
+}
+
+static inline void
+drm_dp_delayed_destroy_mstb(struct