Commit eceae147 authored by Lyude Paul's avatar Lyude Paul

drm/dp_mst: Start tracking per-port VCPI allocations

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 adds a new drm_dp_mst_atomic_check() helper which
must be used by atomic drivers to perform validity checks for the new
VCPI allocations incurred by a state.

Also: update the documentation and make it more obvious that these
/must/ be called by /all/ atomic drivers supporting MST.

Changes since v9:
* Add some missing changes that were requested by danvet that I forgot
  about after I redid all of the kref stuff:
  * Remove unnecessary state changes in intel_dp_mst_atomic_check
  * Cleanup atomic check logic for VCPI allocations - all we need to check in
    compute_config is whether or not this state disables a CRTC, then free
    VCPI based off that

Changes since v8:
 * Fix compile errors, whoops!

Changes since v7:
 - Don't check for mixed stale/valid VCPI allocations, just rely on
 connector registration to stop such erroneous modesets

Changes since v6:
 - Keep a kref to all of the ports we have allocations on. This required
   a good bit of changing to when we call drm_dp_find_vcpi_slots(),
   mainly that we need to ensure that we only redo VCPI allocations on
   actual mode or CRTC changes, not crtc_state->active changes.
   Additionally, we no longer take the registration of the DRM connector
   for each port into account because so long as we have a kref to the
   port in the new or previous atomic state, the connector will stay
   registered.
 - Use the small changes to drm_dp_put_port() to add even more error
   checking to make misusage of the helpers more obvious. I added this
   after having to chase down various use-after-free conditions that
   started popping up from the new helpers so no one else has to
   troubleshoot that.
 - Move some accidental DRM_DEBUG_KMS() calls to DRM_DEBUG_ATOMIC()
 - Update documentation again, note that find/release() should both not be
   called on the same port in a single atomic check phase (but multiple
   calls to one or the other is OK)

Changes since v4:
 - Don't skip the atomic checks for VCPI allocations if no new VCPI
   allocations happen in a state. This makes the next change I'm about
   to list here a lot easier to implement.
 - Don't ignore VCPI allocations on destroyed ports, instead ensure that
   when ports are destroyed and still have VCPI allocations in the
   topology state, the only state changes allowed are releasing said
   ports' VCPI. This prevents a state with a mix of VCPI allocations
   from destroyed ports, and allocations from valid ports.

Changes since v3:
 - Don't release VCPI allocations in the topology state immediately in
   drm_dp_atomic_release_vcpi_slots(), instead mark them as 0 and skip
   over them in drm_dp_mst_duplicate_state(). This makes it so
   drm_dp_atomic_release_vcpi_slots() is still idempotent while also
   throwing warnings if the driver messes up it's book keeping and tries
   to release VCPI slots on a port that doesn't have any pre-existing
   VCPI allocation - danvet
 - Change mst_state/state in some debugging messages to "mst state"

Changes since v2:
 - Use kmemdup() for duplicating MST state - danvet
 - Move port validation out of duplicate state callback - danvet
 - Handle looping through MST topology states in
   drm_dp_mst_atomic_check() so the driver doesn't have to do it
 - Fix documentation in drm_dp_atomic_find_vcpi_slots()
 - Move the atomic check for each individual topology state into it's
   own function, reduces indenting
 - Don't consider "stale" MST ports when calculating the bandwidth
   requirements. This is needed because originally we relied on the
   state duplication functions to prune any stale ports from the new
   state, which would prevent us from incorrectly considering their
   bandwidth requirements alongside legitimate new payloads.
 - Add function references in drm_dp_atomic_release_vcpi_slots() - danvet
 - Annotate atomic VCPI and atomic check functions with __must_check
   - danvet

Changes since v1:
 - Don't use the now-removed ->atomic_check() for private objects hook,
   just give drivers a function to call themselves
