diff --git a/msm/msm_drv.c b/msm/msm_drv.c index 73ccaac548..a48f012156 100644 --- a/msm/msm_drv.c +++ b/msm/msm_drv.c @@ -45,6 +45,7 @@ #include #include #include +#include #include #include "msm_drv.h" @@ -967,6 +968,15 @@ static void context_close(struct msm_file_private *ctx) kfree(ctx); } +static void msm_preclose(struct drm_device *dev, struct drm_file *file) +{ + struct msm_drm_private *priv = dev->dev_private; + struct msm_kms *kms = priv->kms; + + if (kms && kms->funcs && kms->funcs->preclose) + kms->funcs->preclose(kms, file); +} + static void msm_postclose(struct drm_device *dev, struct drm_file *file) { struct msm_drm_private *priv = dev->dev_private; @@ -1471,6 +1481,14 @@ static int msm_release(struct inode *inode, struct file *filp) kfree(node); } + /** + * Handle preclose operation here for removing fb's whose + * refcount > 1. This operation is not triggered from upstream + * drm as msm_driver does not support DRIVER_LEGACY feature. + */ + if (drm_is_current_master(file_priv)) + msm_preclose(dev, file_priv); + return drm_release(inode, filp); } diff --git a/msm/msm_kms.h b/msm/msm_kms.h index 062a911107..f81824f222 100644 --- a/msm/msm_kms.h +++ b/msm/msm_kms.h @@ -97,6 +97,7 @@ struct msm_kms_funcs { struct drm_encoder *slave_encoder, bool is_cmd_mode); void (*postopen)(struct msm_kms *kms, struct drm_file *file); + void (*preclose)(struct msm_kms *kms, struct drm_file *file); void (*postclose)(struct msm_kms *kms, struct drm_file *file); void (*lastclose)(struct msm_kms *kms); int (*register_events)(struct msm_kms *kms, diff --git a/msm/sde/sde_crtc.c b/msm/sde/sde_crtc.c index adf9c6b60a..3b145cce59 100644 --- a/msm/sde/sde_crtc.c +++ b/msm/sde/sde_crtc.c @@ -2837,17 +2837,16 @@ static void _sde_crtc_set_input_fence_timeout(struct sde_crtc_state *cstate) cstate->input_fence_timeout_ns *= NSEC_PER_MSEC; } -/** - * _sde_crtc_clear_dim_layers_v1 - clear all dim layer settings - * @cstate: Pointer to sde crtc state - */ -static void _sde_crtc_clear_dim_layers_v1(struct sde_crtc_state *cstate) +void _sde_crtc_clear_dim_layers_v1(struct drm_crtc_state *state) { u32 i; + struct sde_crtc_state *cstate; - if (!cstate) + if (!state) return; + cstate = to_sde_crtc_state(state); + for (i = 0; i < cstate->num_dim_layers; i++) memset(&cstate->dim_layer[i], 0, sizeof(cstate->dim_layer[i])); @@ -2876,7 +2875,7 @@ static void _sde_crtc_set_dim_layer_v1(struct drm_crtc *crtc, if (!usr_ptr) { /* usr_ptr is null when setting the default property value */ - _sde_crtc_clear_dim_layers_v1(cstate); + _sde_crtc_clear_dim_layers_v1(&cstate->base); SDE_DEBUG("dim_layer data removed\n"); goto clear; } diff --git a/msm/sde/sde_crtc.h b/msm/sde/sde_crtc.h index ef071a9e38..17b738f39c 100644 --- a/msm/sde/sde_crtc.h +++ b/msm/sde/sde_crtc.h @@ -1043,4 +1043,10 @@ void sde_crtc_reset_sw_state(struct drm_crtc *crtc); */ void sde_crtc_disable_cp_features(struct drm_crtc *crtc); +/* + * _sde_crtc_clear_dim_layers_v1 - clear all dim layer settings + * @cstate: Pointer to drm crtc state + */ +void _sde_crtc_clear_dim_layers_v1(struct drm_crtc_state *state); + #endif /* _SDE_CRTC_H_ */ diff --git a/msm/sde/sde_kms.c b/msm/sde/sde_kms.c index 435e52f126..911a42ea02 100644 --- a/msm/sde/sde_kms.c +++ b/msm/sde/sde_kms.c @@ -2397,6 +2397,223 @@ static void sde_kms_destroy(struct msm_kms *kms) kfree(sde_kms); } +static void sde_kms_helper_clear_dim_layers(struct drm_atomic_state *state, struct drm_crtc *crtc) +{ + struct drm_crtc_state *crtc_state = NULL; + struct sde_crtc_state *c_state; + + if (!state || !crtc) { + SDE_ERROR("invalid params\n"); + return; + } + + crtc_state = drm_atomic_get_new_crtc_state(state, crtc); + c_state = to_sde_crtc_state(crtc_state); + + _sde_crtc_clear_dim_layers_v1(crtc_state); + set_bit(SDE_CRTC_DIRTY_DIM_LAYERS, c_state->dirty); +} + +static int sde_kms_set_crtc_for_conn(struct drm_device *dev, + struct drm_encoder *enc, struct drm_atomic_state *state) +{ + struct drm_connector *conn = NULL; + struct drm_connector *tmp_conn = NULL; + struct drm_connector_list_iter conn_iter; + struct drm_crtc_state *crtc_state = NULL; + struct drm_connector_state *conn_state = NULL; + int ret = 0; + + drm_connector_list_iter_begin(dev, &conn_iter); + drm_for_each_connector_iter(tmp_conn, &conn_iter) { + if (enc == tmp_conn->state->best_encoder) { + conn = tmp_conn; + break; + } + } + drm_connector_list_iter_end(&conn_iter); + + if (!conn || !enc->crtc) { + SDE_ERROR("invalid params for enc:%d\n", DRMID(enc)); + return -EINVAL; + } + + crtc_state = drm_atomic_get_crtc_state(state, enc->crtc); + if (IS_ERR(crtc_state)) { + ret = PTR_ERR(crtc_state); + SDE_ERROR("error %d getting crtc %d state\n", + ret, DRMID(enc->crtc)); + return ret; + } + + conn_state = drm_atomic_get_connector_state(state, conn); + if (IS_ERR(conn_state)) { + ret = PTR_ERR(conn_state); + SDE_ERROR("error %d getting connector %d state\n", + ret, DRMID(conn)); + return ret; + } + + crtc_state->active = true; + ret = drm_atomic_set_crtc_for_connector(conn_state, enc->crtc); + if (ret) + SDE_ERROR("error %d setting the crtc\n", ret); + + return ret; +} + +static void _sde_kms_plane_force_remove(struct drm_plane *plane, + struct drm_atomic_state *state) +{ + struct drm_plane_state *plane_state; + int ret = 0; + + plane_state = drm_atomic_get_plane_state(state, plane); + if (IS_ERR(plane_state)) { + ret = PTR_ERR(plane_state); + SDE_ERROR("error %d getting plane %d state\n", + ret, plane->base.id); + return; + } + + plane->old_fb = plane->fb; + + SDE_DEBUG("disabling plane %d\n", plane->base.id); + + ret = drm_atomic_set_crtc_for_plane(plane_state, NULL); + if (ret != 0) + SDE_ERROR("error %d disabling plane %d\n", ret, + plane->base.id); + + drm_atomic_set_fb_for_plane(plane_state, NULL); +} + +static int _sde_kms_remove_fbs(struct sde_kms *sde_kms, struct drm_file *file, + struct drm_atomic_state *state) +{ + struct drm_device *dev = sde_kms->dev; + struct drm_framebuffer *fb, *tfb; + struct list_head fbs; + struct drm_plane *plane; + struct drm_crtc *crtc = NULL; + unsigned int crtc_mask = 0; + int ret = 0; + + INIT_LIST_HEAD(&fbs); + + list_for_each_entry_safe(fb, tfb, &file->fbs, filp_head) { + if (drm_framebuffer_read_refcount(fb) > 1) { + list_move_tail(&fb->filp_head, &fbs); + + drm_for_each_plane(plane, dev) { + if (plane->state && plane->state->fb == fb) { + if (plane->state->crtc) + crtc_mask |= drm_crtc_mask(plane->state->crtc); + _sde_kms_plane_force_remove(plane, state); + } + } + } else { + list_del_init(&fb->filp_head); + drm_framebuffer_put(fb); + } + } + + if (list_empty(&fbs)) { + SDE_DEBUG("skip commit as no fb(s)\n"); + return 0; + } + + drm_for_each_crtc(crtc, dev) { + if ((crtc_mask & drm_crtc_mask(crtc)) && crtc->state->active) { + struct drm_encoder *drm_enc; + + drm_for_each_encoder_mask(drm_enc, crtc->dev, + crtc->state->encoder_mask) { + ret = sde_kms_set_crtc_for_conn(dev, drm_enc, state); + if (ret) + goto error; + } + sde_kms_helper_clear_dim_layers(state, crtc); + } + } + + SDE_EVT32(state, crtc_mask); + SDE_DEBUG("null commit after removing all the pipes\n"); + ret = drm_atomic_commit(state); + +error: + if (ret) { + /* + * move the fbs back to original list, so it would be + * handled during drm_release + */ + list_for_each_entry_safe(fb, tfb, &fbs, filp_head) + list_move_tail(&fb->filp_head, &file->fbs); + + SDE_ERROR("atomic commit failed in preclose, ret:%d\n", ret); + goto end; + } + + while (!list_empty(&fbs)) { + fb = list_first_entry(&fbs, typeof(*fb), filp_head); + + list_del_init(&fb->filp_head); + drm_framebuffer_put(fb); + } + +end: + return ret; +} + +static void sde_kms_preclose(struct msm_kms *kms, struct drm_file *file) +{ + struct sde_kms *sde_kms = to_sde_kms(kms); + struct drm_device *dev = sde_kms->dev; + struct msm_drm_private *priv = dev->dev_private; + unsigned int i; + struct drm_atomic_state *state = NULL; + struct drm_modeset_acquire_ctx ctx; + int ret = 0; + + /* cancel pending flip event */ + for (i = 0; i < priv->num_crtcs; i++) + sde_crtc_complete_flip(priv->crtcs[i], file); + + drm_modeset_acquire_init(&ctx, 0); +retry: + ret = drm_modeset_lock_all_ctx(dev, &ctx); + if (ret == -EDEADLK) { + drm_modeset_backoff(&ctx); + goto retry; + } else if (WARN_ON(ret)) { + goto end; + } + + state = drm_atomic_state_alloc(dev); + if (!state) { + ret = -ENOMEM; + goto end; + } + + state->acquire_ctx = &ctx; + + for (i = 0; i < TEARDOWN_DEADLOCK_RETRY_MAX; i++) { + ret = _sde_kms_remove_fbs(sde_kms, file, state); + if (ret != -EDEADLK) + break; + drm_atomic_state_clear(state); + drm_modeset_backoff(&ctx); + } + +end: + if (state) + drm_atomic_state_put(state); + + SDE_DEBUG("sde preclose done, ret:%d\n", ret); + drm_modeset_drop_locks(&ctx); + drm_modeset_acquire_fini(&ctx); +} + static int _sde_kms_helper_reset_custom_properties(struct sde_kms *sde_kms, struct drm_atomic_state *state) { @@ -3448,12 +3665,7 @@ static void _sde_kms_null_commit(struct drm_device *dev, struct drm_encoder *enc) { struct drm_modeset_acquire_ctx ctx; - struct drm_connector *conn = NULL; - struct drm_connector *tmp_conn = NULL; - struct drm_connector_list_iter conn_iter; struct drm_atomic_state *state = NULL; - struct drm_crtc_state *crtc_state = NULL; - struct drm_connector_state *conn_state = NULL; int retry_cnt = 0; int ret = 0; @@ -3477,32 +3689,10 @@ retry: } state->acquire_ctx = &ctx; - drm_connector_list_iter_begin(dev, &conn_iter); - drm_for_each_connector_iter(tmp_conn, &conn_iter) { - if (enc == tmp_conn->state->best_encoder) { - conn = tmp_conn; - break; - } - } - drm_connector_list_iter_end(&conn_iter); - if (!conn) { - SDE_ERROR("error in finding conn for enc:%d\n", DRMID(enc)); - goto end; - } - - crtc_state = drm_atomic_get_crtc_state(state, enc->crtc); - conn_state = drm_atomic_get_connector_state(state, conn); - if (IS_ERR(conn_state)) { - SDE_ERROR("error %d getting connector %d state\n", - ret, DRMID(conn)); - goto end; - } - - crtc_state->active = true; - ret = drm_atomic_set_crtc_for_connector(conn_state, enc->crtc); + ret = sde_kms_set_crtc_for_conn(dev, enc, state); if (ret) - SDE_ERROR("error %d setting the crtc\n", ret); + goto end; ret = drm_atomic_commit(state); if (ret) @@ -3819,6 +4009,7 @@ static const struct msm_kms_funcs kms_funcs = { .irq_postinstall = sde_irq_postinstall, .irq_uninstall = sde_irq_uninstall, .irq = sde_irq, + .preclose = sde_kms_preclose, .lastclose = sde_kms_lastclose, .prepare_fence = sde_kms_prepare_fence, .prepare_commit = sde_kms_prepare_commit,