// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2011-2021, The Linux Foundation. All rights reserved.
 */

#define pr_fmt(fmt) "subsys-restart: %s(): " fmt, __func__

#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include <linux/list.h>
#include <linux/io.h>
#include <linux/kthread.h>
#include <linux/time.h>
#include <linux/suspend.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/device.h>
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/cdev.h>
#include <linux/platform_device.h>
#include <soc/qcom/subsystem_restart.h>
#include <soc/qcom/subsystem_notif.h>
#include <soc/qcom/sysmon.h>
#include <trace/events/trace_msm_pil_event.h>
#include <linux/soc/qcom/smem_state.h>
#include <linux/of_irq.h>
#include <linux/of.h>
#include <asm/current.h>
#include <linux/timer.h>
#include <linux/seq_file.h>
#include <linux/proc_fs.h>

#include "peripheral-loader.h"
#ifdef OPLUS_BUG_STABILITY
/*Add for disable dump for subsys crash*/
#include <soc/oplus/system/oplus_project.h>
extern bool oem_is_fulldump(void);
bool delay_panic = false;
#endif
#ifdef OPLUS_BUG_STABILITY
bool direct_panic = false;
#endif

#define DISABLE_SSR 0x9889deed
/* If set to 0x9889deed, call to subsystem_restart_dev() returns immediately */
static uint disable_restart_work;
module_param(disable_restart_work, uint, 0644);

static int enable_debug;
module_param(enable_debug, int, 0644);
//#ifdef OPLUS_FEATURE_SENSOR
static DEFINE_MUTEX(subsys_list_lock);
//#endif

/* The maximum shutdown timeout is the product of MAX_LOOPS and DELAY_MS. */
#define SHUTDOWN_ACK_MAX_LOOPS	100
#define SHUTDOWN_ACK_DELAY_MS	100

#ifdef CONFIG_SETUP_SSR_NOTIF_TIMEOUTS
/* Timeout used for detection of notification hangs. In seconds.*/
#define SYSMON_COMM_TIMEOUT	CONFIG_SSR_SYSMON_NOTIF_TIMEOUT
#define SUBSYS_NOTIF_TIMEOUT	CONFIG_SSR_SUBSYS_NOTIF_TIMEOUT

#define setup_timeout(dest_ss, source_ss, comm_type) \
	_setup_timeout(dest_ss, source_ss, comm_type)
#define cancel_timeout(subsys) del_timer_sync(&subsys->timeout_data.timer)
#define init_subsys_timer(subsys) _init_subsys_timer(subsys)

/* Timeout values */
static unsigned long timeout_vals[NUM_SSR_COMMS] = {
	[SUBSYS_TO_SUBSYS_SYSMON] = SYSMON_COMM_TIMEOUT,
	[SUBSYS_TO_HLOS] = SUBSYS_NOTIF_TIMEOUT,
	[HLOS_TO_SUBSYS_SYSMON_SHUTDOWN] = SYSMON_COMM_TIMEOUT,
};

#ifdef CONFIG_PANIC_ON_SSR_NOTIF_TIMEOUT
#define SSR_NOTIF_TIMEOUT_WARN(fmt...) panic(fmt)
#else /* CONFIG_PANIC_ON_SSR_NOTIF_TIMEOUT */
#define SSR_NOTIF_TIMEOUT_WARN(fmt...) WARN(1, fmt)
#endif /* CONFIG_PANIC_ON_SSR_NOTIF_TIMEOUT */

#else /* CONFIG_SETUP_SSR_NOTIF_TIMEOUTS */
#define setup_timeout(dest_ss, source_ss, sysmon_comm)
#define cancel_timeout(subsys)
#define init_subsys_timer(subsys)
#endif /* CONFIG_SETUP_SSR_NOTIF_TIMEOUTS */

/**
 * enum p_subsys_state - state of a subsystem (private)
 * @SUBSYS_NORMAL: subsystem is operating normally
 * @SUBSYS_CRASHED: subsystem has crashed and hasn't been shutdown
 * @SUBSYS_RESTARTING: subsystem has been shutdown and is now restarting
 *
 * The 'private' side of the subsytem state used to determine where in the
 * restart process the subsystem is.
 */
enum p_subsys_state {
	SUBSYS_NORMAL,
	SUBSYS_CRASHED,
	SUBSYS_RESTARTING,
};

/**
 * enum subsys_state - state of a subsystem (public)
 * @SUBSYS_OFFLINING: subsystem is offlining
 * @SUBSYS_OFFLINE: subsystem is offline
 * @SUBSYS_ONLINE: subsystem is online
 *
 * The 'public' side of the subsytem state, exposed to userspace.
 */
enum subsys_state {
	SUBSYS_OFFLINING,
	SUBSYS_OFFLINE,
	SUBSYS_ONLINE,
};

static const char * const subsys_states[] = {
	[SUBSYS_OFFLINING] = "OFFLINING",
	[SUBSYS_OFFLINE] = "OFFLINE",
	[SUBSYS_ONLINE] = "ONLINE",
};

static const char * const restart_levels[] = {
	[RESET_SOC] = "SYSTEM",
	[RESET_SUBSYS_COUPLED] = "RELATED",
};

/**
 * struct subsys_tracking - track state of a subsystem or restart order
 * @p_state: private state of subsystem/order
 * @state: public state of subsystem/order
 * @s_lock: protects p_state
 * @lock: protects subsystem/order callbacks and state
 *
 * Tracks the state of a subsystem or a set of subsystems (restart order).
 * Doing this avoids the need to grab each subsystem's lock and update
 * each subsystems state when restarting an order.
 */
struct subsys_tracking {
	enum p_subsys_state p_state;
	spinlock_t s_lock;
	enum subsys_state state;
	struct mutex lock;
};

/**
 * struct subsys_soc_restart_order - subsystem restart order
 * @subsystem_list: names of subsystems in this restart order
 * @count: number of subsystems in order
 * @track: state tracking and locking
 * @subsys_ptrs: pointers to subsystems in this restart order
 */
struct subsys_soc_restart_order {
	struct device_node **device_ptrs;
	int count;

	struct subsys_tracking track;
	struct subsys_device **subsys_ptrs;
	struct list_head list;
};

struct restart_log {
	struct timeval time;
	struct subsys_device *dev;
	struct list_head list;
};

/**
 * struct subsys_device - subsystem device
 * @desc: subsystem descriptor
 * @work: context for subsystem_restart_wq_func() for this device
 * @ssr_wlock: prevents suspend during subsystem_restart()
 * @wlname: name of wakeup source
 * @device_restart_work: work struct for device restart
 * @track: state tracking and locking
 * @notify: subsys notify handle
 * @dev: device
 * @owner: module that provides @desc
 * @count: reference count of subsystem_get()/subsystem_put()
 * @id: ida
 * @restart_level: restart level (0 - panic, 1 - related, 2 - independent, etc.)
 * @restart_order: order of other devices this devices restarts with
 * @crash_count: number of times the device has crashed
 * @do_ramdump_on_put: ramdump on subsystem_put() if true
 * @err_ready: completion variable to record error ready from subsystem
 * @crashed: indicates if subsystem has crashed
 * @notif_state: current state of subsystem in terms of subsys notifications
 */
struct subsys_device {
	struct subsys_desc *desc;
	struct work_struct work;
	struct wakeup_source *ssr_wlock;
	char wlname[64];
	struct work_struct device_restart_work;
	struct subsys_tracking track;

	void *notify;
	void *early_notify;
	struct device dev;
	struct module *owner;
	int count;
	int id;
	int restart_level;
	int crash_count;
	struct subsys_soc_restart_order *restart_order;
	bool do_ramdump_on_put;
	struct cdev char_dev;
	dev_t dev_no;
	struct completion err_ready;
	struct completion shutdown_ack;
	enum crash_status crashed;
	int notif_state;
	struct list_head list;
};

#ifdef OPLUS_FEATURE_ADSP_RECOVERY
static bool oplus_adsp_ssr = false;

void oplus_set_ssr_state(bool ssr_state)
{
	oplus_adsp_ssr = ssr_state;
	pr_err("%s():oplus_adsp_ssr=%d\n", __func__, oplus_adsp_ssr);

}
EXPORT_SYMBOL(oplus_set_ssr_state);

bool oplus_get_ssr_state(void)
{
	pr_err("%s():oplus_adsp_ssr=%d\n", __func__, oplus_adsp_ssr);
	return oplus_adsp_ssr;
}
EXPORT_SYMBOL(oplus_get_ssr_state);
#endif /* OPLUS_FEATURE_ADSP_RECOVERY */

static struct subsys_device *to_subsys(struct device *d)
{
	return container_of(d, struct subsys_device, dev);
}

void complete_err_ready(struct subsys_device *subsys)
{
	complete(&subsys->err_ready);
}

void complete_shutdown_ack(struct subsys_device *subsys)
{
	complete(&subsys->shutdown_ack);
}

static struct subsys_tracking *subsys_get_track(struct subsys_device *subsys)
{
	struct subsys_soc_restart_order *order = subsys->restart_order;

	if (order)
		return &order->track;
	else
		return &subsys->track;
}

static ssize_t name_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", to_subsys(dev)->desc->name);
}
static DEVICE_ATTR_RO(name);

static ssize_t state_show(struct device *dev, struct device_attribute *attr,
		char *buf)
{
	enum subsys_state state = to_subsys(dev)->track.state;

	return snprintf(buf, PAGE_SIZE, "%s\n", subsys_states[state]);
}
static DEVICE_ATTR_RO(state);