Signed-off-by: default avatarLyude Paul <lyude@redhat.com>
Reviewed-by: default avatarDaniel 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>
Link: https://patchwork.freedesktop.org/patch/msgid/20190111005343.17443-19-lyude@redhat.com
parent bea5c38f
This diff is collapsed.
...@@ -12623,6 +12623,10 @@ static int intel_atomic_check(struct drm_device *dev, ...@@ -12623,6 +12623,10 @@ static int intel_atomic_check(struct drm_device *dev,
"[modeset]" : "[fastset]"); "[modeset]" : "[fastset]");
} }
ret = drm_dp_mst_atomic_check(state);
if (ret)
return ret;
if (any_ms) { if (any_ms) {
ret = intel_modeset_checks(state); ret = intel_modeset_checks(state);
......
...@@ -41,8 +41,12 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder, ...@@ -41,8 +41,12 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder,
struct drm_connector *connector = conn_state->connector; struct drm_connector *connector = conn_state->connector;
void *port = to_intel_connector(connector)->port; void *port = to_intel_connector(connector)->port;
struct drm_atomic_state *state = pipe_config->base.state; struct drm_atomic_state *state = pipe_config->base.state;
struct drm_crtc *crtc = pipe_config->base.crtc;
struct drm_crtc_state *old_crtc_state =
drm_atomic_get_old_crtc_state(state, crtc);
int bpp; int bpp;
int lane_count, slots = 0; int lane_count, slots =
to_intel_crtc_state(old_crtc_state)->dp_m_n.tu;
const struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode; const struct drm_display_mode *adjusted_mode = &pipe_config->base.adjusted_mode;
int mst_pbn; int mst_pbn;
bool constant_n = drm_dp_has_quirk(&intel_dp->desc, bool constant_n = drm_dp_has_quirk(&intel_dp->desc,
...@@ -107,35 +111,39 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder, ...@@ -107,35 +111,39 @@ static bool intel_dp_mst_compute_config(struct intel_encoder *encoder,
return true; return true;
} }
static int intel_dp_mst_atomic_check(struct drm_connector *connector, static int
intel_dp_mst_atomic_check(struct drm_connector *connector,
struct drm_connector_state *new_conn_state) struct drm_connector_state *new_conn_state)
{ {
struct drm_atomic_state *state = new_conn_state->state; struct drm_atomic_state *state = new_conn_state->state;
struct drm_connector_state *old_conn_state; struct drm_connector_state *old_conn_state =
struct drm_crtc *old_crtc; drm_atomic_get_old_connector_state(state, connector);
struct intel_connector *intel_connector =
to_intel_connector(connector);
struct drm_crtc *new_crtc = new_conn_state->crtc;
struct drm_crtc_state *crtc_state; struct drm_crtc_state *crtc_state;
int slots, 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_dp_mst_topology_mgr *mgr;
struct drm_encoder *old_encoder; int ret = 0;
old_encoder = old_conn_state->best_encoder; if (!old_conn_state->crtc)
mgr = &enc_to_mst(old_encoder)->primary->dp.mst_mgr; return 0;
ret = drm_dp_atomic_release_vcpi_slots(state, mgr, slots); /* We only want to free VCPI if this state disables the CRTC on this
if (ret) * connector
DRM_DEBUG_KMS("failed releasing %d vcpi slots:%d\n", slots, ret); */
else if (new_crtc) {
to_intel_crtc_state(crtc_state)->dp_m_n.tu = 0; crtc_state = drm_atomic_get_new_crtc_state(state, new_crtc);
if (!crtc_state ||
!drm_atomic_crtc_needs_modeset(crtc_state) ||
crtc_state->enable)
return 0;
} }
mgr = &enc_to_mst(old_conn_state->best_encoder)->primary->dp.mst_mgr;
ret = drm_dp_atomic_release_vcpi_slots(state, mgr,
intel_connector->port);
return ret; return ret;
} }
......
...@@ -425,9 +425,15 @@ struct drm_dp_payload { ...@@ -425,9 +425,15 @@ struct drm_dp_payload {
#define to_dp_mst_topology_state(x) container_of(x, struct drm_dp_mst_topology_state, base) #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_dp_mst_topology_state {
struct drm_private_state base; struct drm_private_state base;
int avail_slots; struct list_head vcpis;
struct drm_dp_mst_topology_mgr *mgr; struct drm_dp_mst_topology_mgr *mgr;
}; };
...@@ -638,14 +644,17 @@ void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr); ...@@ -638,14 +644,17 @@ void drm_dp_mst_topology_mgr_suspend(struct drm_dp_mst_topology_mgr *mgr);
int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr); int drm_dp_mst_topology_mgr_resume(struct drm_dp_mst_topology_mgr *mgr);
struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state, struct drm_dp_mst_topology_state *drm_atomic_get_mst_topology_state(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr); struct drm_dp_mst_topology_mgr *mgr);
int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state, int __must_check
drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr, struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port, int pbn); struct drm_dp_mst_port *port, int pbn);
int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state, int __must_check
drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state,
struct drm_dp_mst_topology_mgr *mgr, 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, int drm_dp_send_power_updown_phy(struct drm_dp_mst_topology_mgr *mgr,
struct drm_dp_mst_port *port, bool power_up); struct drm_dp_mst_port *port, bool power_up);
int __must_check drm_dp_mst_atomic_check(struct drm_atomic_state *state);
void drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port); void drm_dp_mst_get_port_malloc(struct drm_dp_mst_port *port);
void drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port); void drm_dp_mst_put_port_malloc(struct drm_dp_mst_port *port);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment