diff options
| author | Linux Build Service Account <lnxbuild@localhost> | 2018-07-13 14:31:17 -0700 |
|---|---|---|
| committer | Gerrit - the friendly Code Review server <code-review@localhost> | 2018-07-13 14:31:15 -0700 |
| commit | 125e556ec0de90acbda3e580a3b4ee8fc0bf408b (patch) | |
| tree | fefec7661e778b0b3fe95bb3d6ae65b068a3d63d /drivers/gpu | |
| parent | 1814c3e2556686dd16918920172803767eadde13 (diff) | |
| parent | ac2c415caff00927ac56bca1c05f3b5be1efe5be (diff) | |
Merge "drm: msm: error notification and handling"
Diffstat (limited to 'drivers/gpu')
| -rw-r--r-- | drivers/gpu/drm/msm/Makefile | 1 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde/sde_encoder.c | 2 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c | 4 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde/sde_kms.c | 52 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde/sde_recovery_manager.c | 399 | ||||
| -rw-r--r-- | drivers/gpu/drm/msm/sde/sde_recovery_manager.h | 118 |
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__ */ |