static ssize_t crash_count_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%d\n", to_subsys(dev)->crash_count);
}
static DEVICE_ATTR_RO(crash_count);

static ssize_t
restart_level_show(struct device *dev, struct device_attribute *attr, char *buf)
{
	int level = to_subsys(dev)->restart_level;

	return snprintf(buf, PAGE_SIZE, "%s\n", restart_levels[level]);
}

static ssize_t restart_level_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct subsys_device *subsys = to_subsys(dev);
	const char *p;
	int i, orig_count = count;

	p = memchr(buf, '\n', count);
	if (p)
		count = p - buf;

	for (i = 0; i < ARRAY_SIZE(restart_levels); i++)
		if (!strncasecmp(buf, restart_levels[i], count)) {
			pil_ipc("[%s]: change restart level to %d\n",
				subsys->desc->name, i);
                        //#ifdef OPLUS_BUG_STABILITY
			if(PREVERSION == get_eng_version()){
				pr_info("preversion \n");
				subsys->restart_level = RESET_SUBSYS_COUPLED;
			} else {
				subsys->restart_level = i;
			}
                        //#else
                        //subsys->restart_level = i;
                        //#endif /*OPLUS_BUG_STABILITY*/
			return orig_count;
		}
	return -EPERM;
}
static DEVICE_ATTR_RW(restart_level);

static ssize_t firmware_name_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	return snprintf(buf, PAGE_SIZE, "%s\n", to_subsys(dev)->desc->fw_name);
}

static ssize_t firmware_name_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	struct subsys_device *subsys = to_subsys(dev);
	struct subsys_tracking *track = subsys_get_track(subsys);
	const char *p;
	int orig_count = count;

	p = memchr(buf, '\n', count);
	if (p)
		count = p - buf;

	pr_info("Changing subsys fw_name to %s\n", buf);
	mutex_lock(&track->lock);
	strlcpy(subsys->desc->fw_name, buf,
			min(count + 1, sizeof(subsys->desc->fw_name)));
	mutex_unlock(&track->lock);
	return orig_count;
}
static DEVICE_ATTR_RW(firmware_name);

static ssize_t system_debug_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	struct subsys_device *subsys = to_subsys(dev);
	char p[6] = "set";

	if (!subsys->desc->system_debug)
		strlcpy(p, "reset", sizeof(p));

	return snprintf(buf, PAGE_SIZE, "%s\n", p);
}

static ssize_t system_debug_store(struct device *dev,
				struct device_attribute *attr, const char *buf,
				size_t count)
{
	struct subsys_device *subsys = to_subsys(dev);
	const char *p;
	int orig_count = count;

	p = memchr(buf, '\n', count);
	if (p)
		count = p - buf;

	if (!strncasecmp(buf, "set", count))
		subsys->desc->system_debug = true;
	else if (!strncasecmp(buf, "reset", count))
		subsys->desc->system_debug = false;
	else
		return -EPERM;
	return orig_count;
}
static DEVICE_ATTR_RW(system_debug);
//#ifdef OPLUS_FEATURE_SENSOR
#define CRASH_CAUSE_BUF_LEN 128
char crash_case_buf[CRASH_CAUSE_BUF_LEN] = {0};

void set_subsys_crash_cause(char *str){
	int len = 0;
	len = strlen(str);
	if(len >= CRASH_CAUSE_BUF_LEN)
		len = CRASH_CAUSE_BUF_LEN -1;
	//mutex_lock(&subsys_list_lock);
	memcpy(crash_case_buf, str, len);
	crash_case_buf[len] = '\0';
	//mutex_unlock(&subsys_list_lock);
}
EXPORT_SYMBOL(set_subsys_crash_cause);

static ssize_t crash_cause_show(struct device *dev,
				struct device_attribute *attr, char *buf)
{
	int ret = 0;
	mutex_lock(&subsys_list_lock);
	ret = snprintf(buf, PAGE_SIZE, "%s\n", crash_case_buf);
	mutex_unlock(&subsys_list_lock);
	return ret;
}

static ssize_t crash_cause_store(struct device *dev,
				struct device_attribute *attr, const char *buf,
				size_t count)
{
	return count;
}
static DEVICE_ATTR_RW(crash_cause);
//#endif
int subsys_get_restart_level(struct subsys_device *dev)
{
	return dev->restart_level;
}
EXPORT_SYMBOL(subsys_get_restart_level);

static void subsys_set_state(struct subsys_device *subsys,
			     enum subsys_state state)
{
	unsigned long flags;

	spin_lock_irqsave(&subsys->track.s_lock, flags);
	if (subsys->track.state != state) {
		subsys->track.state = state;
		spin_unlock_irqrestore(&subsys->track.s_lock, flags);
		sysfs_notify(&subsys->dev.kobj, NULL, "state");
		return;
	}
	spin_unlock_irqrestore(&subsys->track.s_lock, flags);
}

/**
 * subsytem_default_online() - Mark a subsystem as online by default
 * @dev: subsystem to mark as online
 *
 * Marks a subsystem as "online" without increasing the reference count
 * on the subsystem. This is typically used by subsystems that are already
 * online when the kernel boots up.
 */
void subsys_default_online(struct subsys_device *dev)
{
	subsys_set_state(dev, SUBSYS_ONLINE);
}
EXPORT_SYMBOL(subsys_default_online);

static struct attribute *subsys_attrs[] = {
	&dev_attr_name.attr,
	&dev_attr_state.attr,
	&dev_attr_crash_count.attr,
	&dev_attr_restart_level.attr,
	&dev_attr_firmware_name.attr,
	&dev_attr_system_debug.attr,
	//#ifdef OPLUS_FEATURE_SENSOR
	&dev_attr_crash_cause.attr,
	//#endif
	NULL,
};

ATTRIBUTE_GROUPS(subsys);

struct bus_type subsys_bus_type = {
	.name		= "msm_subsys",
	.dev_groups	= subsys_groups,
};
EXPORT_SYMBOL(subsys_bus_type);

static DEFINE_IDA(subsys_ida);

static int enable_ramdumps;
module_param(enable_ramdumps, int, 0644);

static int enable_mini_ramdumps;
module_param(enable_mini_ramdumps, int, 0644);

struct workqueue_struct *ssr_wq;
static struct class *char_class;

static LIST_HEAD(restart_log_list);
static LIST_HEAD(subsys_list);
static LIST_HEAD(ssr_order_list);
static DEFINE_MUTEX(soc_order_reg_lock);
static DEFINE_MUTEX(restart_log_mutex);
//#ifdef OPLUS_FEATURE_SENSOR
//static DEFINE_MUTEX(subsys_list_lock);
//#endif
static DEFINE_MUTEX(char_device_lock);
static DEFINE_MUTEX(ssr_order_mutex);

static struct subsys_soc_restart_order *
update_restart_order(struct subsys_device *dev)
{
	int i;
	struct subsys_soc_restart_order *order;
	struct device_node *device = dev->desc->dev->of_node;

	mutex_lock(&soc_order_reg_lock);
	list_for_each_entry(order, &ssr_order_list, list) {
		for (i = 0; i < order->count; i++) {
			if (order->device_ptrs[i] == device) {
				order->subsys_ptrs[i] = dev;
				goto found;
			}
		}
	}
	order = NULL;
found:
	mutex_unlock(&soc_order_reg_lock);

	return order;
}

static int max_restarts;
module_param(max_restarts, int, 0644);

static long max_history_time = 3600;
module_param(max_history_time, long, 0644);

static void do_epoch_check(struct subsys_device *dev)
{
	int n = 0;
	struct timeval *time_first = NULL, *curr_time;
	struct restart_log *r_log, *temp;
	static int max_restarts_check;
	static long max_history_time_check;

	mutex_lock(&restart_log_mutex);

	max_restarts_check = max_restarts;
	max_history_time_check = max_history_time;

	/* Check if epoch checking is enabled */
	if (!max_restarts_check)
		goto out;

	r_log = kmalloc(sizeof(struct restart_log), GFP_KERNEL);
	if (!r_log)
		goto out;
	r_log->dev = dev;
	do_gettimeofday(&r_log->time);
	curr_time = &r_log->time;
	INIT_LIST_HEAD(&r_log->list);

	list_add_tail(&r_log->list, &restart_log_list);

	list_for_each_entry_safe(r_log, temp, &restart_log_list, list) {

		if ((curr_time->tv_sec - r_log->time.tv_sec) >
				max_history_time_check) {

			pr_debug("Deleted node with restart_time = %ld\n",
					r_log->time.tv_sec);
			list_del(&r_log->list);
			kfree(r_log);
			continue;
		}
		if (!n) {
			time_first = &r_log->time;
			pr_debug("Time_first: %ld\n", time_first->tv_sec);
		}
		n++;
		pr_debug("Restart_time: %ld\n", r_log->time.tv_sec);
	}

	if (time_first && n >= max_restarts_check) {
		if ((curr_time->tv_sec - time_first->tv_sec) <
				max_history_time_check)
			panic("Subsystems have crashed %d times in less than %ld seconds!",
				max_restarts_check, max_history_time_check);
	}

out:
	mutex_unlock(&restart_log_mutex);
}

static int is_ramdump_enabled(struct subsys_device *dev)
{
	if (dev->desc->ramdump_disable_irq)
		return !dev->desc->ramdump_disable;

	return enable_ramdumps;
}

