On Tue, Oct 23, 2018 at 07:12:49PM -0400, Lyude Paul wrote:
> There has been a TODO waiting for quite a long time in
> drm_dp_mst_topology.c:
> 
>       /* We cannot rely on port->vcpi.num_slots to update
>        * topology_state->avail_slots as the port may not exist if the parent
>        * branch device was unplugged. This should be fixed by tracking
>        * per-port slot allocation in drm_dp_mst_topology_state instead of
>        * depending on the caller to tell us how many slots to release.
>        */
> 
> That's not the only reason we should fix this: forcing the driver to
> track the VCPI allocations throughout a state's atomic check is
> error prone, because it means that extra care has to be taken with the
> order that drm_dp_atomic_find_vcpi_slots() and
> drm_dp_atomic_release_vcpi_slots() are called in in order to ensure
> idempotency. Currently the only driver actually using these helpers,
> i915, doesn't even do this correctly: multiple ->best_encoder() checks
> with i915's current implementation would not be idempotent and would
> over-allocate VCPI slots, something I learned trying to implement
> fallback retraining in MST.
> 
> So: simplify this whole mess, and teach drm_dp_atomic_find_vcpi_slots()
> and drm_dp_atomic_release_vcpi_slots() to track the VCPI allocations for
> each port. This allows us to ensure idempotency without having to rely
> on the driver as much. Additionally: the driver doesn't need to do any
> kind of VCPI slot tracking anymore if it doesn't need it for it's own
> internal state.
> 
> Additionally; this moves the code for checking whether or not the
> VCPI allocations in the atomic state are valid into the new
> ->atomic_check() hook for private objects, so we can ensure that we only
> check the final VCPI allocation that would be incurred by the new state
> and not the mid-check VCPI allocations. This prevents us from failing
> checks which end up allocating VCPI slots to ports before freeing any of
> the VCPI slots allocated by ports which are about to be disabled.
> 
> Also: update the documentation and make it more obvious that these
> /must/ be called by /all/ atomic drivers supporting MST.
> 
> Signed-off-by: Lyude Paul <ly...@redhat.com>
> Cc: Daniel Vetter <daniel.vet...@ffwll.ch>

lgtm, but I'll wait with detailed review until we have the "where should
we atomic_check this" question sorted out.
-Daniel

