From f3b2c5f89ce87af31d27fcbb083dd2e9eefeb19e Mon Sep 17 00:00:00 2001 From: Tatenda Chipeperekwa Date: Wed, 12 Aug 2020 18:56:56 -0700 Subject: [PATCH] disp: msm: dp: add support for Trusted UI transitions We add support for Trusted UI (TUI) transitions and address the following use cases: 1. Display was active before TUI start - Power off on TUI start, power off on TUI stop - Register access not allowed after TUI start (skip all events except disconnect) 2. Hotplug while TUI is active - Connect: skip sending connect uevent - Disconnect: send disconnect uevent and skip any controller programming 3. TUI start while processing HPD High - Complete all connect work (and therefore any register access) then send connect uevent 4. Audio - Disable audio at TUI start and skip audio programming if TUI is active Change-Id: I553e5fa9f3b8265dd0410bf2d616a9accf90605f Signed-off-by: Tatenda Chipeperekwa --- msm/dp/dp_audio.c | 10 ++++ msm/dp/dp_audio.h | 10 +++- msm/dp/dp_display.c | 135 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 144 insertions(+), 11 deletions(-) diff --git a/msm/dp/dp_audio.c b/msm/dp/dp_audio.c index 8284207408..b51ade3a9b 100644 --- a/msm/dp/dp_audio.c +++ b/msm/dp/dp_audio.c @@ -397,6 +397,11 @@ static int dp_audio_info_setup(struct platform_device *pdev, return rc; } + if (audio->dp_audio.tui_active) { + DP_DEBUG("TUI session active\n"); + return 0; + } + mutex_lock(&audio->ops_lock); audio->channels = params->num_of_channels; @@ -495,6 +500,11 @@ static void dp_audio_teardown_done(struct platform_device *pdev) if (IS_ERR(audio)) return; + if (audio->dp_audio.tui_active) { + DP_DEBUG("TUI session active\n"); + return; + } + mutex_lock(&audio->ops_lock); dp_audio_enable(audio, false); mutex_unlock(&audio->ops_lock); diff --git a/msm/dp/dp_audio.h b/msm/dp/dp_audio.h index 882551e0fe..19e207e0e5 100644 --- a/msm/dp/dp_audio.h +++ b/msm/dp/dp_audio.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2016-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2016-2020, The Linux Foundation. All rights reserved. */ #ifndef _DP_AUDIO_H_ @@ -15,15 +15,18 @@ * struct dp_audio * @lane_count: number of lanes configured in current session * @bw_code: link rate's bandwidth code for current session + * @tui_active: set to true if TUI is active in the system */ struct dp_audio { u32 lane_count; u32 bw_code; + bool tui_active; /** * on() * - * Enables the audio by notifying the user module. + * Notifies user mode clients that DP is powered on, and that audio + * playback can start on the external display. * * @dp_audio: an instance of struct dp_audio. * @@ -34,7 +37,8 @@ struct dp_audio { /** * off() * - * Disables the audio by notifying the user module. + * Notifies user mode clients that DP is shutting down, and audio + * playback should be stopped on the external display. * * @dp_audio: an instance of struct dp_audio. * diff --git a/msm/dp/dp_display.c b/msm/dp/dp_display.c index caeba8da53..1d23dd774f 100644 --- a/msm/dp/dp_display.c +++ b/msm/dp/dp_display.c @@ -63,6 +63,7 @@ enum dp_display_states { DP_STATE_ABORTED = BIT(8), DP_STATE_HDCP_ABORTED = BIT(9), DP_STATE_SRC_PWRDN = BIT(10), + DP_STATE_TUI_ACTIVE = BIT(11), }; static char *dp_display_state_name(enum dp_display_states state) @@ -116,6 +117,10 @@ static char *dp_display_state_name(enum dp_display_states state) len += scnprintf(buf + len, sizeof(buf) - len, "|%s|", "SRC_PWRDN"); + if (state & DP_STATE_TUI_ACTIVE) + len += scnprintf(buf + len, sizeof(buf) - len, "|%s|", + "TUI_ACTIVE"); + if (!strlen(buf)) return "DISCONNECTED"; @@ -667,6 +672,74 @@ error: return rc; } +static void dp_display_pause_audio(struct dp_display_private *dp, bool pause) +{ + struct dp_panel *dp_panel; + int idx; + + for (idx = DP_STREAM_0; idx < DP_STREAM_MAX; idx++) { + if (!dp->active_panels[idx]) + continue; + dp_panel = dp->active_panels[idx]; + + if (dp_panel->audio_supported) + dp_panel->audio->tui_active = pause; + } +} + +static int dp_display_pre_hw_release(void *data) +{ + struct dp_display_private *dp; + struct dp_display *dp_display = data; + + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY); + + if (!dp_display) + return -EINVAL; + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + dp_display_state_add(DP_STATE_TUI_ACTIVE); + cancel_work_sync(&dp->connect_work); + cancel_work_sync(&dp->attention_work); + flush_workqueue(dp->wq); + + dp_display_pause_audio(dp, true); + disable_irq(dp->irq); + + mutex_unlock(&dp->session_lock); + + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT); + return 0; +} + +static int dp_display_post_hw_acquire(void *data) +{ + struct dp_display_private *dp; + struct dp_display *dp_display = data; + + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY); + + if (!dp_display) + return -EINVAL; + + dp = container_of(dp_display, struct dp_display_private, dp_display); + + mutex_lock(&dp->session_lock); + + dp_display_state_remove(DP_STATE_TUI_ACTIVE); + dp_display_pause_audio(dp, false); + enable_irq(dp->irq); + + mutex_unlock(&dp->session_lock); + + SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_EXIT); + return 0; +} + + static int dp_display_bind(struct device *dev, struct device *master, void *data) { @@ -674,6 +747,10 @@ static int dp_display_bind(struct device *dev, struct device *master, struct dp_display_private *dp; struct drm_device *drm; struct platform_device *pdev = to_platform_device(dev); + struct msm_vm_ops vm_event_ops = { + .vm_pre_hw_release = dp_display_pre_hw_release, + .vm_post_hw_acquire = dp_display_post_hw_acquire, + }; if (!dev || !pdev || !master) { DP_ERR("invalid param(s), dev %pK, pdev %pK, master %pK\n", @@ -693,6 +770,8 @@ static int dp_display_bind(struct device *dev, struct device *master, dp->dp_display.drm_dev = drm; dp->priv = drm->dev_private; + msm_register_vm_event(master, dev, &vm_event_ops, + (void *)&dp->dp_display); end: return rc; } @@ -827,6 +906,15 @@ static int dp_display_send_hpd_notification(struct dp_display_private *dp) reinit_completion(&dp->notification_comp); dp_display_send_hpd_event(dp); + /* + * Skip the wait if TUI is active considering that the user mode will + * not act on the notification until after the TUI session is over. + */ + if (dp_display_state_is(DP_STATE_TUI_ACTIVE)) { + dp_display_state_log("[TUI is active, skipping wait]"); + goto skip_wait; + } + if (hpd && dp->mst.mst_active) goto skip_wait; @@ -1200,26 +1288,30 @@ static int dp_display_usbpd_configure_cb(struct device *dev) if (!dev) { DP_ERR("invalid dev\n"); - rc = -EINVAL; - goto end; + return -EINVAL; } dp = dev_get_drvdata(dev); if (!dp) { DP_ERR("no driver data found\n"); - rc = -ENODEV; - goto end; + return -ENODEV; } if (!dp->debug->sim_mode && !dp->parser->no_aux_switch && !dp->parser->gpio_aux_switch) { rc = dp->aux->aux_switch(dp->aux, true, dp->hpd->orientation); if (rc) - goto end; + return rc; } mutex_lock(&dp->session_lock); + if (dp_display_state_is(DP_STATE_TUI_ACTIVE)) { + dp_display_state_log("[TUI is active]"); + mutex_unlock(&dp->session_lock); + return 0; + } + dp_display_state_remove(DP_STATE_ABORTED); dp_display_state_add(DP_STATE_CONFIGURED); @@ -1231,8 +1323,8 @@ static int dp_display_usbpd_configure_cb(struct device *dev) else dp->process_hpd_connect = true; mutex_unlock(&dp->session_lock); -end: - return rc; + + return 0; } static int dp_display_stream_pre_disable(struct dp_display_private *dp, @@ -1268,6 +1360,11 @@ static void dp_display_clean(struct dp_display_private *dp) SDE_EVT32_EXTERNAL(SDE_EVTLOG_FUNC_ENTRY, dp->state); + if (dp_display_state_is(DP_STATE_TUI_ACTIVE)) { + DP_WARN("TUI is active\n"); + return; + } + if (dp_display_is_hdcp_enabled(dp) && status->hdcp_state != HDCP_STATE_INACTIVE) { cancel_delayed_work_sync(&dp->hdcp_cb_work); @@ -1550,7 +1647,24 @@ static int dp_display_usbpd_attention_cb(struct device *dev) if (!dp->hpd->hpd_high) { dp_display_disconnect_sync(dp); - } else if ((dp->hpd->hpd_irq && dp_display_state_is(DP_STATE_READY)) || + return 0; + } + + /* + * Ignore all the attention messages except HPD LOW when TUI is + * active, so user mode can be notified of the disconnect event. This + * allows user mode to tear down the control path after the TUI + * session is over. Ideally this should never happen, but on the off + * chance that there is a race condition in which there is a IRQ HPD + * during tear down of DP at TUI start then this check might help avoid + * a potential issue accessing registers in attention processing. + */ + if (dp_display_state_is(DP_STATE_TUI_ACTIVE)) { + DP_WARN("TUI is active\n"); + return 0; + } + + if ((dp->hpd->hpd_irq && dp_display_state_is(DP_STATE_READY)) || dp->debug->mst_hpd_sim) { queue_work(dp->wq, &dp->attention_work); } else if (dp->process_hpd_connect || @@ -1570,6 +1684,11 @@ static void dp_display_connect_work(struct work_struct *work) struct dp_display_private *dp = container_of(work, struct dp_display_private, connect_work); + if (dp_display_state_is(DP_STATE_TUI_ACTIVE)) { + dp_display_state_log("[TUI is active]"); + return; + } + if (dp_display_state_is(DP_STATE_ABORTED)) { DP_WARN("HPD off requested\n"); return;