#ifdef CONFIG_SETUP_SSR_NOTIF_TIMEOUTS
static void notif_timeout_handler(struct timer_list *t)
{
	char *sysmon_msg = "Sysmon communication from %s to %s taking too long";
	char *subsys_notif_msg = "Subsys notifier chain for %s taking too long";
	char *sysmon_shutdwn_msg = "sysmon_send_shutdown to %s taking too long";
	char *unknown_err_msg = "Unknown communication occurred";
	struct subsys_notif_timeout *timeout_data =
		from_timer(timeout_data, t, timer);
	enum ssr_comm comm_type = timeout_data->comm_type;

	switch (comm_type) {
	case SUBSYS_TO_SUBSYS_SYSMON:
		SSR_NOTIF_TIMEOUT_WARN(sysmon_msg, timeout_data->source_name,
				       timeout_data->dest_name);
		break;
	case SUBSYS_TO_HLOS:
		SSR_NOTIF_TIMEOUT_WARN(subsys_notif_msg,
				       timeout_data->source_name);
		break;
	case HLOS_TO_SUBSYS_SYSMON_SHUTDOWN:
		SSR_NOTIF_TIMEOUT_WARN(sysmon_shutdwn_msg,
				       timeout_data->dest_name);
		break;
	default:
		SSR_NOTIF_TIMEOUT_WARN(unknown_err_msg);
	}

}

static void _setup_timeout(struct subsys_desc *source_ss,
			   struct subsys_desc *dest_ss, enum ssr_comm comm_type)
{
	struct subsys_notif_timeout *timeout_data;
	unsigned long timeout;

	switch (comm_type) {
	case SUBSYS_TO_SUBSYS_SYSMON:
		timeout_data = &source_ss->timeout_data;
		timeout_data->dest_name = dest_ss->name;
		timeout_data->source_name = source_ss->name;
		break;
	case SUBSYS_TO_HLOS:
		timeout_data = &source_ss->timeout_data;
		timeout_data->dest_name = NULL;
		timeout_data->source_name = source_ss->name;
		break;
	case HLOS_TO_SUBSYS_SYSMON_SHUTDOWN:
		timeout_data = &dest_ss->timeout_data;
		timeout_data->dest_name = dest_ss->name;
		timeout_data->source_name = NULL;
		break;
	default:
		return;
	}

	timeout_data->comm_type = comm_type;
	timeout = jiffies + msecs_to_jiffies(timeout_vals[comm_type]);
	mod_timer(&timeout_data->timer, timeout);
}

static void _init_subsys_timer(struct subsys_desc *subsys)
{
	timer_setup(&subsys->timeout_data.timer, notif_timeout_handler, 0);
}

#endif /* CONFIG_SETUP_SSR_NOTIF_TIMEOUTS */

static void send_sysmon_notif(struct subsys_device *dev)
{
	struct subsys_device *subsys;

	mutex_lock(&subsys_list_lock);
	list_for_each_entry(subsys, &subsys_list, list)
		if ((subsys->notif_state > 0) && (subsys != dev)) {
			setup_timeout(subsys->desc, dev->desc,
				      SUBSYS_TO_SUBSYS_SYSMON);
			sysmon_send_event(dev->desc, subsys->desc,
						subsys->notif_state);
			cancel_timeout(subsys->desc);
		}
	mutex_unlock(&subsys_list_lock);
}

static int for_each_subsys_device(struct subsys_device **list,
		unsigned int count, void *data,
		int (*fn)(struct subsys_device *, void *))
{
	int ret;

	while (count--) {
		struct subsys_device *dev = *list++;

		if (!dev)
			continue;
		ret = fn(dev, data);
		if (ret)
			return ret;
	}
	return 0;
}

static void notify_each_subsys_device(struct subsys_device **list,
		unsigned int count,
		enum subsys_notif_type notif, void *data)
{
	struct subsys_device *subsys;

	while (count--) {
		struct subsys_device *dev = *list++;
		struct notif_data notif_data;
		struct platform_device *pdev;

		if (!dev)
			continue;

		pdev = container_of(dev->desc->dev, struct platform_device,
									dev);
		dev->notif_state = notif;

		mutex_lock(&subsys_list_lock);
		list_for_each_entry(subsys, &subsys_list, list)
			if (dev != subsys &&
				subsys->track.state == SUBSYS_ONLINE) {
				setup_timeout(dev->desc, subsys->desc,
					      SUBSYS_TO_SUBSYS_SYSMON);
				sysmon_send_event(subsys->desc, dev->desc,
						  notif);
				cancel_timeout(dev->desc);
			}
		mutex_unlock(&subsys_list_lock);

		if (notif == SUBSYS_AFTER_POWERUP &&
				dev->track.state == SUBSYS_ONLINE)
			send_sysmon_notif(dev);

		notif_data.crashed = subsys_get_crash_status(dev);
		notif_data.enable_ramdump = is_ramdump_enabled(dev);
		notif_data.enable_mini_ramdumps = enable_mini_ramdumps;
		notif_data.no_auth = dev->desc->no_auth;
		notif_data.pdev = pdev;

		trace_pil_notif("before_send_notif", notif, dev->desc->fw_name);
		setup_timeout(dev->desc, NULL, SUBSYS_TO_HLOS);
		subsys_notif_queue_notification(dev->notify, notif,
								&notif_data);
		cancel_timeout(dev->desc);
		trace_pil_notif("after_send_notif", notif, dev->desc->fw_name);
	}
}

static void enable_all_irqs(struct subsys_device *dev)
{
	if (dev->desc->err_ready_irq)
		enable_irq(dev->desc->err_ready_irq);
	if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) {
		enable_irq(dev->desc->wdog_bite_irq);
		irq_set_irq_wake(dev->desc->wdog_bite_irq, 1);
	}
	if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
		enable_irq(dev->desc->err_fatal_irq);
	if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler)
		enable_irq(dev->desc->stop_ack_irq);
	if (dev->desc->shutdown_ack_irq && dev->desc->shutdown_ack_handler)
		enable_irq(dev->desc->shutdown_ack_irq);
	if (dev->desc->ramdump_disable_irq &&
			dev->desc->ramdump_disable_handler)
		enable_irq(dev->desc->ramdump_disable_irq);
	if (dev->desc->generic_irq && dev->desc->generic_handler) {
		enable_irq(dev->desc->generic_irq);
		irq_set_irq_wake(dev->desc->generic_irq, 1);
	}
}

static void disable_all_irqs(struct subsys_device *dev)
{
	if (dev->desc->err_ready_irq)
		disable_irq(dev->desc->err_ready_irq);
	if (dev->desc->wdog_bite_irq && dev->desc->wdog_bite_handler) {
		disable_irq(dev->desc->wdog_bite_irq);
		irq_set_irq_wake(dev->desc->wdog_bite_irq, 0);
	}
	if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
		disable_irq(dev->desc->err_fatal_irq);
	if (dev->desc->stop_ack_irq && dev->desc->stop_ack_handler)
		disable_irq(dev->desc->stop_ack_irq);
	if (dev->desc->shutdown_ack_irq && dev->desc->shutdown_ack_handler)
		disable_irq(dev->desc->shutdown_ack_irq);
	if (dev->desc->generic_irq && dev->desc->generic_handler) {
		disable_irq(dev->desc->generic_irq);
		irq_set_irq_wake(dev->desc->generic_irq, 0);
	}
}

static int wait_for_err_ready(struct subsys_device *subsys)
{
	int ret;

	/*
	 * If subsys is using generic_irq in which case err_ready_irq will be 0,
	 * don't return.
	 */
	if ((subsys->desc->generic_irq <= 0 && !subsys->desc->err_ready_irq) ||
				enable_debug == 1 || is_timeout_disabled())
		return 0;

	ret = wait_for_completion_timeout(&subsys->err_ready,
					  msecs_to_jiffies(10000));
	if (!ret) {
		pr_err("[%s]: Error ready timed out\n", subsys->desc->name);
		return -ETIMEDOUT;
	}

	return 0;
}

static int subsystem_shutdown(struct subsys_device *dev, void *data)
{
	const char *name = dev->desc->name;
	int ret;

	pr_info("[%s:%d]: Shutting down %s\n",
			current->comm, current->pid, name);
	ret = dev->desc->shutdown(dev->desc, true);
	if (ret < 0) {
		if (!dev->desc->ignore_ssr_failure) {
			panic("subsys-restart: [%s:%d]: Failed to shutdown %s!",
				current->comm, current->pid, name);
		} else {
			pr_err("Shutdown failure on %s\n", name);
			return ret;
		}
	}
	dev->crash_count++;
	subsys_set_state(dev, SUBSYS_OFFLINE);
	disable_all_irqs(dev);

	return 0;
}

static int subsystem_ramdump(struct subsys_device *dev, void *data)
{
	const char *name = dev->desc->name;

	if (dev->desc->ramdump)
		if (dev->desc->ramdump(is_ramdump_enabled(dev), dev->desc) < 0)
			pr_warn("%s[%s:%d]: Ramdump failed.\n",
				name, current->comm, current->pid);
	dev->do_ramdump_on_put = false;
	return 0;
}

static int subsystem_free_memory(struct subsys_device *dev, void *data)
{
	if (dev->desc->free_memory)
		dev->desc->free_memory(dev->desc);
	return 0;
}

