summaryrefslogtreecommitdiff
path: root/drivers/gpu/drm
diff options
context:
space:
mode:
authorGuchun Chen <guchunc@codeaurora.org>2017-06-29 14:15:04 +0800
committerGuchun Chen <guchunc@codeaurora.org>2017-08-08 16:31:37 +0800
commitb2278c42ae5241b3cdb3dc3ead107dd550c48b1a (patch)
tree8aeced561e266a080a11bfaf4881fc830ccfff80 /drivers/gpu/drm
parent58274ed8f17d7f826283d19c305fb7d85beecb3e (diff)
msm: sde: fix page freeing error when handing off early display
Sometimes, after user space is up and starts the handoff for early animation/display, page freeing error happens like below. The error happens in a very small possibility, but will lead to device rebooting. Call trace: dump_backtrace+0x0/0x1b8 show_stack+0x14/0x1c dump_stack+0xa8/0xe0 bad_page+0xf0/0x118 free_pages_prepare+0x100/0x2cc free_hot_cold_page+0x38/0x1a0 __free_pages+0x34/0x48 _sde_splash_free_bootup_memory_to_system+0x78/0x9c sde_splash_clean_up_free_resource+0xbc/0x1f0 sde_crtc_complete_commit+0x114/0x14c sde_kms_complete_commit+0x50/0x98 complete_commit+0x684/0x6b0 _msm_drm_commit_work_cb+0x30/0x38 kthread_worker_fn+0xcc/0x170 kthread+0xf8/0x100 ret_from_fork+0x10/0x20 The issue is caused by race competition. In async commit mode, the first is, mutex lock should be involved to ensure the member of splash_info structure can be protected to read/write in multiple threads. And the second is, 2 DSI connectors are available in kernel, and the coming sequent commits may happen on one DSI or on two DSI respectively. Because DSI interfaces shares the same resource in LK, if the consecutive commits are for one DSI, DSI resources could not be released, as another DSI is still visiting and using these resources. So till confirming the commits for different DSIs have been done, it's safe to release DSI resource. Change-Id: Ie6386e3e58d5320cd4e2caab9a07b4e659eb8780 Signed-off-by: Guchun Chen <guchunc@codeaurora.org> Signed-off-by: Yujun Zhang <yujunzhang@codeaurora.org>
Diffstat (limited to 'drivers/gpu/drm')
-rw-r--r--drivers/gpu/drm/msm/sde/sde_crtc.c9
-rw-r--r--drivers/gpu/drm/msm/sde/sde_splash.c98
-rw-r--r--drivers/gpu/drm/msm/sde/sde_splash.h10
3 files changed, 87 insertions, 30 deletions
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index 9ab216213d8e..2e9e2192670d 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -601,6 +601,7 @@ void sde_crtc_complete_commit(struct drm_crtc *crtc,
struct sde_crtc *sde_crtc;
struct sde_crtc_state *cstate;
struct drm_connector *conn;
+ struct sde_connector *c_conn;
struct drm_device *dev;
struct msm_drm_private *priv;
struct sde_kms *sde_kms;
@@ -625,16 +626,18 @@ void sde_crtc_complete_commit(struct drm_crtc *crtc,
for (i = 0; i < cstate->num_connectors; ++i)
sde_connector_complete_commit(cstate->connectors[i]);
- if (!sde_kms->splash_info.handoff &&
- sde_kms->splash_info.lk_is_exited) {
+ if (sde_splash_get_lk_complete_status(&sde_kms->splash_info)) {
mutex_lock(&dev->mode_config.mutex);
drm_for_each_connector(conn, crtc->dev) {
if (conn->state->crtc != crtc)
continue;
+ c_conn = to_sde_connector(conn);
+
sde_splash_clean_up_free_resource(priv->kms,
&priv->phandle,
- conn->connector_type);
+ c_conn->connector_type,
+ c_conn->display);
}
mutex_unlock(&dev->mode_config.mutex);
}
diff --git a/drivers/gpu/drm/msm/sde/sde_splash.c b/drivers/gpu/drm/msm/sde/sde_splash.c
index 79989f3ac1e9..19e6406600cd 100644
--- a/drivers/gpu/drm/msm/sde/sde_splash.c
+++ b/drivers/gpu/drm/msm/sde/sde_splash.c
@@ -22,6 +22,7 @@
#include "sde_hw_util.h"
#include "sde_hw_intf.h"
#include "sde_hw_catalog.h"
+#include "dsi_display.h"
#define MDP_SSPP_TOP0_OFF 0x1000
#define DISP_INTF_SEL 0x004
@@ -37,6 +38,9 @@
#define SDE_LK_EXIT_VALUE 0xDEADBEEF
#define SDE_LK_EXIT_MAX_LOOP 20
+
+static DEFINE_MUTEX(sde_splash_lock);
+
/*
* In order to free reseved memory from bootup, and we are not
* able to call the __init free functions, so we need to free
@@ -279,6 +283,15 @@ static void _sde_splash_destroy_splash_node(struct sde_splash_info *sinfo)
sinfo->splash_mem_size = NULL;
}
+static void _sde_splash_get_connector_ref_cnt(struct sde_splash_info *sinfo,
+ u32 *hdmi_cnt, u32 *dsi_cnt)
+{
+ mutex_lock(&sde_splash_lock);
+ *hdmi_cnt = sinfo->hdmi_connector_cnt;
+ *dsi_cnt = sinfo->dsi_connector_cnt;
+ mutex_unlock(&sde_splash_lock);
+}
+
static int _sde_splash_free_resource(struct msm_mmu *mmu,
struct sde_splash_info *sinfo, enum splash_connector_type conn)
{
@@ -442,13 +455,13 @@ int sde_splash_get_handoff_status(struct msm_kms *kms)
if (num_of_display_on) {
sinfo->handoff = true;
sinfo->program_scratch_regs = true;
+ sinfo->lk_is_exited = false;
} else {
sinfo->handoff = false;
sinfo->program_scratch_regs = false;
+ sinfo->lk_is_exited = true;
}
- sinfo->lk_is_exited = false;
-
return 0;
}
@@ -505,15 +518,29 @@ void sde_splash_setup_connector_count(struct sde_splash_info *sinfo,
}
}
+bool sde_splash_get_lk_complete_status(struct sde_splash_info *sinfo)
+{
+ bool ret = 0;
+
+ mutex_lock(&sde_splash_lock);
+ ret = !sinfo->handoff && !sinfo->lk_is_exited;
+ mutex_unlock(&sde_splash_lock);
+
+ return ret;
+}
+
int sde_splash_clean_up_free_resource(struct msm_kms *kms,
- struct sde_power_handle *phandle, int connector_type)
+ struct sde_power_handle *phandle,
+ int connector_type, void *display)
{
struct sde_kms *sde_kms;
struct sde_splash_info *sinfo;
struct msm_mmu *mmu;
+ struct dsi_display *dsi_display = display;
int ret = 0;
- static bool hdmi_is_released;
- static bool dsi_is_released;
+ int hdmi_conn_count = 0;
+ int dsi_conn_count = 0;
+ static const char *last_commit_display_type = "unknown";
if (!phandle || !kms) {
SDE_ERROR("invalid phandle/kms.\n");
@@ -527,16 +554,20 @@ int sde_splash_clean_up_free_resource(struct msm_kms *kms,
return -EINVAL;
}
- /* When both hdmi's and dsi's resource are freed,
- * 1. Destroy splash node objects.
- * 2. Release the memory which LK's stack is running on.
- * 3. Withdraw AHB data bus bandwidth voting.
- */
- if (sinfo->hdmi_connector_cnt == 0 &&
- sinfo->dsi_connector_cnt == 0) {
+ _sde_splash_get_connector_ref_cnt(sinfo, &hdmi_conn_count,
+ &dsi_conn_count);
+
+ mutex_lock(&sde_splash_lock);
+ if (hdmi_conn_count == 0 && dsi_conn_count == 0 &&
+ !sinfo->lk_is_exited) {
+ /* When both hdmi's and dsi's handoff are finished,
+ * 1. Destroy splash node objects.
+ * 2. Release the memory which LK's stack is running on.
+ * 3. Withdraw AHB data bus bandwidth voting.
+ */
DRM_INFO("HDMI and DSI resource handoff is completed\n");
- sinfo->lk_is_exited = false;
+ sinfo->lk_is_exited = true;
_sde_splash_destroy_splash_node(sinfo);
@@ -546,6 +577,7 @@ int sde_splash_clean_up_free_resource(struct msm_kms *kms,
sde_power_data_bus_bandwidth_ctrl(phandle,
sde_kms->core_client, false);
+ mutex_unlock(&sde_splash_lock);
return 0;
}
@@ -553,25 +585,36 @@ int sde_splash_clean_up_free_resource(struct msm_kms *kms,
switch (connector_type) {
case DRM_MODE_CONNECTOR_HDMIA:
- if (!hdmi_is_released)
+ if (sinfo->hdmi_connector_cnt == 1) {
sinfo->hdmi_connector_cnt--;
- if ((sinfo->hdmi_connector_cnt == 0) && (!hdmi_is_released)) {
- hdmi_is_released = true;
-
ret = _sde_splash_free_resource(mmu,
- sinfo, SPLASH_HDMI);
+ sinfo, SPLASH_HDMI);
}
break;
case DRM_MODE_CONNECTOR_DSI:
- if (!dsi_is_released)
- sinfo->dsi_connector_cnt--;
-
- if ((sinfo->dsi_connector_cnt == 0) && (!dsi_is_released)) {
- dsi_is_released = true;
+ /*
+ * Basically, we have commits coming on two DSI connectors.
+ * So when releasing DSI resource, it's ensured that the
+ * coming commits should happen on different DSIs, to promise
+ * the handoff has finished on the two DSIs, then it's safe
+ * to release DSI resource, otherwise, problem happens when
+ * freeing memory, while DSI0 or DSI1 is still visiting
+ * the memory.
+ */
+ if (strcmp(dsi_display->display_type, "unknown") &&
+ strcmp(last_commit_display_type,
+ dsi_display->display_type)) {
+ if (sinfo->dsi_connector_cnt > 1)
+ sinfo->dsi_connector_cnt--;
+ else if (sinfo->dsi_connector_cnt == 1) {
+ ret = _sde_splash_free_resource(mmu,
+ sinfo, SPLASH_DSI);
+
+ sinfo->dsi_connector_cnt--;
+ }
- ret = _sde_splash_free_resource(mmu,
- sinfo, SPLASH_DSI);
+ last_commit_display_type = dsi_display->display_type;
}
break;
default:
@@ -580,6 +623,8 @@ int sde_splash_clean_up_free_resource(struct msm_kms *kms,
__func__, connector_type);
}
+ mutex_unlock(&sde_splash_lock);
+
return ret;
}
@@ -603,6 +648,7 @@ int sde_splash_clean_up_exit_lk(struct msm_kms *kms)
}
/* Monitor LK's status and tell it to exit. */
+ mutex_lock(&sde_splash_lock);
if (sinfo->program_scratch_regs) {
if (_sde_splash_lk_check(sde_kms->hw_intr))
_sde_splash_notify_lk_exit(sde_kms->hw_intr);
@@ -610,6 +656,7 @@ int sde_splash_clean_up_exit_lk(struct msm_kms *kms)
sinfo->handoff = false;
sinfo->program_scratch_regs = false;
}
+ mutex_unlock(&sde_splash_lock);
if (!sde_kms->aspace[0] || !sde_kms->aspace[0]->mmu) {
/* We do not return fault value here, to ensure
@@ -631,6 +678,5 @@ int sde_splash_clean_up_exit_lk(struct msm_kms *kms)
}
}
- sinfo->lk_is_exited = true;
return 0;
}
diff --git a/drivers/gpu/drm/msm/sde/sde_splash.h b/drivers/gpu/drm/msm/sde/sde_splash.h
index 47965693f0b8..babf88335e49 100644
--- a/drivers/gpu/drm/msm/sde/sde_splash.h
+++ b/drivers/gpu/drm/msm/sde/sde_splash.h
@@ -95,7 +95,8 @@ int sde_splash_clean_up_exit_lk(struct msm_kms *kms);
* HDMI's and DSI's resource respectively.
*/
int sde_splash_clean_up_free_resource(struct msm_kms *kms,
- struct sde_power_handle *phandle, int connector_type);
+ struct sde_power_handle *phandle,
+ int connector_type, void *display);
/**
* sde_splash_parse_dt.
@@ -121,4 +122,11 @@ void sde_splash_destroy(struct sde_splash_info *sinfo,
struct sde_power_handle *phandle,
struct sde_power_client *pclient);
+/**
+ * sde_splash_get_lk_complete_status
+ *
+ * Get LK's status to check if it has been stopped.
+ */
+bool sde_splash_get_lk_complete_status(struct sde_splash_info *sinfo);
+
#endif