Skip to content

Commit eceae14

Browse files
committed
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: Lyude Paul <lyude@redhat.com> Reviewed-by: 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> Link: https://patchwork.freedesktop.org/patch/msgid/20190111005343.17443-19-lyude@redhat.com
1 parent bea5c38 commit eceae14

File tree

4 files changed

+266
-62
lines changed

4 files changed

+266
-62
lines changed

drivers/gpu/drm/drm_dp_mst_topology.c

Lines changed: 215 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3011,21 +3011,42 @@ static int drm_dp_init_vcpi(struct drm_dp_mst_topology_mgr *mgr,
30113011
}
30123012

30133013
/**
3014-
* drm_dp_atomic_find_vcpi_slots() - Find and add vcpi slots to the state
3014+
* drm_dp_atomic_find_vcpi_slots() - Find and add VCPI slots to the state
30153015
* @state: global atomic state
30163016
* @mgr: MST topology manager for the port
30173017
* @port: port to find vcpi slots for
30183018
* @pbn: bandwidth required for the mode in PBN
30193019
*
3020-
* RETURNS:
3021-
* Total slots in the atomic state assigned for this port or error
3020+
* Allocates VCPI slots to @port, replacing any previous VCPI allocations it
3021+
* may have had. Any atomic drivers which support MST must call this function
3022+
* in their &drm_encoder_helper_funcs.atomic_check() callback to change the
3023+
* current VCPI allocation for the new state, but only when
3024+
* &drm_crtc_state.mode_changed or &drm_crtc_state.connectors_changed is set
3025+
* to ensure compatibility with userspace applications that still use the
3026+
* legacy modesetting UAPI.
3027+
*
3028+
* Allocations set by this function are not checked against the bandwidth
3029+
* restraints of @mgr until the driver calls drm_dp_mst_atomic_check().
3030+
*
3031+
* Additionally, it is OK to call this function multiple times on the same
3032+
* @port as needed. It is not OK however, to call this function and
3033+
* drm_dp_atomic_release_vcpi_slots() in the same atomic check phase.
3034+
*
3035+
* See also:
3036+
* drm_dp_atomic_release_vcpi_slots()
3037+
* drm_dp_mst_atomic_check()
3038+
*
3039+
* Returns:
3040+
* Total slots in the atomic state assigned for this port, or a negative error
3041+
* code if the port no longer exists
30223042
*/
30233043
int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state,
30243044
struct drm_dp_mst_topology_mgr *mgr,
30253045
struct drm_dp_mst_port *port, int pbn)
30263046
{
30273047
struct drm_dp_mst_topology_state *topology_state;
3028-
int req_slots;
3048+
struct drm_dp_vcpi_allocation *pos, *vcpi = NULL;
3049+
int prev_slots, req_slots, ret;
30293050

30303051
topology_state = drm_atomic_get_mst_topology_state(state, mgr);
30313052
if (IS_ERR(topology_state))
@@ -3034,52 +3055,112 @@ int drm_dp_atomic_find_vcpi_slots(struct drm_atomic_state *state,
30343055
port = drm_dp_mst_topology_get_port_validated(mgr, port);
30353056
if (port == NULL)
30363057
return -EINVAL;
3037-
req_slots = DIV_ROUND_UP(pbn, mgr->pbn_div);
3038-
DRM_DEBUG_KMS("vcpi slots req=%d, avail=%d\n",
3039-
req_slots, topology_state->avail_slots);
30403058

3041-
if (req_slots > topology_state->avail_slots) {
3042-
drm_dp_mst_topology_put_port(port);
3043-
return -ENOSPC;
3059+
/* Find the current allocation for this port, if any */
3060+
list_for_each_entry(pos, &topology_state->vcpis, next) {
3061+
if (pos->port == port) {
3062+
vcpi = pos;
3063+
prev_slots = vcpi->vcpi;
3064+
3065+
/*
3066+
* This should never happen, unless the driver tries
3067+
* releasing and allocating the same VCPI allocation,
3068+
* which is an error
3069+
*/
3070+
if (WARN_ON(!prev_slots)) {
3071+
DRM_ERROR("cannot allocate and release VCPI on [MST PORT:%p] in the same state\n",
3072+
port);
3073+
return -EINVAL;
3074+
}
3075+
3076+
break;
3077+
}
30443078
}
3079+
if (!vcpi)
3080+
prev_slots = 0;
3081+
3082+
req_slots = DIV_ROUND_UP(pbn, mgr->pbn_div);
3083+
3084+
DRM_DEBUG_ATOMIC("[CONNECTOR:%d:%s] [MST PORT:%p] VCPI %d -> %d\n",
3085+
port->connector->base.id, port->connector->name,
3086+
port, prev_slots, req_slots);
3087+
3088+
/* Add the new allocation to the state */
3089+
if (!vcpi) {
3090+
vcpi = kzalloc(sizeof(*vcpi), GFP_KERNEL);
3091+
if (!vcpi) {
3092+
ret = -ENOMEM;
3093+
goto out;
3094+
}
30453095

3046-
topology_state->avail_slots -= req_slots;
3047-
DRM_DEBUG_KMS("vcpi slots avail=%d", topology_state->avail_slots);
3096+
drm_dp_mst_get_port_malloc(port);
3097+
vcpi->port = port;
3098+
list_add(&vcpi->next, &topology_state->vcpis);
3099+
}
3100+
vcpi->vcpi = req_slots;
30483101

3102+
ret = req_slots;
3103+
out:
30493104
drm_dp_mst_topology_put_port(port);
3050-
return req_slots;
3105+
return ret;
30513106
}
30523107
EXPORT_SYMBOL(drm_dp_atomic_find_vcpi_slots);
30533108

30543109
/**
30553110
* drm_dp_atomic_release_vcpi_slots() - Release allocated vcpi slots
30563111
* @state: global atomic state
30573112
* @mgr: MST topology manager for the port
3058-
* @slots: number of vcpi slots to release
3113+
* @port: The port to release the VCPI slots from
30593114
*
3060-
* RETURNS:
3061-
* 0 if @slots were added back to &drm_dp_mst_topology_state->avail_slots or
3062-
* negative error code
3115+
* Releases any VCPI slots that have been allocated to a port in the atomic
3116+
* state. Any atomic drivers which support MST must call this function in
3117+
* their &drm_connector_helper_funcs.atomic_check() callback when the
3118+
* connector will no longer have VCPI allocated (e.g. because it's CRTC was
3119+
* removed) when it had VCPI allocated in the previous atomic state.
3120+
*
3121+
* It is OK to call this even if @port has been removed from the system.
3122+
* Additionally, it is OK to call this function multiple times on the same
3123+
* @port as needed. It is not OK however, to call this function and
3124+
* drm_dp_atomic_find_vcpi_slots() on the same @port in a single atomic check
3125+
* phase.
3126+
*
3127+
* See also:
3128+
* drm_dp_atomic_find_vcpi_slots()
3129+
* drm_dp_mst_atomic_check()
3130+
*
3131+
* Returns:
3132+
* 0 if all slots for this port were added back to
3133+
* &drm_dp_mst_topology_state.avail_slots or negative error code
30633134
*/
30643135
int drm_dp_atomic_release_vcpi_slots(struct drm_atomic_state *state,
30653136
struct drm_dp_mst_topology_mgr *mgr,
3066-
int slots)
3137+
struct drm_dp_mst_port *port)
30673138
{
30683139
struct drm_dp_mst_topology_state *topology_state;
3140+
struct drm_dp_vcpi_allocation *pos;
3141+
bool found = false;
30693142

30703143
topology_state = drm_atomic_get_mst_topology_state(state, mgr);
30713144
if (IS_ERR(topology_state))
30723145
return PTR_ERR(topology_state);
30733146

3074-
/* We cannot rely on port->vcpi.num_slots to update
3075-
* topology_state->avail_slots as the port may not exist if the parent
3076-
* branch device was unplugged. This should be fixed by tracking
3077-
* per-port slot allocation in drm_dp_mst_topology_state instead of
3078-
* depending on the caller to tell us how many slots to release.
3079-
*/
3080-
topology_state->avail_slots += slots;
3081-
DRM_DEBUG_KMS("vcpi slots released=%d, avail=%d\n",
3082-
slots, topology_state->avail_slots);
3147+
list_for_each_entry(pos, &topology_state->vcpis, next) {
3148+
if (pos->port == port) {
3149+
found = true;
3150+
break;
3151+
}
3152+
}
3153+
if (WARN_ON(!found)) {
3154+
DRM_ERROR("no VCPI for [MST PORT:%p] found in mst state %p\n",
3155+
port, &topology_state->base);
3156+
return -EINVAL;
3157+
}
3158+
3159+
DRM_DEBUG_ATOMIC("[MST PORT:%p] VCPI %d -> 0\n", port, pos->vcpi);
3160+
if (pos->vcpi) {
3161+
drm_dp_mst_put_port_malloc(port);
3162+
pos->vcpi = 0;
3163+
}
30833164

30843165
return 0;
30853166
}
@@ -3501,26 +3582,130 @@ static void drm_dp_destroy_connector_work(struct work_struct *work)
35013582
static struct drm_private_state *
35023583
drm_dp_mst_duplicate_state(struct drm_private_obj *obj)
35033584
{
3504-
struct drm_dp_mst_topology_state *state;
3585+
struct drm_dp_mst_topology_state *state, *old_state =
3586+
to_dp_mst_topology_state(obj->state);
3587+
struct drm_dp_vcpi_allocation *pos, *vcpi;
35053588

3506-
state = kmemdup(obj->state, sizeof(*state), GFP_KERNEL);
3589+
state = kmemdup(old_state, sizeof(*state), GFP_KERNEL);
35073590
if (!state)
35083591
return NULL;
35093592

35103593
__drm_atomic_helper_private_obj_duplicate_state(obj, &state->base);
35113594

3595+
INIT_LIST_HEAD(&state->vcpis);
3596+
3597+
list_for_each_entry(pos, &old_state->vcpis, next) {
3598+
/* Prune leftover freed VCPI allocations */
3599+
if (!pos->vcpi)
3600+
continue;
3601+
3602+
vcpi = kmemdup(pos, sizeof(*vcpi), GFP_KERNEL);
3603+
if (!vcpi)
3604+
goto fail;
3605+
3606+
drm_dp_mst_get_port_malloc(vcpi->port);
3607+
list_add(&vcpi->next, &state->vcpis);
3608+
}
3609+
35123610
return &state->base;
3611+
3612+
fail:
3613+
list_for_each_entry_safe(pos, vcpi, &state->vcpis, next) {
3614+
drm_dp_mst_put_port_malloc(pos->port);
3615+
kfree(pos);
3616+
}
3617+
kfree(state);
3618+
3619+
return NULL;
35133620
}
35143621

35153622
static void drm_dp_mst_destroy_state(struct drm_private_obj *obj,
35163623
struct drm_private_state *state)
35173624
{
35183625
struct drm_dp_mst_topology_state *mst_state =
35193626
to_dp_mst_topology_state(state);
3627+
struct drm_dp_vcpi_allocation *pos, *tmp;
3628+
3629+
list_for_each_entry_safe(pos, tmp, &mst_state->vcpis, next) {
3630+
/* We only keep references to ports with non-zero VCPIs */
3631+
if (pos->vcpi)
3632+
drm_dp_mst_put_port_malloc(pos->port);
3633+
kfree(pos);
3634+
}
35203635

35213636
kfree(mst_state);
35223637
}
35233638

3639+
static inline int
3640+
drm_dp_mst_atomic_check_topology_state(struct drm_dp_mst_topology_mgr *mgr,
3641+
struct drm_dp_mst_topology_state *mst_state)
3642+
{
3643+
struct drm_dp_vcpi_allocation *vcpi;
3644+
int avail_slots = 63;
3645+
3646+
list_for_each_entry(vcpi, &mst_state->vcpis, next) {
3647+
/* Releasing VCPI is always OK-even if the port is gone */
3648+
if (!vcpi->vcpi) {
3649+
DRM_DEBUG_ATOMIC("[MST PORT:%p] releases all VCPI slots\n",
3650+
vcpi->port);
3651+
continue;
3652+
}
3653+
3654+
DRM_DEBUG_ATOMIC("[MST PORT:%p] requires %d vcpi slots\n",
3655+
vcpi->port, vcpi->vcpi);
3656+
3657+
avail_slots -= vcpi->vcpi;
3658+
if (avail_slots < 0) {
3659+
DRM_DEBUG_ATOMIC("[MST PORT:%p] not enough VCPI slots in mst state %p (avail=%d)\n",
3660+
vcpi->port, mst_state,
3661+
avail_slots + vcpi->vcpi);
3662+
return -ENOSPC;
3663+
}
3664+
}
3665+
DRM_DEBUG_ATOMIC("[MST MGR:%p] mst state %p VCPI avail=%d used=%d\n",
3666+
mgr, mst_state, avail_slots,
3667+
63 - avail_slots);
3668+
3669+
return 0;
3670+
}
3671+
3672+
/**
3673+
* drm_dp_mst_atomic_check - Check that the new state of an MST topology in an
3674+
* atomic update is valid
3675+
* @state: Pointer to the new &struct drm_dp_mst_topology_state
3676+
*
3677+
* Checks the given topology state for an atomic update to ensure that it's
3678+
* valid. This includes checking whether there's enough bandwidth to support
3679+
* the new VCPI allocations in the atomic update.
3680+
*
3681+
* Any atomic drivers supporting DP MST must make sure to call this after
3682+
* checking the rest of their state in their
3683+
* &drm_mode_config_funcs.atomic_check() callback.
3684+
*
3685+
* See also:
3686+
* drm_dp_atomic_find_vcpi_slots()
3687+
* drm_dp_atomic_release_vcpi_slots()
3688+
*
3689+
* Returns:
3690+
*
3691+
* 0 if the new state is valid, negative error code otherwise.
3692+
*/
3693+
int drm_dp_mst_atomic_check(struct drm_atomic_state *state)
3694+
{
3695+
struct drm_dp_mst_topology_mgr *mgr;
3696+
struct drm_dp_mst_topology_state *mst_state;
3697+
int i, ret = 0;
3698+
3699+
for_each_new_mst_mgr_in_state(state, mgr, mst_state, i) {
3700+
ret = drm_dp_mst_atomic_check_topology_state(mgr, mst_state);
3701+
if (ret)
3702+
break;
3703+
}
3704+
3705+
return ret;
3706+
}
3707+
EXPORT_SYMBOL(drm_dp_mst_atomic_check);
3708+
35243709
const struct drm_private_state_funcs drm_dp_mst_topology_state_funcs = {
35253710
.atomic_duplicate_state = drm_dp_mst_duplicate_state,
35263711
.atomic_destroy_state = drm_dp_mst_destroy_state,
@@ -3603,9 +3788,7 @@ int drm_dp_mst_topology_mgr_init(struct drm_dp_mst_topology_mgr *mgr,
36033788
return -ENOMEM;
36043789

36053790
mst_state->mgr = mgr;
3606-
3607-
/* max. time slots - one slot for MTP header */
3608-
mst_state->avail_slots = 63;
3791+
INIT_LIST_HEAD(&mst_state->vcpis);
36093792

36103793
drm_atomic_private_obj_init(dev, &mgr->base,
36113794
&mst_state->base,

drivers/gpu/drm/i915/intel_display.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12623,6 +12623,10 @@ static int intel_atomic_check(struct drm_device *dev,
1262312623
"[modeset]" : "[fastset]");
1262412624
}
1262512625

12626+
ret = drm_dp_mst_atomic_check(state);
12627+
if (ret)
12628+
return ret;
12629+
1262612630
if (any_ms) {
1262712631
ret = intel_modeset_checks(state);
1262812632

0 commit comments

Comments
 (0)