static int subsystem_powerup(struct subsys_device *dev, void *data)
{
	const char *name = dev->desc->name;
	int ret;

	pr_info("[%s:%d]: Powering up %s\n", current->comm, current->pid, name);
	reinit_completion(&dev->err_ready);

	enable_all_irqs(dev);
	ret = dev->desc->powerup(dev->desc);
	if (ret < 0) {
		notify_each_subsys_device(&dev, 1, SUBSYS_POWERUP_FAILURE,
								NULL);
		if (system_state == SYSTEM_RESTART
			|| system_state == SYSTEM_POWER_OFF)
			WARN(1, "SSR aborted: %s, system reboot/shutdown is under way\n",
				name);
		else if (!dev->desc->ignore_ssr_failure)
			panic("[%s:%d]: Powerup error: %s!",
				current->comm, current->pid, name);
		else
			pr_err("Powerup failure on %s\n", name);
		return ret;
	}

	ret = wait_for_err_ready(dev);
	if (ret) {
		notify_each_subsys_device(&dev, 1, SUBSYS_POWERUP_FAILURE,
								NULL);
		if (!dev->desc->ignore_ssr_failure)
			panic("[%s:%d]: Timed out waiting for error ready: %s!",
				current->comm, current->pid, name);
		else
			return ret;
	}
	subsys_set_state(dev, SUBSYS_ONLINE);
	subsys_set_crash_status(dev, CRASH_STATUS_NO_CRASH);

	return 0;
}

static int __find_subsys_device(struct device *dev, void *data)
{
	struct subsys_device *subsys = to_subsys(dev);

	return !strcmp(subsys->desc->name, data);
}

struct subsys_device *find_subsys_device(const char *str)
{
	struct device *dev;

	if (!str)
		return NULL;

	dev = bus_find_device(&subsys_bus_type, NULL, (void *)str,
			__find_subsys_device);
	return dev ? to_subsys(dev) : NULL;
}
EXPORT_SYMBOL(find_subsys_device);

#ifdef OPLUS_BUG_STABILITY
int op_restart_modem(struct subsys_device *subsys)
{
	int restart_level;

	if (!subsys) {
		return -ENODEV;
	}

	pr_err("%s\n", __func__);

	restart_level = subsys->restart_level;
	subsys->restart_level = RESET_SUBSYS_COUPLED;
	if (subsys->desc->force_reset) {
		subsys->desc->force_reset(subsys->desc);
	}

	subsys->restart_level = restart_level;

	return 0;
}
EXPORT_SYMBOL(op_restart_modem);
#endif /* OPLUS_BUG_STABILITY */

static int subsys_start(struct subsys_device *subsys)
{
	int ret;

	notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_POWERUP,
								NULL);

	reinit_completion(&subsys->err_ready);
	ret = subsys->desc->powerup(subsys->desc);
	if (ret) {
		notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE,
									NULL);
		return ret;
	}
	enable_all_irqs(subsys);

	if (subsys->desc->is_not_loadable) {
		subsys_set_state(subsys, SUBSYS_ONLINE);
		return 0;
	}
	pil_ipc("[%s]: before wait_for_err_ready\n", subsys->desc->name);
	ret = wait_for_err_ready(subsys);
	if (ret) {
		/* pil-boot succeeded but we need to shutdown
		 * the device because error ready timed out.
		 */
		notify_each_subsys_device(&subsys, 1, SUBSYS_POWERUP_FAILURE,
									NULL);
		subsys->desc->shutdown(subsys->desc, false);
		disable_all_irqs(subsys);
		return ret;
	}
	subsys_set_state(subsys, SUBSYS_ONLINE);

	notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_POWERUP,
								NULL);
	pil_ipc("[%s]: exit\n", subsys->desc->name);
	return ret;
}

static void subsys_stop(struct subsys_device *subsys)
{
	const char *name = subsys->desc->name;

	pil_ipc("[%s]: entry\n", subsys->desc->name);
	notify_each_subsys_device(&subsys, 1, SUBSYS_BEFORE_SHUTDOWN, NULL);
	reinit_completion(&subsys->shutdown_ack);
	if (!of_property_read_bool(subsys->desc->dev->of_node,
					"qcom,pil-force-shutdown")) {
		subsys_set_state(subsys, SUBSYS_OFFLINING);
		setup_timeout(NULL, subsys->desc,
			      HLOS_TO_SUBSYS_SYSMON_SHUTDOWN);
		subsys->desc->sysmon_shutdown_ret =
				sysmon_send_shutdown(subsys->desc);
		cancel_timeout(subsys->desc);
		if (subsys->desc->sysmon_shutdown_ret)
			pr_debug("Graceful shutdown failed for %s\n", name);
	}

	subsys->desc->shutdown(subsys->desc, false);
	subsys_set_state(subsys, SUBSYS_OFFLINE);
	disable_all_irqs(subsys);
	notify_each_subsys_device(&subsys, 1, SUBSYS_AFTER_SHUTDOWN, NULL);
	pil_ipc("[%s]: exit\n", subsys->desc->name);
}

int subsystem_set_fwname(const char *name, const char *fw_name)
{
	struct subsys_device *subsys;

	if (!name)
		return -EINVAL;

	if (!fw_name)
		return -EINVAL;

	subsys = find_subsys_device(name);
	if (!subsys)
		return -EINVAL;

	pr_debug("Changing subsys [%s] fw_name to [%s]\n", name, fw_name);
	strlcpy(subsys->desc->fw_name, fw_name,
		sizeof(subsys->desc->fw_name));

	return 0;
}
EXPORT_SYMBOL(subsystem_set_fwname);

int wait_for_shutdown_ack(struct subsys_desc *desc)
{
	int ret;
	struct subsys_device *dev;

	if (!desc)
		return 0;

	dev = find_subsys_device(desc->name);
	if (!dev)
		return 0;

	ret = wait_for_completion_timeout(&dev->shutdown_ack,
						msecs_to_jiffies(10000));
	if (!ret) {
		pr_err("[%s]: Timed out waiting for shutdown ack\n",
				desc->name);
		return -ETIMEDOUT;
	}
	return ret;
}
EXPORT_SYMBOL(wait_for_shutdown_ack);

void *__subsystem_get(const char *name, const char *fw_name)
{
	struct subsys_device *subsys;
	struct subsys_device *subsys_d;
	int ret;
	void *retval;
	struct subsys_tracking *track;

	if (!name)
		return NULL;

	subsys = retval = find_subsys_device(name);
	if (!subsys)
		return ERR_PTR(-ENODEV);
	if (!try_module_get(subsys->owner)) {
		retval = ERR_PTR(-ENODEV);
		goto err_module;
	}

	subsys_d = subsystem_get(subsys->desc->pon_depends_on);
	if (IS_ERR(subsys_d)) {
		retval = subsys_d;
		goto err_depends;
	}

	track = subsys_get_track(subsys);
	mutex_lock(&track->lock);
	if (!subsys->count) {
		if (fw_name) {
			pr_info("Changing subsys fw_name to %s\n", fw_name);
			strlcpy(subsys->desc->fw_name, fw_name,
				sizeof(subsys->desc->fw_name));
		}
		ret = subsys_start(subsys);
		if (ret) {
			retval = ERR_PTR(ret);
			goto err_start;
		}
	}
	subsys->count++;
	mutex_unlock(&track->lock);
	return retval;
err_start:
	mutex_unlock(&track->lock);
	subsystem_put(subsys_d);
err_depends:
	module_put(subsys->owner);
err_module:
	put_device(&subsys->dev);
	return retval;
}

/**
 * subsytem_get() - Boot a subsystem
 * @name: pointer to a string containing the name of the subsystem to boot
 *
 * This function returns a pointer if it succeeds. If an error occurs an
 * ERR_PTR is returned.
 *
 * If this feature is disable, the value %NULL will be returned.
 */
void *subsystem_get(const char *name)
{
	return __subsystem_get(name, NULL);
}
EXPORT_SYMBOL(subsystem_get);

/**
 * subsystem_get_with_fwname() - Boot a subsystem using the firmware name passed
 * @name: pointer to a string containing the name of the subsystem to boot
 * @fw_name: pointer to a string containing the subsystem firmware image name
 *
 * This function returns a pointer if it succeeds. If an error occurs an
 * ERR_PTR is returned.
 *
 * If this feature is disable, the value %NULL will be returned.
 */
void *subsystem_get_with_fwname(const char *name, const char *fw_name)
{
	return __subsystem_get(name, fw_name);
}
EXPORT_SYMBOL(subsystem_get_with_fwname);

/**
 * subsystem_put() - Shutdown a subsystem
 * @peripheral_handle: pointer from a previous call to subsystem_get()
 *
 * This doesn't imply that a subsystem is shutdown until all callers of
 * subsystem_get() have called subsystem_put().
 */
void subsystem_put(void *subsystem)
{
	struct subsys_device *subsys_d, *subsys = subsystem;
	struct subsys_tracking *track;

	if (IS_ERR_OR_NULL(subsys))
		return;

	subsys_d = find_subsys_device(subsys->desc->poff_depends_on);
	if (subsys_d)
		subsystem_put(subsys_d);

	track = subsys_get_track(subsys);
	mutex_lock(&track->lock);
	if (WARN(!subsys->count, "%s: %s: Reference count mismatch\n",
			subsys->desc->name, __func__))
		goto err_out;
	if (!--subsys->count) {
		subsys_stop(subsys);
		if (subsys->do_ramdump_on_put)
			subsystem_ramdump(subsys, NULL);
		subsystem_free_memory(subsys, NULL);
	}
	mutex_unlock(&track->lock);

	module_put(subsys->owner);
	put_device(&subsys->dev);
	return;
err_out:
	mutex_unlock(&track->lock);
}
EXPORT_SYMBOL(subsystem_put);

