summaryrefslogtreecommitdiff
path: root/drivers/gpu
diff options
context:
space:
mode:
authorLinux Build Service Account <lnxbuild@localhost>2018-07-13 14:31:17 -0700
committerGerrit - the friendly Code Review server <code-review@localhost>2018-07-13 14:31:15 -0700
commit125e556ec0de90acbda3e580a3b4ee8fc0bf408b (patch)
treefefec7661e778b0b3fe95bb3d6ae65b068a3d63d /drivers/gpu
parent1814c3e2556686dd16918920172803767eadde13 (diff)
parentac2c415caff00927ac56bca1c05f3b5be1efe5be (diff)
Merge "drm: msm: error notification and handling"
Diffstat (limited to 'drivers/gpu')
-rw-r--r--drivers/gpu/drm/msm/Makefile1
-rw-r--r--drivers/gpu/drm/msm/sde/sde_encoder.c2
-rw-r--r--drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c4
-rw-r--r--drivers/gpu/drm/msm/sde/sde_kms.c52
-rw-r--r--drivers/gpu/drm/msm/sde/sde_recovery_manager.c399
-rw-r--r--drivers/gpu/drm/msm/sde/sde_recovery_manager.h118
6 files changed, 575 insertions, 1 deletions
diff --git a/drivers/gpu/drm/msm/Makefile b/drivers/gpu/drm/msm/Makefile
index 678b2178cb69..f8758e063146 100644
--- a/drivers/gpu/drm/msm/Makefile
+++ b/drivers/gpu/drm/msm/Makefile
@@ -49,6 +49,7 @@ msm_drm-y := \
sde/sde_color_processing.o \
sde/sde_vbif.o \
sde/sde_splash.o \
+ sde/sde_recovery_manager.o \
sde_dbg.o \
sde_dbg_evtlog.o \
sde_io_util.o \
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index fa17768d9939..77fdcd86c920 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -21,6 +21,7 @@
#include <linux/seq_file.h>
#include "msm_drv.h"
+#include "sde_recovery_manager.h"
#include "sde_kms.h"
#include "drm_crtc.h"
#include "drm_crtc_helper.h"
@@ -603,6 +604,7 @@ static void sde_encoder_underrun_callback(struct drm_encoder *drm_enc,
atomic_inc(&phy_enc->underrun_cnt);
SDE_EVT32(DRMID(drm_enc), atomic_read(&phy_enc->underrun_cnt));
+ sde_recovery_set_events(SDE_UNDERRUN);
trace_sde_encoder_underrun(DRMID(drm_enc),
atomic_read(&phy_enc->underrun_cnt));
SDE_DBG_CTRL("stop_ftrace");
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
index 2f89c571fcfc..8db77f2c60e8 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2018, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@@ -11,6 +11,7 @@
*/
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
+#include "sde_recovery_manager.h"
#include "sde_encoder_phys.h"
#include "sde_hw_interrupts.h"
#include "sde_core_irq.h"
@@ -704,6 +705,7 @@ static int sde_encoder_phys_vid_wait_for_vblank(
SDE_EVT32(DRMID(phys_enc->parent),
vid_enc->hw_intf->idx - INTF_0);
SDE_ERROR_VIDENC(vid_enc, "kickoff timed out\n");
+ sde_recovery_set_events(SDE_VSYNC_MISS);
if (notify && phys_enc->parent_ops.handle_frame_done)
phys_enc->parent_ops.handle_frame_done(
phys_enc->parent, phys_enc,
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c
index 95ab14ffc3ac..bd8f3701a282 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.c
+++ b/drivers/gpu/drm/msm/sde/sde_kms.c
@@ -37,6 +37,7 @@
#include "sde_encoder.h"
#include "sde_plane.h"
#include "sde_crtc.h"
+#include "sde_recovery_manager.h"
#define CREATE_TRACE_POINTS
#include "sde_trace.h"
@@ -58,6 +59,19 @@
#define SDE_DEBUGFS_DIR "msm_sde"
#define SDE_DEBUGFS_HWMASKNAME "hw_log_mask"
+static int sde_kms_recovery_callback(int err_code,
+ struct recovery_client_info *client_info);
+
+static struct recovery_client_info info = {
+ .name = "sde_kms",
+ .recovery_cb = sde_kms_recovery_callback,
+ .err_supported[0] = {SDE_UNDERRUN, 0, 0},
+ .err_supported[1] = {SDE_VSYNC_MISS, 0, 0},
+ .no_of_err = 2,
+ .handle = NULL,
+ .pdata = NULL,
+};
+
/**
* sdecustom - enable certain driver customizations for sde clients
* Enabling this modifies the standard DRM behavior slightly and assumes
@@ -1062,6 +1076,8 @@ static void sde_kms_destroy(struct msm_kms *kms)
return;
}
+ sde_recovery_client_unregister(info.handle);
+ info.handle = NULL;
_sde_kms_hw_destroy(sde_kms, dev->platformdev);
kfree(sde_kms);
}
@@ -1264,6 +1280,11 @@ static int sde_kms_hw_init(struct msm_kms *kms)
goto end;
}
+ rc = sde_recovery_client_register(&info);
+ if (rc)
+ pr_err("%s recovery mgr register failed %d\n",
+ __func__, rc);
+
sde_kms = to_sde_kms(kms);
dev = sde_kms->dev;
if (!dev || !dev->platformdev) {
@@ -1487,10 +1508,34 @@ end:
return rc;
}
+static int sde_kms_recovery_callback(int err_code,
+ struct recovery_client_info *client_info)
+{
+ int rc = 0;
+
+ switch (err_code) {
+ case SDE_UNDERRUN:
+ pr_debug("%s [SDE_UNDERRUN] error is auto HW receovered\n",
+ __func__);
+ break;
+
+ case SDE_VSYNC_MISS:
+ pr_debug("%s [SDE_VSYNC_MISS] trigger soft reset\n", __func__);
+ break;
+
+ default:
+ pr_err("%s error %d undefined\n", __func__, err_code);
+
+ }
+
+ return rc;
+}
+
struct msm_kms *sde_kms_init(struct drm_device *dev)
{
struct msm_drm_private *priv;
struct sde_kms *sde_kms;
+ int rc = 0;
if (!dev || !dev->dev_private) {
SDE_ERROR("drm device node invalid\n");
@@ -1505,6 +1550,13 @@ struct msm_kms *sde_kms_init(struct drm_device *dev)
return ERR_PTR(-ENOMEM);
}
+ rc = sde_init_recovery_mgr(dev);
+ if (rc) {
+ SDE_ERROR("Failed SDE recovery mgr Init, err = %d\n", rc);
+ kfree(sde_kms);
+ return ERR_PTR(-EFAULT);
+ }
+
msm_kms_init(&sde_kms->base, &kms_funcs);
sde_kms->dev = dev;
diff --git a/drivers/gpu/drm/msm/sde/sde_recovery_manager.c b/drivers/gpu/drm/msm/sde/sde_recovery_manager.c
new file mode 100644
index 000000000000..ae42fd309293
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_recovery_manager.c
@@ -0,0 +1,399 @@
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include "sde_recovery_manager.h"
+#include "sde_kms.h"
+
+
+static struct recovery_mgr_info *rec_mgr;
+
+static ssize_t sde_recovery_mgr_rda_clients_attr(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ ssize_t len = 0;
+ struct list_head *pos;
+ struct recovery_client_db *temp = NULL;
+
+ mutex_lock(&rec_mgr->rec_lock);
+
+ len = snprintf(buf, PAGE_SIZE, "Clients:\n");
+
+ list_for_each(pos, &rec_mgr->client_list) {
+ temp = list_entry(pos, struct recovery_client_db, list);
+
+ len += snprintf(buf + len, PAGE_SIZE - len, "%s\n",
+ temp->client_info.name);
+ }
+
+ mutex_unlock(&rec_mgr->rec_lock);
+
+ return len;
+}
+
+static DEVICE_ATTR(clients, S_IRUGO, sde_recovery_mgr_rda_clients_attr, NULL);
+
+static struct attribute *recovery_attrs[] = {
+ &dev_attr_clients.attr,
+ NULL,
+};
+
+static struct attribute_group recovery_mgr_attr_group = {
+ .attrs = recovery_attrs,
+};
+
+static void sde_recovery_mgr_notify(bool err_state)
+{
+ char *envp[2];
+ char *uevent_str = kzalloc(SZ_4K, GFP_KERNEL);
+
+ if (uevent_str == NULL) {
+ DRM_ERROR("failed to allocate event string\n");
+ return;
+ }
+ if (err_state == true)
+ snprintf(uevent_str, MAX_REC_UEVENT_LEN,
+ "DISPLAY_ERROR_RECOVERED\n");
+ else
+ snprintf(uevent_str, MAX_REC_UEVENT_LEN,
+ "DISPLAY_CRITICAL_ERROR\n");
+
+ DRM_DEBUG("generating uevent [%s]\n", uevent_str);
+
+ envp[0] = uevent_str;
+ envp[1] = NULL;
+
+ mutex_lock(&rec_mgr->dev->mode_config.mutex);
+ kobject_uevent_env(&rec_mgr->dev->primary->kdev->kobj,
+ KOBJ_CHANGE, envp);
+ mutex_unlock(&rec_mgr->dev->mode_config.mutex);
+ kfree(uevent_str);
+}
+
+static void sde_recovery_mgr_recover(int err_code)
+{
+ struct list_head *pos;
+ struct recovery_client_db *c = NULL;
+ int tmp_err, rc, pre, post, i;
+ bool found = false;
+ static bool rec_flag = true;
+
+ mutex_lock(&rec_mgr->rec_lock);
+ list_for_each(pos, &rec_mgr->client_list) {
+ c = list_entry(pos, struct recovery_client_db, list);
+
+ mutex_unlock(&rec_mgr->rec_lock);
+
+ for (i = 0; i < MAX_REC_ERR_SUPPORT; i++) {
+ tmp_err = c->client_info.err_supported[i].
+ reported_err_code;
+ if (tmp_err == err_code) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found == true) {
+
+ pre = c->client_info.err_supported[i].pre_err_code;
+ if (pre && pre != '0')
+ sde_recovery_mgr_recover(pre);
+
+ if (c->client_info.recovery_cb) {
+ rc = c->client_info.recovery_cb(err_code,
+ &c->client_info);
+ if (rc) {
+ pr_err("%s failed to recover error %d\n",
+ __func__, err_code);
+ rec_flag = false;
+ } else {
+ pr_debug("%s Recovery successful[%d]\n",
+ __func__, err_code);
+ }
+ }
+
+ post = c->client_info.err_supported[i].post_err_code;
+ if (post && post != '0')
+ sde_recovery_mgr_recover(post);
+
+ }
+ mutex_lock(&rec_mgr->rec_lock);
+
+ if (found)
+ break;
+ }
+
+ if (rec_flag) {
+ pr_debug("%s successful full recovery\n", __func__);
+ sde_recovery_mgr_notify(true);
+ }
+
+ mutex_unlock(&rec_mgr->rec_lock);
+}
+
+static void sde_recovery_mgr_event_work(struct work_struct *work)
+{
+ struct list_head *pos, *q;
+ struct recovery_event_db *temp_event;
+ int err_code;
+
+ if (!rec_mgr) {
+ pr_err("%s recovery manager is NULL\n", __func__);
+ return;
+ }
+
+ mutex_lock(&rec_mgr->rec_lock);
+
+ list_for_each_safe(pos, q, &rec_mgr->event_list) {
+ temp_event = list_entry(pos, struct recovery_event_db, list);
+
+ err_code = temp_event->err;
+
+ rec_mgr->recovery_ongoing = true;
+
+ mutex_unlock(&rec_mgr->rec_lock);
+
+ /* notify error */
+ sde_recovery_mgr_notify(false);
+ /* recover error */
+ sde_recovery_mgr_recover(err_code);
+
+ mutex_lock(&rec_mgr->rec_lock);
+
+ list_del(pos);
+ kfree(temp_event);
+ }
+
+ rec_mgr->recovery_ongoing = false;
+ mutex_unlock(&rec_mgr->rec_lock);
+
+}
+
+int sde_recovery_set_events(int err)
+{
+ int rc = 0;
+ struct list_head *pos;
+ struct recovery_event_db *temp;
+ bool found = false;
+
+ mutex_lock(&rec_mgr->rec_lock);
+
+ /* check if there is same event in the list */
+ list_for_each(pos, &rec_mgr->event_list) {
+ temp = list_entry(pos, struct recovery_event_db, list);
+ if (err == temp->err) {
+ found = true;
+ pr_info("%s error %d is already present in list\n",
+ __func__, err);
+ break;
+ }
+ }
+
+ if (!found) {
+ temp = kzalloc(sizeof(struct recovery_event_db), GFP_KERNEL);
+ if (!temp) {
+ pr_err("%s out of memory\n", __func__);
+ rc = -ENOMEM;
+ goto out;
+ }
+ temp->err = err;
+
+ list_add_tail(&temp->list, &rec_mgr->event_list);
+ queue_work(rec_mgr->event_queue, &rec_mgr->event_work);
+ }
+
+out:
+ mutex_unlock(&rec_mgr->rec_lock);
+ return rc;
+}
+
+int sde_recovery_client_register(struct recovery_client_info *client)
+{
+ int rc = 0;
+ struct list_head *pos;
+ struct recovery_client_db *c = NULL;
+ bool found = false;
+
+ if (!rec_mgr) {
+ pr_err("%s recovery manager is not initialized\n", __func__);
+ return -EPERM;
+ }
+
+ if (!strlen(client->name)) {
+ pr_err("%s client name is empty\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_lock(&rec_mgr->rec_lock);
+
+ /* check if there is same client */
+ list_for_each(pos, &rec_mgr->client_list) {
+ c = list_entry(pos, struct recovery_client_db, list);
+ if (!strcmp(c->client_info.name,
+ client->name)) {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found) {
+ c = kzalloc(sizeof(*c), GFP_KERNEL);
+ if (!c) {
+ pr_err("%s out of memory for client", __func__);
+ rc = -ENOMEM;
+ goto out;
+ }
+ } else {
+ pr_err("%s client = %s is already registered\n",
+ __func__, client->name);
+ client->handle = c;
+ goto out;
+ }
+
+ memcpy(&(c->client_info), client, sizeof(struct recovery_client_info));
+
+ list_add_tail(&c->list, &rec_mgr->client_list);
+ rec_mgr->num_of_clients++;
+
+ client->handle = c;
+
+out:
+ mutex_unlock(&rec_mgr->rec_lock);
+ return rc;
+}
+
+int sde_recovery_client_unregister(void *handle)
+{
+ struct list_head *pos, *q, *pos1;
+ struct recovery_client_db *temp_client;
+ struct recovery_event_db *temp;
+ int client_err = 0;
+ bool found = false;
+ bool found_pending = false;
+ int i, rc = 0;
+ struct recovery_client_info *client =
+ &((struct recovery_client_db *)handle)->client_info;
+
+ if (!handle) {
+ pr_err("%s handle is NULL\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!strlen(client->name)) {
+ pr_err("%s client name is empty\n", __func__);
+ return -EINVAL;
+ }
+
+ mutex_lock(&rec_mgr->rec_lock);
+
+ if (rec_mgr->recovery_ongoing) {
+ pr_err("%s SDE Executing Recovery, Failed! Unregister client %s\n",
+ __func__, client->name);
+ goto out;
+ }
+
+ /* check if client is present in the list */
+ list_for_each_safe(pos, q, &rec_mgr->client_list) {
+ temp_client = list_entry(pos, struct recovery_client_db, list);
+ if (!strcmp(temp_client->client_info.name, client->name)) {
+ found = true;
+
+ /* free any pending event for this client */
+ list_for_each(pos1, &rec_mgr->event_list) {
+ temp = list_entry(pos1,
+ struct recovery_event_db, list);
+
+ found_pending = false;
+ for (i = 0; i < MAX_REC_ERR_SUPPORT; i++) {
+ client_err = temp_client->
+ client_info.err_supported[i].
+ reported_err_code;
+ if (temp->err == client_err)
+ found_pending = true;
+ }
+
+ if (found_pending) {
+ list_del(pos1);
+ kfree(temp);
+ }
+ }
+
+ list_del(pos);
+ kfree(temp_client);
+ rec_mgr->num_of_clients--;
+ break;
+ }
+ }
+
+ if (!found) {
+ pr_err("%s can't find the client[%s] from db\n",
+ __func__, client->name);
+ rc = -EFAULT;
+ }
+
+out:
+ mutex_unlock(&rec_mgr->rec_lock);
+ return rc;
+}
+
+int sde_init_recovery_mgr(struct drm_device *dev)
+{
+ struct recovery_mgr_info *rec = NULL;
+ int rc = 0;
+
+ if (!dev || !dev->dev_private) {
+ SDE_ERROR("drm device node invalid\n");
+ return -EINVAL;
+ }
+
+ rec = kzalloc(sizeof(struct recovery_mgr_info), GFP_KERNEL);
+ if (!rec)
+ return -ENOMEM;
+
+ mutex_init(&rec->rec_lock);
+
+ rec->dev = dev;
+ rc = sysfs_create_group(&dev->primary->kdev->kobj,
+ &recovery_mgr_attr_group);
+ if (rc) {
+ pr_err("%s sysfs_create_group fails=%d", __func__, rc);
+ rec->sysfs_created = false;
+ } else {
+ rec->sysfs_created = true;
+ }
+
+ INIT_LIST_HEAD(&rec->event_list);
+ INIT_LIST_HEAD(&rec->client_list);
+ INIT_WORK(&rec->event_work, sde_recovery_mgr_event_work);
+ rec->event_queue = create_workqueue("recovery_event");
+
+ if (IS_ERR_OR_NULL(rec->event_queue)) {
+ pr_err("%s unable to create queue; errno = %ld",
+ __func__, PTR_ERR(rec->event_queue));
+ rec->event_queue = NULL;
+ rc = -EFAULT;
+ goto err;
+ }
+
+ rec_mgr = rec;
+
+ return rc;
+
+err:
+ mutex_destroy(&rec->rec_lock);
+ if (rec->sysfs_created)
+ sysfs_remove_group(&rec_mgr->dev->primary->kdev->kobj,
+ &recovery_mgr_attr_group);
+ kfree(rec);
+ return rc;
+}
diff --git a/drivers/gpu/drm/msm/sde/sde_recovery_manager.h b/drivers/gpu/drm/msm/sde/sde_recovery_manager.h
new file mode 100644
index 000000000000..32fe17a187a0
--- /dev/null
+++ b/drivers/gpu/drm/msm/sde/sde_recovery_manager.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2018, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __SDE_RECOVERY_MANAGER_H__
+#define __SDE_RECOVERY_MANAGER_H__
+
+#include <linux/list.h>
+#include <linux/kthread.h>
+#include <linux/platform_device.h>
+#include <linux/kobject.h>
+#include <drm/msm_drm.h>
+#include <linux/slab.h>
+#include <drm/drmP.h>
+
+
+
+/* MSM Recovery Manager related definitions */
+
+#define MAX_REC_NAME_LEN (16)
+#define MAX_REC_UEVENT_LEN (64)
+#define MAX_REC_ERR_SUPPORT (2)
+
+/* MSM Recovery Manager Error Code */
+#define SDE_UNDERRUN 222
+#define SDE_VSYNC_MISS 333
+
+/**
+ * struct recovery_mgr_info - Recovery manager information
+ * @dev: drm device.
+ * @rec_lock: mutex lock for synchronized access to recovery mgr data.
+ * @event_list: list of reported events.
+ * @client_list: list of registered clients.
+ * @event_work: work for event handling.
+ * @event_queue: Queue for scheduling the event work.
+ * @num_of_clients: no. of clients registered.
+ * @recovery_ongoing: status indicating execution of recovery thread.
+ */
+struct recovery_mgr_info {
+ struct drm_device *dev;
+ struct mutex rec_lock;
+ struct list_head event_list;
+ struct list_head client_list;
+ struct work_struct event_work;
+ struct workqueue_struct *event_queue;
+ int num_of_clients;
+ int sysfs_created;
+ int recovery_ongoing;
+};
+
+/**
+ * struct recovery_error_info - Error information
+ * @reported_err_code: error reported for recovery.
+ * @pre_err_code: list of errors to be recovered before reported_err_code.
+ * @post_err_code: list of errors to be recovered after reported_err_code.
+ */
+struct recovery_error_info {
+ int reported_err_code;
+ int pre_err_code;
+ int post_err_code;
+};
+
+/**
+ * struct recovery_client_info - Client information
+ * @name: name of the client.
+ * @recovery_cb: recovery callback to recover the errors reported.
+ * @err_supported: list of errors that can be detected by client.
+ * @no_of_err: no. of errors supported by the client.
+ * @handle: Opaque handle passed to client
+ */
+struct recovery_client_info {
+ char name[MAX_REC_NAME_LEN];
+ int (*recovery_cb)(int err_code,
+ struct recovery_client_info *client_info);
+ struct recovery_error_info
+ err_supported[MAX_REC_ERR_SUPPORT];
+ int no_of_err;
+ void *pdata;
+ void *handle;
+};
+
+/**
+ * struct recovery_event_db - event database.
+ * @err: error code that client reports.
+ * @list: list pointer.
+ */
+struct recovery_event_db {
+ int err;
+ struct list_head list;
+};
+
+/**
+ * struct recovery_client_db - client database.
+ * @client_info: information that client registers.
+ * @list: list pointer.
+ */
+struct recovery_client_db {
+ struct recovery_client_info client_info;
+ struct list_head list;
+};
+
+int sde_recovery_set_events(int err);
+int sde_recovery_client_register(struct recovery_client_info *client);
+int sde_recovery_client_unregister(void *handle);
+int sde_init_recovery_mgr(struct drm_device *dev);
+
+
+#endif /* __SDE_RECOVERY_MANAGER_H__ */