365 lines
10 KiB
C
365 lines
10 KiB
C
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
/*
|
||
|
* Copyright (c) 2021 Samsung Electronics Co., Ltd.
|
||
|
* http://www.samsung.com/
|
||
|
*
|
||
|
* Exynos - S2R Scenario Lockup Detector
|
||
|
*
|
||
|
*/
|
||
|
|
||
|
#include <linux/platform_device.h>
|
||
|
#include <linux/of.h>
|
||
|
#include <linux/of_device.h>
|
||
|
#include <linux/module.h>
|
||
|
#include <linux/mm.h>
|
||
|
#include <linux/cpu.h>
|
||
|
#include <linux/cpuhotplug.h>
|
||
|
#include <linux/suspend.h>
|
||
|
#include <linux/sched/clock.h>
|
||
|
#include <linux/preempt.h>
|
||
|
#include <linux/timer.h>
|
||
|
#include <uapi/linux/sched/types.h>
|
||
|
#include <trace/events/power.h>
|
||
|
#include <soc/samsung/exynos-ssld.h>
|
||
|
|
||
|
enum s2r_stage {
|
||
|
ePM_RUNNING = 0,
|
||
|
ePM_SUSPEND_PREPARE,
|
||
|
ePM_DEV_SUSPEND_PREPARE,
|
||
|
ePM_DEV_SUSPEND_NOIRQ,
|
||
|
ePM_DEV_RESUME_NOIRQ,
|
||
|
ePM_DEV_RESUME_COMPLETE,
|
||
|
ePM_POST_SUSPEND,
|
||
|
ePM_CNT,
|
||
|
};
|
||
|
|
||
|
struct ssld_descriptor {
|
||
|
enum s2r_stage s2r_stage;
|
||
|
const char *s2r_stage_name[ePM_CNT];
|
||
|
struct timer_list timer;
|
||
|
struct task_struct *s2r_leading_tsk;
|
||
|
u64 pm_prepare_jiffies;
|
||
|
struct s2r_trace_info last_info;
|
||
|
struct notifier_block pre_s2r_lockup_detector_pm_nb;
|
||
|
struct notifier_block post_s2r_lockup_detector_pm_nb;
|
||
|
size_t panic_msg_offset;
|
||
|
char panic_msg[1024];
|
||
|
};
|
||
|
|
||
|
static unsigned int threshold = 25;
|
||
|
module_param(threshold, uint, 0644);
|
||
|
|
||
|
ATOMIC_NOTIFIER_HEAD(ssld_notifier_list);
|
||
|
|
||
|
void ssld_notifier_chain_register(struct notifier_block *nb)
|
||
|
{
|
||
|
atomic_notifier_chain_register(&ssld_notifier_list, nb);
|
||
|
}
|
||
|
EXPORT_SYMBOL_GPL(ssld_notifier_chain_register);
|
||
|
|
||
|
static int __maybe_unused sec_from_saved_jiffies(u64 from)
|
||
|
{
|
||
|
if (from > jiffies)
|
||
|
return 0;
|
||
|
else
|
||
|
return (jiffies - from) / HZ;
|
||
|
}
|
||
|
|
||
|
static int s2r_lockup_detector_prepare(struct device *dev)
|
||
|
{
|
||
|
struct ssld_descriptor *desc;
|
||
|
|
||
|
desc = dev_get_drvdata(dev);
|
||
|
desc->s2r_stage = ePM_DEV_SUSPEND_PREPARE;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int s2r_lockup_detector_suspend_noirq(struct device *dev)
|
||
|
{
|
||
|
struct ssld_descriptor *desc;
|
||
|
|
||
|
desc = dev_get_drvdata(dev);
|
||
|
desc->s2r_stage = ePM_DEV_SUSPEND_NOIRQ;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static int s2r_lockup_detector_resume_noirq(struct device *dev)
|
||
|
{
|
||
|
struct ssld_descriptor *desc;
|
||
|
|
||
|
desc = dev_get_drvdata(dev);
|
||
|
desc->s2r_stage = ePM_DEV_RESUME_NOIRQ;
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
static void s2r_lockup_detector_complete(struct device *dev)
|
||
|
{
|
||
|
struct ssld_descriptor *desc;
|
||
|
|
||
|
desc = dev_get_drvdata(dev);
|
||
|
desc->s2r_stage = ePM_DEV_RESUME_COMPLETE;
|
||
|
}
|
||
|
|
||
|
static const struct dev_pm_ops __maybe_unused s2r_lockup_detector_pm_ops = {
|
||
|
.prepare = s2r_lockup_detector_prepare,
|
||
|
.suspend_noirq = s2r_lockup_detector_suspend_noirq,
|
||
|
.resume_noirq = s2r_lockup_detector_resume_noirq,
|
||
|
.complete = s2r_lockup_detector_complete,
|
||
|
};
|
||
|
|
||
|
static int pre_s2r_lockup_detector_pm_notifier(struct notifier_block *notifier,
|
||
|
unsigned long pm_event, void *v)
|
||
|
{
|
||
|
struct ssld_descriptor *desc = container_of(notifier, struct ssld_descriptor,
|
||
|
pre_s2r_lockup_detector_pm_nb);
|
||
|
|
||
|
switch (pm_event) {
|
||
|
case PM_SUSPEND_PREPARE:
|
||
|
desc->s2r_stage = ePM_SUSPEND_PREPARE;
|
||
|
desc->pm_prepare_jiffies = jiffies;
|
||
|
desc->s2r_leading_tsk = current;
|
||
|
if (timer_pending(&desc->timer)) {
|
||
|
mod_timer(&desc->timer, jiffies + HZ * threshold);
|
||
|
} else {
|
||
|
desc->timer.expires = jiffies + HZ * threshold;
|
||
|
add_timer(&desc->timer);
|
||
|
}
|
||
|
break;
|
||
|
case PM_POST_SUSPEND:
|
||
|
if (desc->s2r_stage != ePM_DEV_RESUME_COMPLETE) {
|
||
|
del_timer_sync(&desc->timer);
|
||
|
desc->s2r_stage = ePM_RUNNING;
|
||
|
}
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return NOTIFY_OK;
|
||
|
}
|
||
|
|
||
|
static int post_s2r_lockup_detector_pm_notifier(struct notifier_block *notifier,
|
||
|
unsigned long pm_event, void *v)
|
||
|
{
|
||
|
struct ssld_descriptor *desc = container_of(notifier, struct ssld_descriptor,
|
||
|
post_s2r_lockup_detector_pm_nb);
|
||
|
|
||
|
switch (pm_event) {
|
||
|
case PM_POST_SUSPEND:
|
||
|
if (timer_pending(&desc->timer))
|
||
|
del_timer_sync(&desc->timer);
|
||
|
desc->s2r_stage = ePM_RUNNING;
|
||
|
break;
|
||
|
default:
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
return NOTIFY_OK;
|
||
|
}
|
||
|
|
||
|
static void print_log_to_buffer(struct ssld_descriptor *desc, const char *fmt, ...)
|
||
|
{
|
||
|
va_list args;
|
||
|
|
||
|
if (desc->panic_msg_offset >= (sizeof(desc->panic_msg) - 1)) {
|
||
|
pr_emerg("%s: buffer is full\n", __func__);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
va_start(args, fmt);
|
||
|
desc->panic_msg_offset += vscnprintf(desc->panic_msg + desc->panic_msg_offset,
|
||
|
sizeof(desc->panic_msg) - desc->panic_msg_offset,
|
||
|
fmt, args);
|
||
|
va_end(args);
|
||
|
}
|
||
|
|
||
|
static void print_last_suspend_resume_info(struct ssld_descriptor *desc)
|
||
|
{
|
||
|
struct s2r_trace_info *info = &desc->last_info;
|
||
|
|
||
|
if (info->start) {
|
||
|
if (!!info->dev) {
|
||
|
print_log_to_buffer(desc, "DPM dev timeout[%s/%s],",
|
||
|
dev_driver_string(info->dev), dev_name(info->dev));
|
||
|
print_log_to_buffer(desc, "time = %lu usec|pm_ops(%s, %d)",
|
||
|
info->time / 1000, info->pm_ops, info->event);
|
||
|
} else {
|
||
|
print_log_to_buffer(desc, "S2R timeout,");
|
||
|
print_log_to_buffer(desc, "time = %lu usec|action(%s, %d)",
|
||
|
info->time / 1000, info->action, info->val);
|
||
|
}
|
||
|
} else {
|
||
|
print_log_to_buffer(desc, "Not DPM callback,");
|
||
|
if (!!info->dev) {
|
||
|
print_log_to_buffer(desc, "last DPM dev[%s/%s](%d) time = %lu usec",
|
||
|
dev_driver_string(info->dev), dev_name(info->dev),
|
||
|
info->error, info->time / 1000);
|
||
|
} else {
|
||
|
print_log_to_buffer(desc, "last pm info(%s, %d) time = %lu usec",
|
||
|
info->action, info->val, info->time / 1000);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void s2r_lockup_detector_handler(struct timer_list *t)
|
||
|
{
|
||
|
struct ssld_descriptor *desc = container_of(t, struct ssld_descriptor, timer);
|
||
|
|
||
|
if (desc->s2r_stage > ePM_RUNNING && desc->s2r_stage < ePM_POST_SUSPEND) {
|
||
|
struct ssld_notifier_data nb_data;
|
||
|
|
||
|
pr_emerg("Suspend/Resume hang between %s and %s\n",
|
||
|
desc->s2r_stage_name[desc->s2r_stage],
|
||
|
desc->s2r_stage_name[desc->s2r_stage + 1]);
|
||
|
pr_emerg("curr jiffies(%lu) pm_prepare_jiffies (%lu) expires jiffies(%lu)\n",
|
||
|
jiffies, desc->pm_prepare_jiffies, t->expires);
|
||
|
pr_emerg("Suspend/Resume hang detected\n");
|
||
|
print_log_to_buffer(desc, "SSLD:");
|
||
|
print_last_suspend_resume_info(desc);
|
||
|
print_log_to_buffer(desc, "[%s, %d]",
|
||
|
desc->s2r_leading_tsk->comm, desc->s2r_leading_tsk->pid);
|
||
|
|
||
|
nb_data.s2r_leading_tsk = desc->s2r_leading_tsk;
|
||
|
nb_data.last_info = &desc->last_info;
|
||
|
nb_data.pm_prepare_jiffies = desc->pm_prepare_jiffies;
|
||
|
atomic_notifier_call_chain(&ssld_notifier_list, 0, &nb_data);
|
||
|
|
||
|
panic("%s", desc->panic_msg);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
static void ssld_suspend_resume(void *data, const char *action, int val, bool start)
|
||
|
{
|
||
|
struct ssld_descriptor *desc = (struct ssld_descriptor *)data;
|
||
|
|
||
|
desc->last_info.time = local_clock();
|
||
|
desc->last_info.dev = NULL;
|
||
|
desc->last_info.action = action;
|
||
|
desc->last_info.val = val;
|
||
|
desc->last_info.start = start;
|
||
|
}
|
||
|
|
||
|
static void ssld_dev_pm_cb_start(void *data, struct device *dev, const char *pm_ops, int event)
|
||
|
{
|
||
|
struct ssld_descriptor *desc = (struct ssld_descriptor *)data;
|
||
|
|
||
|
desc->last_info.time = local_clock();
|
||
|
desc->last_info.dev = dev;
|
||
|
desc->last_info.pm_ops = pm_ops;
|
||
|
desc->last_info.event = event;
|
||
|
desc->last_info.start = true;
|
||
|
}
|
||
|
|
||
|
static void ssld_dev_pm_cb_end(void *data, struct device *dev, int error)
|
||
|
{
|
||
|
struct ssld_descriptor *desc = (struct ssld_descriptor *)data;
|
||
|
|
||
|
desc->last_info.time = local_clock();
|
||
|
desc->last_info.dev = dev;
|
||
|
desc->last_info.pm_ops = NULL;
|
||
|
desc->last_info.error = error;
|
||
|
desc->last_info.start = false;
|
||
|
}
|
||
|
|
||
|
static void s2r_lockup_detector_resource_init(struct ssld_descriptor *desc)
|
||
|
{
|
||
|
desc->s2r_stage_name[ePM_RUNNING] = "RUNNING";
|
||
|
desc->s2r_stage_name[ePM_SUSPEND_PREPARE] = "PM_SUSPEND_PREPARE";
|
||
|
desc->s2r_stage_name[ePM_DEV_SUSPEND_PREPARE] = "DEV_SUSPEND_PREPARE";
|
||
|
desc->s2r_stage_name[ePM_DEV_SUSPEND_NOIRQ] = "DEV_SUSPEND_NOIRQ";
|
||
|
desc->s2r_stage_name[ePM_DEV_RESUME_NOIRQ] = "DEV_RESUME_NOIRQ";
|
||
|
desc->s2r_stage_name[ePM_DEV_RESUME_COMPLETE] = "DEV_RESUME_COMPLETE";
|
||
|
desc->s2r_stage_name[ePM_POST_SUSPEND] = "PM_POST_SUSPEND";
|
||
|
|
||
|
desc->pre_s2r_lockup_detector_pm_nb.notifier_call = pre_s2r_lockup_detector_pm_notifier;
|
||
|
desc->pre_s2r_lockup_detector_pm_nb.priority = INT_MAX;
|
||
|
register_pm_notifier(&desc->pre_s2r_lockup_detector_pm_nb);
|
||
|
|
||
|
desc->post_s2r_lockup_detector_pm_nb.notifier_call = post_s2r_lockup_detector_pm_notifier;
|
||
|
desc->post_s2r_lockup_detector_pm_nb.priority = INT_MIN;
|
||
|
register_pm_notifier(&desc->post_s2r_lockup_detector_pm_nb);
|
||
|
|
||
|
desc->s2r_stage = ePM_RUNNING;
|
||
|
timer_setup(&desc->timer, s2r_lockup_detector_handler, 0);
|
||
|
|
||
|
register_trace_suspend_resume(ssld_suspend_resume, desc);
|
||
|
register_trace_device_pm_callback_start(ssld_dev_pm_cb_start, desc);
|
||
|
register_trace_device_pm_callback_end(ssld_dev_pm_cb_end, desc);
|
||
|
}
|
||
|
|
||
|
static struct bus_type ssld_bus_type = {
|
||
|
.name = "ssld",
|
||
|
};
|
||
|
|
||
|
static struct device_driver s2r_lockup_detector_driver = {
|
||
|
.name = "ssld_drv",
|
||
|
.bus = &ssld_bus_type,
|
||
|
.pm = &s2r_lockup_detector_pm_ops,
|
||
|
};
|
||
|
|
||
|
static struct device s2r_lockup_detector_device = {
|
||
|
.init_name = "ssld_dev",
|
||
|
.bus = &ssld_bus_type,
|
||
|
};
|
||
|
|
||
|
static int __init s2r_lockup_detector_driver_init(void)
|
||
|
{
|
||
|
struct ssld_descriptor *ssld_desc;
|
||
|
int ret;
|
||
|
|
||
|
ret = bus_register(&ssld_bus_type);
|
||
|
if (ret)
|
||
|
goto bus_register_err;
|
||
|
|
||
|
ret = driver_register(&s2r_lockup_detector_driver);
|
||
|
if (ret < 0)
|
||
|
goto driver_register_err;
|
||
|
|
||
|
ret = device_register(&s2r_lockup_detector_device);
|
||
|
if (ret < 0)
|
||
|
goto device_register_err;
|
||
|
|
||
|
ssld_desc = devm_kzalloc(&s2r_lockup_detector_device,
|
||
|
sizeof(struct ssld_descriptor), GFP_KERNEL);
|
||
|
if (!ssld_desc) {
|
||
|
ret = -ENOMEM;
|
||
|
goto alloc_fail;
|
||
|
}
|
||
|
|
||
|
dev_set_drvdata(&s2r_lockup_detector_device, ssld_desc);
|
||
|
s2r_lockup_detector_resource_init(ssld_desc);
|
||
|
pr_info("s2r lockup detector is enabled(threshold = %u)\n", threshold);
|
||
|
return 0;
|
||
|
|
||
|
alloc_fail:
|
||
|
device_unregister(&s2r_lockup_detector_device);
|
||
|
device_register_err:
|
||
|
driver_unregister(&s2r_lockup_detector_driver);
|
||
|
driver_register_err:
|
||
|
bus_unregister(&ssld_bus_type);
|
||
|
bus_register_err:
|
||
|
return ret;
|
||
|
}
|
||
|
|
||
|
static void __exit s2r_lockup_detector_driver_exit(void)
|
||
|
{
|
||
|
struct ssld_descriptor *desc = dev_get_drvdata(&s2r_lockup_detector_device);
|
||
|
|
||
|
unregister_trace_suspend_resume(ssld_suspend_resume, desc);
|
||
|
unregister_trace_device_pm_callback_start(ssld_dev_pm_cb_start, desc);
|
||
|
unregister_trace_device_pm_callback_end(ssld_dev_pm_cb_end, desc);
|
||
|
|
||
|
unregister_pm_notifier(&desc->pre_s2r_lockup_detector_pm_nb);
|
||
|
unregister_pm_notifier(&desc->post_s2r_lockup_detector_pm_nb);
|
||
|
del_timer(&desc->timer);
|
||
|
device_unregister(&s2r_lockup_detector_device);
|
||
|
driver_unregister(&s2r_lockup_detector_driver);
|
||
|
bus_unregister(&ssld_bus_type);
|
||
|
}
|
||
|
|
||
|
module_init(s2r_lockup_detector_driver_init);
|
||
|
module_exit(s2r_lockup_detector_driver_exit);
|
||
|
|
||
|
MODULE_DESCRIPTION("Samsung Exynos S2R Scenario Lockup Detector");
|
||
|
MODULE_LICENSE("GPL v2");
|