static void subsystem_restart_wq_func(struct work_struct *work)
{
	struct subsys_device *dev = container_of(work,
						struct subsys_device, work);
	struct subsys_device **list;
	struct subsys_desc *desc = dev->desc;
	struct subsys_soc_restart_order *order = dev->restart_order;
	struct subsys_tracking *track;
	unsigned int count;
	unsigned long flags;
	int ret;

	/*
	 * It's OK to not take the registration lock at this point.
	 * This is because the subsystem list inside the relevant
	 * restart order is not being traversed.
	 */
	if (order) {
		list = order->subsys_ptrs;
		count = order->count;
		track = &order->track;
	} else {
		list = &dev;
		count = 1;
		track = &dev->track;
	}

	/*
	 * If a system reboot/shutdown is under way, ignore subsystem errors.
	 * However, print a message so that we know that a subsystem behaved
	 * unexpectedly here.
	 */
	if (system_state == SYSTEM_RESTART
		|| system_state == SYSTEM_POWER_OFF) {
		WARN(1, "SSR aborted: %s, system reboot/shutdown is under way\n",
			desc->name);
		return;
	}

	mutex_lock(&track->lock);
	do_epoch_check(dev);

	if (dev->track.state == SUBSYS_OFFLINE) {
		mutex_unlock(&track->lock);
		WARN(1, "SSR aborted: %s subsystem not online\n", desc->name);
		return;
	}

	/*
	 * It's necessary to take the registration lock because the subsystem
	 * list in the SoC restart order will be traversed and it shouldn't be
	 * changed until _this_ restart sequence completes.
	 */
	mutex_lock(&soc_order_reg_lock);

	pr_debug("[%s:%d]: Starting restart sequence for %s\n",
			current->comm, current->pid, desc->name);
	notify_each_subsys_device(list, count, SUBSYS_BEFORE_SHUTDOWN, NULL);
	ret = for_each_subsys_device(list, count, NULL, subsystem_shutdown);
	if (ret)
		goto err;
	notify_each_subsys_device(list, count, SUBSYS_AFTER_SHUTDOWN, NULL);

	notify_each_subsys_device(list, count, SUBSYS_RAMDUMP_NOTIFICATION,
									NULL);

	spin_lock_irqsave(&track->s_lock, flags);
	track->p_state = SUBSYS_RESTARTING;
	spin_unlock_irqrestore(&track->s_lock, flags);

	/* Collect ram dumps for all subsystems in order here */
	for_each_subsys_device(list, count, NULL, subsystem_ramdump);

	for_each_subsys_device(list, count, NULL, subsystem_free_memory);

	notify_each_subsys_device(list, count, SUBSYS_BEFORE_POWERUP, NULL);
	ret = for_each_subsys_device(list, count, NULL, subsystem_powerup);
	if (ret)
		goto err;
	notify_each_subsys_device(list, count, SUBSYS_AFTER_POWERUP, NULL);

	pr_info("[%s:%d]: Restart sequence for %s completed.\n",
			current->comm, current->pid, desc->name);

err:
	/* Reset subsys count */
	if (ret)
		dev->count = 0;

	mutex_unlock(&soc_order_reg_lock);
	mutex_unlock(&track->lock);

	spin_lock_irqsave(&track->s_lock, flags);
	track->p_state = SUBSYS_NORMAL;
	__pm_relax(dev->ssr_wlock);
	spin_unlock_irqrestore(&track->s_lock, flags);
}

static void __subsystem_restart_dev(struct subsys_device *dev)
{
	struct subsys_desc *desc = dev->desc;
	const char *name = dev->desc->name;
	struct subsys_tracking *track;
	unsigned long flags;

	pr_debug("Restarting %s [level=%s]!\n", desc->name,
			restart_levels[dev->restart_level]);

	track = subsys_get_track(dev);
	/*
	 * Allow drivers to call subsystem_restart{_dev}() as many times as
	 * they want up until the point where the subsystem is shutdown.
	 */
	spin_lock_irqsave(&track->s_lock, flags);
	if (track->p_state != SUBSYS_CRASHED &&
					dev->track.state == SUBSYS_ONLINE) {
		if (track->p_state != SUBSYS_RESTARTING) {
			track->p_state = SUBSYS_CRASHED;
			__pm_stay_awake(dev->ssr_wlock);
			queue_work(ssr_wq, &dev->work);
		} else {
			panic("Subsystem %s crashed during SSR!", name);
		}
	} else
		WARN(dev->track.state == SUBSYS_OFFLINE,
			"SSR aborted: %s subsystem not online\n", name);
	spin_unlock_irqrestore(&track->s_lock, flags);
}

static void device_restart_work_hdlr(struct work_struct *work)
{
	struct subsys_device *dev = container_of(work, struct subsys_device,
							device_restart_work);

	notify_each_subsys_device(&dev, 1, SUBSYS_SOC_RESET, NULL);
	/*
	 * Temporary workaround until ramdump userspace application calls
	 * sync() and fclose() on attempting the dump.
	 */
	msleep(100);
	panic("subsys-restart: Resetting the SoC - %s crashed.",
							dev->desc->name);
}

#ifdef OPLUS_FEATURE_MODEM_MINIDUMP
unsigned int getBKDRHash(char *str, unsigned int len)
{
	unsigned int seed = 131; /* 31 131 1313 13131 131313 etc.. */
	unsigned int hash = 0;
	unsigned int i    = 0;
	if (str == NULL) {
		return 0;
	}
	for(i = 0; i < len; str++, i++) {
		hash = (hash * seed) + (*str);
	}
	return hash;
}
EXPORT_SYMBOL(getBKDRHash);

void __subsystem_send_uevent(struct device *dev, char *reason)
{
	int ret_val;
	char modem_event[] = "MODEM_EVENT=modem_failure";
	char modem_reason[300] = {0};
	char modem_hashreason[MAX_REASON_LEN] = {0};
	char *envp[4];
	unsigned int hashid = 0;

	envp[0] = (char *)&modem_event;
	if(reason){
		snprintf(modem_reason, sizeof(modem_reason),"MODEM_REASON=%s", reason);
	}else{
	    snprintf(modem_reason, sizeof(modem_reason),"MODEM_REASON=unkown");
	}
	modem_reason[299] = 0;
	envp[1] = (char *)&modem_reason;

	hashid = getBKDRHash(reason, strlen(reason));
	snprintf(modem_hashreason, sizeof(modem_hashreason), "MODEM_HASH_REASON=fid:%u;cause:%s", hashid, reason);
	modem_hashreason[MAX_REASON_LEN - 1] = 0;
	pr_info("__subsystem_send_uevent: modem_hashreason: %s\n", modem_hashreason);
	envp[2] = (char *)&modem_hashreason;

	envp[3] = 0;

	if(dev){
		ret_val = kobject_uevent_env(&(dev->kobj), KOBJ_CHANGE, envp);
		if(!ret_val){
			pr_info("modem crash:kobject_uevent_env success!\n");
		}else{
			pr_info("modem crash:kobject_uevent_env fail,error=%d!\n", ret_val);
		}
    }
}
EXPORT_SYMBOL(__subsystem_send_uevent);

void subsystem_send_uevent(struct subsys_device *dev, char *reason)
{
	__subsystem_send_uevent(&(dev->dev), reason);
	return;
}
EXPORT_SYMBOL(subsystem_send_uevent);
#endif /*OPLUS_FEATURE_MODEM_MINIDUMP*/

#ifdef OPLUS_FEATURE_WIFI_DCS_SWITCH
#define WCNSS_CRASH_REASON_SMEM		422
#include <linux/soc/qcom/smem.h>
#include <linux/soc/qcom/smem_state.h>
void __wlan_subsystem_send_uevent(struct device *dev, char *reason, const char *name)
{
	int ret_val;
	char event[] = "WLAN_SWITCH_EVENT=Subsystem";
	char fw_Reason[300] = {0};
	char sub_name[300] = {0};
	char *envp[4];

	if (name) {
		snprintf(sub_name, sizeof(sub_name), "subsystem_name=%s", name);
	} else {
		snprintf(sub_name, sizeof(sub_name), "subsystem_name=unkown");
	}

	if (reason) {
		snprintf(fw_Reason, sizeof(fw_Reason), "WLAN_MONITER_REASON=%s", reason);
	} else {
		snprintf(fw_Reason, sizeof(fw_Reason), "WLAN_MONITER_REASON=unkown");
	}

	envp[0] = (char *)&event;

	fw_Reason[299] = 0;
	envp[1] = (char *)&fw_Reason;
	envp[2] = (char *)&sub_name;
	envp[3] = 0;

	if (dev) {
		ret_val = kobject_uevent_env(&(dev->kobj), KOBJ_CHANGE, envp);
		if (!ret_val) {
			pr_info("wlan crash:kobject_uevent_env success!\n");
		} else {
			pr_info("wlan crash:kobject_uevent_env fail,error=%d!\n", ret_val);
		}
	}
}
EXPORT_SYMBOL(__wlan_subsystem_send_uevent);

void wlan_subsystem_send_uevent(struct subsys_device *dev, char *reason, const char *name)
{
	__wlan_subsystem_send_uevent(&(dev->dev), reason, name);
	return;
}
EXPORT_SYMBOL(wlan_subsystem_send_uevent);
#endif /* OPLUS_FEATURE_WIFI_DCS_SWITCH */

