Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 125e556e authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "drm: msm: error notification and handling"

parents 1814c3e2 ac2c415c
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -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 \
+2 −0
Original line number Diff line number Diff line
@@ -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");
+3 −1
Original line number Diff line number Diff line
/* 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,
+52 −0
Original line number Diff line number Diff line
@@ -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;

+399 −0
Original line number Diff line number Diff line
/*
 * 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;
}
Loading