> ---
>  drivers/gpu/drm/drm_dp_mst_topology.c | 172 +++++++++++++++++++++-----
>  drivers/gpu/drm/i915/intel_dp_mst.c   |  31 ++---
>  include/drm/drm_dp_mst_helper.h       |  10 +-
>  3 files changed, 167 insertions(+), 46 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_dp_mst_topology.c 
> b/drivers/gpu/drm/drm_dp_mst_topology.c
> index 8c3cfac437f4..adb4298570cc 100644
> --- a/drivers/gpu/drm/drm_dp_mst_topology.c
> +++ b/drivers/gpu/drm/drm_dp_mst_topology.c
> @@ -2614,21 +2614,33 @@ static int drm_dp_init_vcpi(struct 
> drm_dp_mst_topology_mgr *mgr,
>  }
>  
>  /**
> - * drm_dp_atomic_find_vcpi_slots() - Find and add vcpi slots to the state
> + * drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state
>   * @state: global atomic state
>   * @mgr: MST topology manager for the port
>   * @port: port to find vcpi slots for
>   * @pbn: bandwidth required for the mode in PBN
>   *
> + * Allocates VCPI slots to @port, replacing any previous VCPI allocations it
> + * may have had. Any atomic drivers which support MST must call this function
> + * in their atomic_check() handlers to change the current VCPI allocation for
> + * the new state. After the ->atomic_check() hooks of the driver and all 
> other
> + * mode objects in the state have been called, DRM will check the final VCPI
> + * allocations to ensure that they will fit into the available bandwidth on
> + * the topology.
> + *
> + * See also: drm_dp_atomic_release_vcpi_slots()
> + *
>   * RETURNS:
> - * Total slots in the atomic state assigned for this port or error
> + * Total slots in the atomic state assigned for this port, or a negative 
> error
> + * code if the port no longer exists
>   */
>  int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state,
>                                 struct drm_dp_mst_topology_mgr *mgr,
>                                 struct drm_dp_mst_port *port, int pbn)
>  {
>       struct drm_dp_mst_topology_state *topology_state;
> -     int req_slots;
> +     struct drm_dp_vcpi_allocation *pos, *vcpi = NULL;
> +     int prev_slots, req_slots, ret;
>  
>       topology_state = drm_atomic_get_mst_topology_state(state, mgr);
>       if (IS_ERR(topology_state))
> @@ -2637,20 +2649,41 @@ int drm_dp_atomic_find_vcpi_slots(struct 
> drm_atomic_state *state,
>       port = drm_dp_get_validated_port_ref(mgr, port);
>       if (port == NULL)
>               return -EINVAL;
> -     req_slots = DIV_ROUND_UP(pbn, mgr->pbn_div);
> -     DRM_DEBUG_KMS("vcpi slots req=%d, avail=%d\n",
> -                     req_slots, topology_state->avail_slots);
>  
> -     if (req_slots > topology_state->avail_slots) {
> -             drm_dp_put_port(port);
> -             return -ENOSPC;
> +     /* Find the current allocation for this port, if any */
> +     list_for_each_entry(pos, &topology_state->vcpis, next) {
> +             if (pos->port == port) {
> +                     vcpi = pos;
> +                     prev_slots = vcpi->vcpi;
> +                     break;
> +             }
>       }
> +     if (!vcpi)
> +             prev_slots = 0;
>  
> -     topology_state->avail_slots -= req_slots;
> -     DRM_DEBUG_KMS("vcpi slots avail=%d", topology_state->avail_slots);
> +     req_slots = DIV_ROUND_UP(pbn, mgr->pbn_div);
> +
> +     DRM_DEBUG_KMS("[CONNECTOR:%d:%s] [MST PORT:%p] vcpi %d -> %d\n",
> +                   port->connector->base.id, port->connector->name, port,
> +                   prev_slots, req_slots);
>  
> +     /* Add the new allocation to the state */
> +     if (!vcpi) {
> +             vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL);
> +             if (!vcpi) {
> +                     ret = -ENOMEM;
> +                     goto out;
> +             }
> +
> +             vcpi->port = port;
> +             list_add(&vcpi->next, &topology_state->vcpis);
> +     }
> +     vcpi->vcpi = req_slots;
> +
> +     ret = req_slots;
> +out:
>       drm_dp_put_port(port);
> -     return req_slots;
> +     return ret;
>  }
>  EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots);
>  
> @@ -2658,32 +2691,46 @@ EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots);
>   * drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots
>   * @state: global atomic state
>   * @mgr: MST topology manager for the port
> - * @slots: number of vcpi slots to release
> + *
> + * Releases any VCPI slots that have been allocated to a port in the atomic
> + * state. Any atomic drivers which support MST must call this function in
> + * their connector's atomic_check() handler when the connector will no longer
> + * have VCPI allocated (e.g. because it's CRTC was removed).
> + *
> + * It is OK to call this even if @port has been removed from the system, in
> + * which case it will just amount to a no-op.
> + *
> + * See also: drm_dp_atomic_find_vcpi_slots()
>   *
>   * RETURNS:
> - * 0 if @slots were added back to &drm_dp_mst_topology_state->avail_slots or
> - * negative error code
> + * 0 if all slots for this port were added back to
> + * &drm_dp_mst_topology_state->avail_slots or negative error code
>   */
>  int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state,
>                                    struct drm_dp_mst_topology_mgr *mgr,
> -                                  int slots)
> +                                  struct drm_dp_mst_port *port)
>  {
>       struct drm_dp_mst_topology_state *topology_state;
> +     struct drm_dp_vcpi_allocation *tmp, *pos;
>  
>       topology_state = drm_atomic_get_mst_topology_state(state, mgr);
>       if (IS_ERR(topology_state))
>               return PTR_ERR(topology_state);
>  
> -     /* We cannot rely on port->vcpi.num_slots to update
> -      * topology_state->avail_slots as the port may not exist if the parent
> -      * branch device was unplugged. This should be fixed by tracking
> -      * per-port slot allocation in drm_dp_mst_topology_state instead of
> -      * depending on the caller to tell us how many slots to release.
> -      */
> -     topology_state->avail_slots += slots;
> -     DRM_DEBUG_KMS("vcpi slots released=%d, avail=%d\n",
> -                     slots, topology_state->avail_slots);
> +     list_for_each_entry_safe(pos, tmp, &topology_state->vcpis, next) {
> +             if (pos->port == port) {
> +                     list_del(&pos->next);
> +                     DRM_DEBUG_KMS("[MST PORT:%p] vcpi %d -> 0\n",
> +                                   port, pos->vcpi);
>  
> +                     kfree(pos);
> +                     return 0;
> +             }
> +     }
> +
> +     /* If no allocation was found, all that means is that the port was
> +      * destroyed since the last atomic commit. That's OK!
> +      */
>       return 0;
>  }
>  EXPORT_SYMBOL(drm_dp_atomic_release_vcpi_slots);
> @@ -3112,15 +3159,50 @@ static void drm_dp_destroy_connector_work(struct 
> work_struct *work)
>  static struct drm_private_state *
>  drm_dp_mst_duplicate_state(struct drm_private_obj *obj)
>  {
> -     struct drm_dp_mst_topology_state *state;
> +     struct drm_dp_mst_topology_state *state, *old_state =
> +             to_dp_mst_topology_state(obj->state);
> +     struct drm_dp_mst_topology_mgr *mgr = old_state->mgr;
> +     struct drm_dp_mst_port *port;
> +     struct drm_dp_vcpi_allocation *pos, *vcpi;
>  
> -     state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL);
> +     state = kzalloc(sizeof(*state), GFP_KERNEL);
>       if (!state)
>               return NULL;
>  
>       __drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
>  
> +     state->mgr = mgr;
> +     INIT_LIST_HEAD(&state->vcpis);
> +
> +     /* Copy over the VCPI allocations for ports that still exist */
> +     list_for_each_entry(pos, &old_state->vcpis, next) {
> +             port = drm_dp_get_validated_port_ref(mgr, pos->port);
> +             if (!port) {
> +                     DRM_DEBUG_ATOMIC("[MST PORT:%p] is gone, vcpi %d -> 
> 0\n",
> +                                      pos->port, pos->vcpi);
> +                     continue;
> +             }
> +
> +             vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL);
> +             if (!vcpi) {
> +                     drm_dp_put_port(port);
> +                     goto fail_alloc;
> +             }
> +
> +             vcpi->port = port;
> +             vcpi->vcpi = pos->vcpi;
> +             list_add(&vcpi->next, &state->vcpis);
> +             drm_dp_put_port(port);
> +     }
> +
>       return &state->base;
> +
> +fail_alloc:
> +     list_for_each_entry_safe(pos, vcpi, &state->vcpis, next)
> +             kfree(pos);
> +     kfree(state);
> +
> +     return NULL;
>  }
>  
>  static void drm_dp_mst_destroy_state(struct drm_private_obj *obj,
> @@ -3128,13 +3210,45 @@ static void drm_dp_mst_destroy_state(struct 
> drm_private_obj *obj,
>  {
>       struct drm_dp_mst_topology_state *mst_state =
>               to_dp_mst_topology_state(state);
> +     struct drm_dp_vcpi_allocation *pos, *tmp;
> +
> +     list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next)
> +             kfree(pos);
>  
>       kfree(mst_state);
>  }
>  
> +static int drm_dp_mst_atomic_check(struct drm_private_obj *obj,
> +                                struct drm_private_state *state)
> +{
> +     struct drm_dp_mst_topology_state *mst_state =
> +             to_dp_mst_topology_state(state);
> +     struct drm_dp_mst_topology_mgr *mgr = mst_state->mgr;
> +     struct drm_dp_vcpi_allocation *pos;
> +     int avail_slots = 63;
> +
> +     list_for_each_entry(pos, &mst_state->vcpis, next) {
> +             DRM_DEBUG_ATOMIC("[MST PORT:%p] requires %d vcpi slots\n",
> +                              pos->port, pos->vcpi);
> +
> +             avail_slots -= pos->vcpi;
> +             if (avail_slots < 0) {
> +                     DRM_DEBUG_ATOMIC("[MST PORT:%p] not enough vcpi slots 
> in state %p (avail=%d)\n",
> +                                      pos->port, state,
> +                                      avail_slots + pos->vcpi);
> +                     return -ENOSPC;
> +             }
> +     }
> +     DRM_DEBUG_ATOMIC("[MST MGR:%p] state %p vcpi avail=%d used=%d\n",
> +                      mgr, state, avail_slots, 63 - avail_slots);
> +
> +     return 0;
> +}
> +
>  static const struct drm_private_state_funcs mst_state_funcs = {
>       .atomic_duplicate_state = drm_dp_mst_duplicate_state,
>       .atomic_destroy_state = drm_dp_mst_destroy_state,
> +     .atomic_check = drm_dp_mst_atomic_check,
>  };
>  
>  /**
> @@ -3213,9 +3327,7 @@ int drm_dp_mst_topology_mgr_init(struct 
> drm_dp_mst_topology_mgr *mgr,
>               return -ENOMEM;
>  
>       mst_state->mgr = mgr;
> -
> -     /* max. time slots - one slot for MTP header */
> -     mst_state->avail_slots = 63;
> +     INIT_LIST_HEAD(&mst_state->vcpis);
>  
>       drm_atomic_private_obj_init(&mgr->base,
>                                   &mst_state->base,
> diff --git a/drivers/gpu/drm/i915/intel_dp_mst.c 
> b/drivers/gpu/drm/i915/intel_dp_mst.c
> index 8b71d64ebd9d..aaf904738b78 100644
> --- a/drivers/gpu/drm/i915/intel_dp_mst.c
> +++ b/drivers/gpu/drm/i915/intel_dp_mst.c
> @@ -114,28 +114,31 @@ static int intel_dp_mst_atomic_check(struct 
> drm_connector *connector,
>       struct drm_connector_state *old_conn_state;
>       struct drm_crtc *old_crtc;
>       struct drm_crtc_state *crtc_state;
> -     int slots, ret = 0;
> +     struct intel_connector *intel_connector =
> +             to_intel_connector(connector);
> +     struct drm_dp_mst_topology_mgr *mgr =
> +             &intel_connector->mst_port->mst_mgr;
> +     struct drm_dp_mst_port *port = intel_connector->port;
> +     int ret = 0;
>  
>       old_conn_state = drm_atomic_get_old_connector_state(state, connector);
>       old_crtc = old_conn_state->crtc;
>       if (!old_crtc)
>               return ret;
>  
> -     crtc_state = drm_atomic_get_new_crtc_state(state, old_crtc);
> -     slots = to_intel_crtc_state(crtc_state)->dp_m_n.tu;
> -     if (drm_atomic_crtc_needs_modeset(crtc_state) && slots > 0) {
> -             struct drm_dp_mst_topology_mgr *mgr;
> -             struct drm_encoder *old_encoder;
> +     /* Free VCPI, since compute_config() won't be run */
> +     if (!new_conn_state->crtc) {
> +             crtc_state = drm_atomic_get_new_crtc_state(state, old_crtc);
>  
> -             old_encoder = old_conn_state->best_encoder;
> -             mgr = &enc_to_mst(old_encoder)->primary->dp.mst_mgr;
> -
> -             ret = drm_dp_atomic_release_vcpi_slots(state, mgr, slots);
> -             if (ret)
> -                     DRM_DEBUG_KMS("failed releasing %d vcpi slots:%d\n", 
> slots, ret);
> -             else
> -                     to_intel_crtc_state(crtc_state)->dp_m_n.tu = 0;
> +             ret = drm_dp_atomic_release_vcpi_slots(state, mgr, port);
> +             if (ret) {
> +                     DRM_DEBUG_KMS("failed releasing vcpi slots: %d\n",
> +                                   ret);
> +                     return ret;
> +             }
> +             to_intel_crtc_state(crtc_state)->dp_m_n.tu = 0;
>       }
> +
>       return ret;
>  }
>  
> diff --git a/include/drm/drm_dp_mst_helper.h b/include/drm/drm_dp_mst_helper.h
> index 59f005b419cf..4c0805e56335 100644
> --- a/include/drm/drm_dp_mst_helper.h
> +++ b/include/drm/drm_dp_mst_helper.h
> @@ -406,9 +406,15 @@ struct drm_dp_payload {
>  
>  #define to_dp_mst_topology_state(x) container_of(x, struct 
> drm_dp_mst_topology_state, base)
>  
> +struct drm_dp_vcpi_allocation {
> +     struct drm_dp_mst_port *port;
> +     int vcpi;
> +     struct list_head next;
> +};
> +
>  struct drm_dp_mst_topology_state {
>       struct drm_private_state base;
> -     int avail_slots;
> +     struct list_head vcpis;
>       struct drm_dp_mst_topology_mgr *mgr;
>  };
>  
> @@ -624,7 +630,7 @@ int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state 
> *state,
>                                 struct drm_dp_mst_port *port, int pbn);
>  int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state,
>                                    struct drm_dp_mst_topology_mgr *mgr,
> -                                  int slots);
> +                                  struct drm_dp_mst_port *port);
>  int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr,
>                                struct drm_dp_mst_port *port, bool power_up);
>  
> -- 
> 2.17.2
> 

-- 
Daniel Vetter
Software Engineer, Intel Corporation
http://blog.ffwll.ch
_______________________________________________
dri-devel mailing list
dri-devel@lists.freedesktop.org
https://lists.freedesktop.org/mailman/listinfo/dri-devel

Reply via email to