int subsystem_restart_dev(struct subsys_device *dev)
{
	const char *name;

	if (!get_device(&dev->dev))
		return -ENODEV;

	if (!try_module_get(dev->owner)) {
		put_device(&dev->dev);
		return -ENODEV;
	}

	name = dev->desc->name;

#ifdef OPLUS_FEATURE_ADSP_RECOVERY
	if (name && !strcmp(name, "adsp")) {
		if (oplus_get_ssr_state()) {
			pr_err("%s: adsp restarting, Ignoring request\n", __func__);
			return 0;
		} else {
			oplus_set_ssr_state(true);
		}
	}
#endif
#ifdef OPLUS_FEATURE_WIFI_DCS_SWITCH
	if (subsys_get_crash_status(dev) == CRASH_STATUS_ERR_FATAL) {
		pr_info("subsystem_restart_dev wlan send uevent");
		__wlan_subsystem_send_uevent(&(dev->dev), "", dev->desc->name);
	}
#endif /* OPLUS_FEATURE_WIFI_DCS_SWITCH */
	send_early_notifications(dev->early_notify);

	/*
	 * If a system reboot/shutdown is underway, ignore subsystem errors.
	 * However, print a message so that we know that a subsystem behaved
	 * unexpectedly here.
	 */
	if (system_state == SYSTEM_RESTART
		|| system_state == SYSTEM_POWER_OFF) {
		pr_err("%s crashed during a system poweroff/shutdown.\n", name);
		return -EBUSY;
	}

	pr_info("Restart sequence requested for %s, restart_level = %s.\n",
		name, restart_levels[dev->restart_level]);

	if (disable_restart_work == DISABLE_SSR) {
		pr_warn("subsys-restart: Ignoring restart request for %s\n",
									name);
		return 0;
	}

	switch (dev->restart_level) {

	case RESET_SUBSYS_COUPLED:
		__subsystem_restart_dev(dev);
		break;
	case RESET_SOC:
	#ifdef VENDOR_EDIT
		if (!strcmp(name, "esoc0") && oem_is_fulldump()) {
			if (!direct_panic) {
				delay_panic = true;
			}
			direct_panic = false;
			__subsystem_restart_dev(dev);
			break;
		} else {
			direct_panic = false;
			__pm_stay_awake(dev->ssr_wlock);
			schedule_work(&dev->device_restart_work);
		}
	#else
		__pm_stay_awake(dev->ssr_wlock);
		schedule_work(&dev->device_restart_work);
	#endif
		return 0;
	default:
		panic("subsys-restart: Unknown restart level!\n");
		break;
	}
	module_put(dev->owner);
	put_device(&dev->dev);

	return 0;
}
EXPORT_SYMBOL(subsystem_restart_dev);

int subsystem_restart(const char *name)
{
	int ret;
	struct subsys_device *dev = find_subsys_device(name);

	if (!dev)
		return -ENODEV;

	ret = subsystem_restart_dev(dev);
	put_device(&dev->dev);
	return ret;
}
EXPORT_SYMBOL(subsystem_restart);

int subsystem_crashed(const char *name)
{
	struct subsys_device *dev = find_subsys_device(name);
	struct subsys_tracking *track;

	if (!dev)
		return -ENODEV;

	if (!get_device(&dev->dev))
		return -ENODEV;

	track = subsys_get_track(dev);

	mutex_lock(&track->lock);
	dev->do_ramdump_on_put = true;
	/*
	 * TODO: Make this work with multiple consumers where one is calling
	 * subsystem_restart() and another is calling this function. To do
	 * so would require updating private state, etc.
	 */
	mutex_unlock(&track->lock);

	put_device(&dev->dev);
	return 0;
}
EXPORT_SYMBOL(subsystem_crashed);

void subsys_set_crash_status(struct subsys_device *dev,
				enum crash_status crashed)
{
	if (!dev) {
		pr_err("subsys_set_crash_status() dev is NULL\n");
		return;
	}
	dev->crashed = crashed;
}
EXPORT_SYMBOL(subsys_set_crash_status);

enum crash_status subsys_get_crash_status(struct subsys_device *dev)
{
	if (!dev) {
		pr_err("subsys_get_crash_status() dev is NULL\n");
		return CRASH_STATUS_WDOG_BITE;
	}

	return dev->crashed;
}
EXPORT_SYMBOL(subsys_get_crash_status);

static struct subsys_device *desc_to_subsys(struct device *d)
{
	struct subsys_device *device, *subsys_dev = 0;

	mutex_lock(&subsys_list_lock);
	list_for_each_entry(device, &subsys_list, list)
		if (device->desc->dev == d)
			subsys_dev = device;
	mutex_unlock(&subsys_list_lock);
	return subsys_dev;
}

void notify_proxy_vote(struct device *device)
{
	struct subsys_device *dev = desc_to_subsys(device);

	if (dev)
		notify_each_subsys_device(&dev, 1, SUBSYS_PROXY_VOTE, NULL);
}

void notify_proxy_unvote(struct device *device)
{
	struct subsys_device *dev = desc_to_subsys(device);

	if (dev)
		notify_each_subsys_device(&dev, 1, SUBSYS_PROXY_UNVOTE, NULL);
}

void notify_before_auth_and_reset(struct device *device)
{
	struct subsys_device *dev = desc_to_subsys(device);

	if (dev)
		notify_each_subsys_device(&dev, 1,
			SUBSYS_BEFORE_AUTH_AND_RESET, NULL);
}


static int subsys_device_open(struct inode *inode, struct file *file)
{
	struct subsys_device *device, *subsys_dev = 0;
	void *retval;

	mutex_lock(&subsys_list_lock);
	list_for_each_entry(device, &subsys_list, list)
		if (MINOR(device->dev_no) == iminor(inode))
			subsys_dev = device;
	mutex_unlock(&subsys_list_lock);

	if (!subsys_dev)
		return -EINVAL;

	retval = subsystem_get_with_fwname(subsys_dev->desc->name,
					subsys_dev->desc->fw_name);
	if (IS_ERR(retval))
		return PTR_ERR(retval);

	return 0;
}

static int subsys_device_close(struct inode *inode, struct file *file)
{
	struct subsys_device *device, *subsys_dev = 0;

	mutex_lock(&subsys_list_lock);
	list_for_each_entry(device, &subsys_list, list)
		if (MINOR(device->dev_no) == iminor(inode))
			subsys_dev = device;
	mutex_unlock(&subsys_list_lock);

	if (!subsys_dev)
		return -EINVAL;

	subsystem_put(subsys_dev);
	return 0;
}

static const struct file_operations subsys_device_fops = {
		.owner = THIS_MODULE,
		.open = subsys_device_open,
		.release = subsys_device_close,
};

static void subsys_device_release(struct device *dev)
{
	struct subsys_device *subsys = to_subsys(dev);

	wakeup_source_unregister(subsys->ssr_wlock);
	mutex_destroy(&subsys->track.lock);
	ida_simple_remove(&subsys_ida, subsys->id);
	kfree(subsys);
}
static irqreturn_t subsys_err_ready_intr_handler(int irq, void *subsys)
{
	struct subsys_device *subsys_dev = subsys;

	dev_info(subsys_dev->desc->dev,
		"Subsystem error monitoring/handling services are up\n");

	if (subsys_dev->desc->is_not_loadable)
		return IRQ_HANDLED;

	complete(&subsys_dev->err_ready);
	return IRQ_HANDLED;
}

static int subsys_char_device_add(struct subsys_device *subsys_dev)
{
	int ret = 0;
	static int major, minor;
	dev_t dev_no;

	mutex_lock(&char_device_lock);
	if (!major) {
		ret = alloc_chrdev_region(&dev_no, 0, 4, "subsys");
		if (ret < 0) {
			pr_err("Failed to alloc subsys_dev region, err %d\n",
									ret);
			goto fail;
		}
		major = MAJOR(dev_no);
		minor = MINOR(dev_no);
	} else
		dev_no = MKDEV(major, minor);

	if (!device_create(char_class, subsys_dev->desc->dev, dev_no,
			NULL, "subsys_%s", subsys_dev->desc->name)) {
		pr_err("Failed to create subsys_%s device\n",
						subsys_dev->desc->name);
		goto fail_unregister_cdev_region;
	}

	cdev_init(&subsys_dev->char_dev, &subsys_device_fops);
	subsys_dev->char_dev.owner = THIS_MODULE;
	ret = cdev_add(&subsys_dev->char_dev, dev_no, 1);
	if (ret < 0)
		goto fail_destroy_device;

	subsys_dev->dev_no = dev_no;
	minor++;
	mutex_unlock(&char_device_lock);

	return 0;

fail_destroy_device:
	device_destroy(char_class, dev_no);
fail_unregister_cdev_region:
	unregister_chrdev_region(dev_no, 1);
fail:
	mutex_unlock(&char_device_lock);
	return ret;
}

static void subsys_char_device_remove(struct subsys_device *subsys_dev)
{
	cdev_del(&subsys_dev->char_dev);
	device_destroy(char_class, subsys_dev->dev_no);
	unregister_chrdev_region(subsys_dev->dev_no, 1);
}

static void subsys_remove_restart_order(struct device_node *device)
{
	struct subsys_soc_restart_order *order;
	int i;

	mutex_lock(&ssr_order_mutex);
	list_for_each_entry(order, &ssr_order_list, list)
		for (i = 0; i < order->count; i++)
			if (order->device_ptrs[i] == device)
				order->subsys_ptrs[i] = NULL;
	mutex_unlock(&ssr_order_mutex);
}

static struct subsys_soc_restart_order *ssr_parse_restart_orders(struct
							subsys_desc * desc)
{
	int i, j, count, num = 0;
	struct subsys_soc_restart_order *order, *tmp;
	struct device *dev = desc->dev;
	struct device_node *ssr_node;
	uint32_t len;

	if (!of_get_property(dev->of_node, "qcom,restart-group", &len))
		return NULL;

	count = len/sizeof(uint32_t);

	order = devm_kzalloc(dev, sizeof(*order), GFP_KERNEL);
	if (!order)
		return ERR_PTR(-ENOMEM);

	order->subsys_ptrs = devm_kzalloc(dev,
				count * sizeof(struct subsys_device *),
				GFP_KERNEL);
	if (!order->subsys_ptrs)
		return ERR_PTR(-ENOMEM);

	order->device_ptrs = devm_kzalloc(dev,
				count * sizeof(struct device_node *),
				GFP_KERNEL);
	if (!order->device_ptrs)
		return ERR_PTR(-ENOMEM);

	for (i = 0; i < count; i++) {
		ssr_node = of_parse_phandle(dev->of_node,
						"qcom,restart-group", i);
		if (!ssr_node)
			return ERR_PTR(-ENXIO);
		of_node_put(ssr_node);
		pr_info("%s device has been added to %s's restart group\n",
						ssr_node->name, desc->name);
		order->device_ptrs[i] = ssr_node;
	}

	/*
	 * Check for similar restart groups. If found, return
	 * without adding the new group to the ssr_order_list.
	 */
	mutex_lock(&ssr_order_mutex);
	list_for_each_entry(tmp, &ssr_order_list, list) {
		for (i = 0; i < count; i++) {
			for (j = 0; j < count; j++) {
				if (order->device_ptrs[j] !=
					tmp->device_ptrs[i])
					continue;
				else
					num++;
			}
		}

		if (num == count && tmp->count == count)
			goto err;
		else if (num) {
			tmp = ERR_PTR(-EINVAL);
			goto err;
		}
	}

	order->count = count;
	mutex_init(&order->track.lock);
	spin_lock_init(&order->track.s_lock);

	INIT_LIST_HEAD(&order->list);
	list_add_tail(&order->list, &ssr_order_list);
	mutex_unlock(&ssr_order_mutex);

	return order;
err:
	mutex_unlock(&ssr_order_mutex);
	return tmp;
}

static int __get_irq(struct subsys_desc *desc, const char *prop,
		unsigned int *irq)
{
	int irql = 0;
	struct device_node *dnode = desc->dev->of_node;

	if (of_property_match_string(dnode, "interrupt-names", prop) < 0)
		return -ENOENT;

	irql = of_irq_get_byname(dnode, prop);
	if (irql < 0) {
		pr_err("[%s]: Error getting IRQ \"%s\"\n", desc->name,
		prop);
		return irql;
	}
	*irq = irql;
	return 0;
}

static int __get_smem_state(struct subsys_desc *desc, const char *prop,
		int *smem_bit)
{
	struct device_node *dnode = desc->dev->of_node;

	if (of_find_property(dnode, "qcom,smem-states", NULL)) {
		desc->state = qcom_smem_state_get(desc->dev, prop, smem_bit);
		if (IS_ERR_OR_NULL(desc->state)) {
			pr_err("Could not get smem-states %s\n", prop);
			return PTR_ERR(desc->state);
		}
		return 0;
	}
	return -ENOENT;
}

static int subsys_parse_devicetree(struct subsys_desc *desc)
{
	struct subsys_soc_restart_order *order;
	int ret;
	struct platform_device *pdev = container_of(desc->dev,
					struct platform_device, dev);

	ret = __get_irq(desc, "qcom,err-fatal", &desc->err_fatal_irq);
	if (ret && ret != -ENOENT)
		return ret;

	ret = __get_irq(desc, "qcom,err-ready", &desc->err_ready_irq);
	if (ret && ret != -ENOENT)
		return ret;

	ret = __get_irq(desc, "qcom,stop-ack", &desc->stop_ack_irq);
	if (ret && ret != -ENOENT)
		return ret;

	ret = __get_irq(desc, "qcom,ramdump-disabled",
			&desc->ramdump_disable_irq);
	if (ret && ret != -ENOENT)
		return ret;

	ret = __get_irq(desc, "qcom,shutdown-ack", &desc->shutdown_ack_irq);
	if (ret && ret != -ENOENT)
		return ret;

	ret = __get_irq(desc, "qcom,wdog", &desc->wdog_bite_irq);
	if (ret && ret != -ENOENT)
		return ret;

	ret = __get_smem_state(desc, "qcom,force-stop", &desc->force_stop_bit);
	if (ret && ret != -ENOENT)
		return ret;

	if (of_property_read_bool(pdev->dev.of_node,
					"qcom,pil-generic-irq-handler")) {
		ret = platform_get_irq(pdev, 0);
		if (ret > 0)
			desc->generic_irq = ret;
	}

	desc->ignore_ssr_failure = of_property_read_bool(pdev->dev.of_node,
						"qcom,ignore-ssr-failure");

	order = ssr_parse_restart_orders(desc);
	if (IS_ERR(order)) {
		pr_err("Could not initialize SSR restart order, err = %ld\n",
							PTR_ERR(order));
		return PTR_ERR(order);
	}

	if (of_property_read_string(pdev->dev.of_node, "qcom,pon-depends-on",
				&desc->pon_depends_on))
		pr_debug("pon-depends-on not set for %s\n", desc->name);

	if (of_property_read_string(pdev->dev.of_node, "qcom,poff-depends-on",
				&desc->poff_depends_on))
		pr_debug("poff-depends-on not set for %s\n", desc->name);

	return 0;
}

static int subsys_setup_irqs(struct subsys_device *subsys)
{
	struct subsys_desc *desc = subsys->desc;
	int ret;

	if (desc->err_fatal_irq && desc->err_fatal_handler) {
		ret = devm_request_threaded_irq(desc->dev, desc->err_fatal_irq,
				NULL,
				desc->err_fatal_handler,
				IRQF_TRIGGER_RISING, desc->name, desc);
		if (ret < 0) {
			dev_err(desc->dev, "[%s]: Unable to register error fatal IRQ handler: %d, irq is %d\n",
				desc->name, ret, desc->err_fatal_irq);
			return ret;
		}
		disable_irq(desc->err_fatal_irq);
	}

	if (desc->stop_ack_irq && desc->stop_ack_handler) {
		ret = devm_request_threaded_irq(desc->dev, desc->stop_ack_irq,
				NULL,
			desc->stop_ack_handler,
			IRQF_TRIGGER_RISING, desc->name, desc);
		if (ret < 0) {
			dev_err(desc->dev, "[%s]: Unable to register stop ack handler: %d\n",
				desc->name, ret);
			return ret;
		}
		disable_irq(desc->stop_ack_irq);
	}

	if (desc->wdog_bite_irq && desc->wdog_bite_handler) {
		ret = devm_request_irq(desc->dev, desc->wdog_bite_irq,
			desc->wdog_bite_handler,
			IRQF_TRIGGER_RISING, desc->name, desc);
		if (ret < 0) {
			dev_err(desc->dev, "[%s]: Unable to register wdog bite handler: %d\n",
				desc->name, ret);
			return ret;
		}
		disable_irq(desc->wdog_bite_irq);
	}

	if (desc->shutdown_ack_irq && desc->shutdown_ack_handler) {
		ret = devm_request_threaded_irq(desc->dev,
				desc->shutdown_ack_irq,
				NULL,
			desc->shutdown_ack_handler,
			IRQF_TRIGGER_RISING, desc->name, desc);
		if (ret < 0) {
			dev_err(desc->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
				desc->name, ret);
			return ret;
		}
		disable_irq(desc->shutdown_ack_irq);
	}

	if (desc->ramdump_disable_irq && desc->ramdump_disable_handler) {
		ret = devm_request_threaded_irq(desc->dev,
				desc->ramdump_disable_irq,
				NULL,
			desc->ramdump_disable_handler,
			IRQF_TRIGGER_RISING, desc->name, desc);
		if (ret < 0) {
			dev_err(desc->dev, "[%s]: Unable to register shutdown ack handler: %d\n",
				desc->name, ret);
			return ret;
		}
		disable_irq(desc->ramdump_disable_irq);
	}

	if (desc->generic_irq && desc->generic_handler) {
		ret = devm_request_irq(desc->dev, desc->generic_irq,
			desc->generic_handler,
			IRQF_TRIGGER_HIGH, desc->name, desc);
		if (ret < 0) {
			dev_err(desc->dev, "[%s]: Unable to register generic irq handler: %d\n",
				desc->name, ret);
			return ret;
		}
		disable_irq(desc->generic_irq);
	}

	if (desc->err_ready_irq) {
		ret = devm_request_threaded_irq(desc->dev,
					desc->err_ready_irq,
					NULL,
					subsys_err_ready_intr_handler,
					IRQF_TRIGGER_RISING,
					"error_ready_interrupt", subsys);
		if (ret < 0) {
			dev_err(desc->dev,
				"[%s]: Unable to register err ready handler\n",
				desc->name);
			return ret;
		}
		disable_irq(desc->err_ready_irq);
	}

	return 0;
}

static void subsys_free_irqs(struct subsys_device *subsys)
{
	struct subsys_desc *desc = subsys->desc;

	if (desc->err_fatal_irq && desc->err_fatal_handler)
		devm_free_irq(desc->dev, desc->err_fatal_irq, desc);
	if (desc->stop_ack_irq && desc->stop_ack_handler)
		devm_free_irq(desc->dev, desc->stop_ack_irq, desc);
	if (desc->shutdown_ack_irq && desc->shutdown_ack_handler)
		devm_free_irq(desc->dev, desc->shutdown_ack_irq, desc);
	if (desc->ramdump_disable_irq && desc->ramdump_disable_handler)
		devm_free_irq(desc->dev, desc->ramdump_disable_irq, desc);
	if (desc->wdog_bite_irq && desc->wdog_bite_handler)
		devm_free_irq(desc->dev, desc->wdog_bite_irq, desc);
	if (desc->err_ready_irq)
		devm_free_irq(desc->dev, desc->err_ready_irq, subsys);
}

static void init_all_completions(struct subsys_device *subsys_dev)
{
	init_completion(&subsys_dev->err_ready);
	init_completion(&subsys_dev->shutdown_ack);
}

struct subsys_device *subsys_register(struct subsys_desc *desc)
{
	struct subsys_device *subsys;
	struct device_node *ofnode = desc->dev->of_node;
	int ret;

	subsys = kzalloc(sizeof(*subsys), GFP_KERNEL);
	if (!subsys)
		return ERR_PTR(-ENOMEM);

	subsys->desc = desc;
	subsys->owner = desc->owner;
	subsys->dev.parent = desc->dev;
	subsys->dev.bus = &subsys_bus_type;
	subsys->dev.release = subsys_device_release;
	subsys->notif_state = -1;
#ifdef OPLUS_BUG_STABILITY
	if(!oplus_daily_build() && !(get_eng_version() == AGING))
		subsys->restart_level = RESET_SUBSYS_COUPLED;
#endif /*OPLUS_BUG_STABILITY */
	subsys->desc->sysmon_pid = -1;
	subsys->desc->state = NULL;
	strlcpy(subsys->desc->fw_name, desc->name,
			sizeof(subsys->desc->fw_name));

	subsys->notify = subsys_notif_add_subsys(desc->name);
	subsys->early_notify = subsys_get_early_notif_info(desc->name);

	snprintf(subsys->wlname, sizeof(subsys->wlname), "ssr(%s)", desc->name);

	subsys->ssr_wlock =
		wakeup_source_register(&subsys->dev, subsys->wlname);
	if (!subsys->ssr_wlock) {
		kfree(subsys);
		return ERR_PTR(-ENOMEM);
	}

	INIT_WORK(&subsys->work, subsystem_restart_wq_func);
	INIT_WORK(&subsys->device_restart_work, device_restart_work_hdlr);
	spin_lock_init(&subsys->track.s_lock);
	init_subsys_timer(desc);

	subsys->id = ida_simple_get(&subsys_ida, 0, 0, GFP_KERNEL);
	if (subsys->id < 0) {
		wakeup_source_unregister(subsys->ssr_wlock);
		ret = subsys->id;
		kfree(subsys);
		return ERR_PTR(ret);
	}

	dev_set_name(&subsys->dev, "subsys%d", subsys->id);

	mutex_init(&subsys->track.lock);
	init_all_completions(subsys);

	ret = device_register(&subsys->dev);
	if (ret) {
		put_device(&subsys->dev);
		return ERR_PTR(ret);
	}

	ret = subsys_char_device_add(subsys);
	if (ret)
		goto err_register;

	if (ofnode) {
		ret = subsys_parse_devicetree(desc);
		if (ret)
			goto err_register;

		subsys->restart_order = update_restart_order(subsys);

		if (of_property_read_u32(ofnode, "qcom,ssctl-instance-id",
					&desc->ssctl_instance_id))
			pr_debug("Reading instance-id for %s failed\n",
								desc->name);

		if (of_property_read_u32(ofnode, "qcom,sysmon-id",
					&subsys->desc->sysmon_pid))
			pr_debug("Reading sysmon-id for %s failed\n",
								desc->name);

		subsys->desc->edge = of_get_property(ofnode, "qcom,edge",
									NULL);
		if (!subsys->desc->edge)
			pr_debug("Reading qcom,edge for %s failed\n",
								desc->name);
	}

	ret = sysmon_notifier_register(desc);
	if (ret < 0)
		goto err_sysmon_notifier;

	if (subsys->desc->edge) {
		ret = sysmon_glink_register(desc);
		if (ret < 0)
			goto err_sysmon_glink_register;
	}
	mutex_lock(&subsys_list_lock);
	INIT_LIST_HEAD(&subsys->list);
	list_add_tail(&subsys->list, &subsys_list);
	mutex_unlock(&subsys_list_lock);

	if (ofnode) {
		ret = subsys_setup_irqs(subsys);
		if (ret < 0)
			goto err_setup_irqs;
	}

	return subsys;
err_setup_irqs:
	if (subsys->desc->edge)
		sysmon_glink_unregister(desc);
err_sysmon_glink_register:
	sysmon_notifier_unregister(subsys->desc);
err_sysmon_notifier:
	if (ofnode)
		subsys_remove_restart_order(ofnode);
err_register:
	subsys_char_device_remove(subsys);
	device_unregister(&subsys->dev);
	return ERR_PTR(ret);
}
EXPORT_SYMBOL(subsys_register);

void subsys_unregister(struct subsys_device *subsys)
{
	struct subsys_device *subsys_dev, *tmp;
	struct device_node *device = subsys->desc->dev->of_node;

	if (IS_ERR_OR_NULL(subsys))
		return;

	if (get_device(&subsys->dev)) {
		mutex_lock(&subsys_list_lock);
		list_for_each_entry_safe(subsys_dev, tmp, &subsys_list, list)
			if (subsys_dev == subsys)
				list_del(&subsys->list);
		mutex_unlock(&subsys_list_lock);

		if (device) {
			subsys_free_irqs(subsys);
			subsys_remove_restart_order(device);
		}
		mutex_lock(&subsys->track.lock);
		WARN_ON(subsys->count);
		device_unregister(&subsys->dev);
		mutex_unlock(&subsys->track.lock);
		subsys_char_device_remove(subsys);
		sysmon_notifier_unregister(subsys->desc);
		if (subsys->desc->edge)
			sysmon_glink_unregister(subsys->desc);
		put_device(&subsys->dev);
	}
}
EXPORT_SYMBOL(subsys_unregister);

static int subsys_panic(struct device *dev, void *data)
{
	struct subsys_device *subsys = to_subsys(dev);

	if (subsys->desc->crash_shutdown)
		subsys->desc->crash_shutdown(subsys->desc);
	return 0;
}

static int ssr_panic_handler(struct notifier_block *this,
				unsigned long event, void *ptr)
{
	bus_for_each_dev(&subsys_bus_type, NULL, NULL, subsys_panic);
	return NOTIFY_DONE;
}

static struct notifier_block panic_nb = {
	.notifier_call  = ssr_panic_handler,
};

extern bool ts_wait_error;
extern bool ts_send_error;
#ifdef CONFIG_ESOC_MDM_4x
extern bool modem_force_rst;
#endif

static ssize_t force_rst_write(struct file *file,
				const char __user *buf,
				size_t count,
				loff_t *lo)
{
	char read_buf[4] = {0};
	struct subsys_device *subsys = find_subsys_device("esoc0");

	if (!subsys)
		return 0;

	if (copy_from_user(read_buf, buf, 1)) {
		pr_err("%s: failed to copy from user.\n", __func__);
		return count;
	}

	pr_info("%s: %s\n", __func__, read_buf);

	if (!strncmp(read_buf, "2", 1)) {
		panic("force esoc crash");
	}

	if (!strncmp(read_buf, "1", 1) && (ts_send_error || ts_wait_error)) {
#ifdef CONFIG_ESOC_MDM_4x
		modem_force_rst = true;
#endif
		ts_wait_error = false;
		ts_send_error = false;
		pr_err("force to reset modem\n");
		op_restart_modem(subsys);
	}

	return count;
}

static ssize_t force_rst_read(struct file *file,
				char __user *buf,
				size_t count,
				loff_t *ppos)
{
	return count;
}

static const struct file_operations esoc_force_rst_fops = {
    .write = force_rst_write,
    .read  = force_rst_read,
    .open  = simple_open,
    .owner = THIS_MODULE,
};

static int __init subsys_restart_init(void)
{
	int ret;
	struct proc_dir_entry *d_entry = NULL;
	ssr_wq = alloc_workqueue("ssr_wq",
		WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 0);
	BUG_ON(!ssr_wq);

	ret = bus_register(&subsys_bus_type);
	if (ret)
		goto err_bus;

	char_class = class_create(THIS_MODULE, "subsys");
	if (IS_ERR(char_class)) {
		ret = -ENOMEM;
		pr_err("Failed to create subsys_dev class\n");
		goto err_class;
	}

	ret = atomic_notifier_chain_register(&panic_notifier_list,
			&panic_nb);
	if (ret)
		goto err_soc;

	d_entry = proc_create_data("force_reset", 0666, NULL, &esoc_force_rst_fops, NULL);
	if (!d_entry) {
		goto err_soc;
	}

	return 0;

err_soc:
	class_destroy(char_class);
err_class:
	bus_unregister(&subsys_bus_type);
err_bus:
	destroy_workqueue(ssr_wq);
	return ret;
}
arch_initcall(subsys_restart_init);

MODULE_DESCRIPTION("Subsystem Restart Driver");
MODULE_LICENSE("GPL v2");
