1415 lines
37 KiB
C
Executable file
1415 lines
37 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2020 Samsung Electronics Co., Ltd.
|
|
* http://www.samsung.com
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "sysevent: %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/rtc.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 <linux/of_irq.h>
|
|
#include <linux/of.h>
|
|
#include <linux/timekeeping.h>
|
|
#include <asm/current.h>
|
|
#include <linux/timer.h>
|
|
|
|
#include <soc/samsung/imgloader.h>
|
|
#include <soc/samsung/sysevent.h>
|
|
#include <soc/samsung/sysevent_notif.h>
|
|
|
|
static uint disable_sysevent_support;
|
|
module_param(disable_sysevent_support, uint, 0644);
|
|
|
|
/**
|
|
* enum p_sysevent_state - state of a sysevent (private)
|
|
* @SYSTEM_EVENT_NORMAL: sysevent is operating normally
|
|
* @SYSTEM_EVENT_CRASHED: sysevent has crashed and hasn't been shutdown
|
|
* @SYSTEM_EVENT_RESTARTING: sysevent has been shutdown and is now restarting
|
|
*
|
|
* The 'private' side of the subsytem state used to determine where in the
|
|
* restart process the sysevent is.
|
|
*/
|
|
enum p_sysevent_state {
|
|
SYSTEM_EVENT_NORMAL,
|
|
SYSTEM_EVENT_CRASHED,
|
|
SYSTEM_EVENT_RESTARTING,
|
|
};
|
|
|
|
/**
|
|
* enum sysevent_state - state of a sysevent (public)
|
|
* @SYSTEM_EVENT_OFFLINING: sysevent is offlining
|
|
* @SYSTEM_EVENT_OFFLINE: sysevent is offline
|
|
* @sysevent_ONLINE: sysevent is online
|
|
*
|
|
* The 'public' side of the subsytem state, exposed to userspace.
|
|
*/
|
|
enum sysevent_state {
|
|
SYSTEM_EVENT_OFFLINING,
|
|
SYSTEM_EVENT_OFFLINE,
|
|
SYSTEM_EVENT_ONLINE,
|
|
};
|
|
|
|
static const char * const sysevent_states[] = {
|
|
[SYSTEM_EVENT_OFFLINING] = "OFFLINING",
|
|
[SYSTEM_EVENT_OFFLINE] = "OFFLINE",
|
|
[SYSTEM_EVENT_ONLINE] = "ONLINE",
|
|
};
|
|
|
|
static const char * const restart_levels[] = {
|
|
[RESET_SOC] = "SYSTEM",
|
|
[RESET_SYSTEM_EVENT_COUPLED] = "RELATED",
|
|
};
|
|
|
|
/**
|
|
* struct sysevent_tracking - track state of a sysevent or restart order
|
|
* @p_state: private state of sysevent/order
|
|
* @state: public state of sysevent/order
|
|
* @s_lock: protects p_state
|
|
* @lock: protects sysevent/order callbacks and state
|
|
*
|
|
* Tracks the state of a sysevent or a set of sysevents (restart order).
|
|
* Doing this avoids the need to grab each sysevent's lock and update
|
|
* each sysevents state when restarting an order.
|
|
*/
|
|
struct sysevent_tracking {
|
|
enum p_sysevent_state p_state;
|
|
spinlock_t s_lock;
|
|
enum sysevent_state state;
|
|
struct mutex lock;
|
|
};
|
|
|
|
struct restart_log {
|
|
struct timespec64 time;
|
|
struct sysevent_device *dev;
|
|
struct list_head list;
|
|
};
|
|
|
|
/**
|
|
* struct sysevent_device - sysevent device
|
|
* @desc: sysevent descriptor
|
|
* @work: context for sysevent_restart_wq_func() for this device
|
|
* @sysevent_wlock: prevents suspend during sysevent_restart()
|
|
* @device_restart_work: work struct for device restart
|
|
* @track: state tracking and locking
|
|
* @notify: sysevent notify handle
|
|
* @dev: device
|
|
* @owner: module that provides @desc
|
|
* @count: reference count of sysevent_get()/sysevent_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 sysevent_put() if true
|
|
* @err_ready: completion variable to record error ready from sysevent
|
|
* @crashed: indicates if sysevent has crashed
|
|
* @notif_state: current state of sysevent in terms of sysevent notifications
|
|
*/
|
|
struct sysevent_device {
|
|
struct sysevent_desc *desc;
|
|
struct work_struct work;
|
|
struct wakeup_source *sysevent_wlock;
|
|
char wlname[64];
|
|
struct work_struct device_restart_work;
|
|
struct sysevent_tracking track;
|
|
|
|
void *notify;
|
|
void *early_notify;
|
|
struct device dev;
|
|
struct module *owner;
|
|
int count;
|
|
int id;
|
|
int restart_level;
|
|
int crash_count;
|
|
bool do_ramdump_on_put;
|
|
struct cdev char_dev;
|
|
dev_t dev_no;
|
|
enum crash_status crashed;
|
|
int notif_state;
|
|
struct list_head list;
|
|
};
|
|
|
|
static struct sysevent_device *to_sysevent(struct device *d)
|
|
{
|
|
return container_of(d, struct sysevent_device, dev);
|
|
}
|
|
|
|
static struct sysevent_tracking *sysevent_get_track(struct sysevent_device *sysevent)
|
|
{
|
|
return &sysevent->track;
|
|
}
|
|
|
|
static ssize_t name_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", to_sysevent(dev)->desc->name);
|
|
}
|
|
static DEVICE_ATTR_RO(name);
|
|
|
|
static ssize_t state_show(struct device *dev, struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
enum sysevent_state state = to_sysevent(dev)->track.state;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", sysevent_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_sysevent(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_sysevent(dev)->restart_level;
|
|
|
|
return snprintf(buf, PAGE_SIZE, "%s\n", restart_levels[level]);
|
|
}
|
|
|
|
static ssize_t crash_reason_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
struct sysevent_device *sysevent = to_sysevent(dev);
|
|
|
|
spin_lock_irqsave(&sysevent->desc->sysevent_sysfs_lock, flags);
|
|
ret = snprintf(buf, PAGE_SIZE, "%s\n",
|
|
to_sysevent(dev)->desc->last_crash_reason);
|
|
spin_unlock_irqrestore(&sysevent->desc->sysevent_sysfs_lock, flags);
|
|
return ret;
|
|
}
|
|
static DEVICE_ATTR_RO(crash_reason);
|
|
|
|
static ssize_t crash_timestamp_show(struct device *dev,
|
|
struct device_attribute *attr, char *buf)
|
|
{
|
|
int ret;
|
|
unsigned long flags;
|
|
struct sysevent_device *sysevent = to_sysevent(dev);
|
|
|
|
spin_lock_irqsave(&sysevent->desc->sysevent_sysfs_lock, flags);
|
|
ret = snprintf(buf, PAGE_SIZE, "%s\n",
|
|
to_sysevent(dev)->desc->last_crash_timestamp);
|
|
spin_unlock_irqrestore(&sysevent->desc->sysevent_sysfs_lock, flags);
|
|
return ret;
|
|
}
|
|
static DEVICE_ATTR_RO(crash_timestamp);
|
|
|
|
static ssize_t restart_level_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sysevent_device *sysevent = to_sysevent(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)) {
|
|
sysevent->restart_level = i;
|
|
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_sysevent(dev)->desc->fw_name);
|
|
}
|
|
|
|
static ssize_t firmware_name_store(struct device *dev,
|
|
struct device_attribute *attr, const char *buf, size_t count)
|
|
{
|
|
struct sysevent_device *sysevent = to_sysevent(dev);
|
|
struct sysevent_tracking *track = sysevent_get_track(sysevent);
|
|
const char *p;
|
|
int orig_count = count;
|
|
|
|
p = memchr(buf, '\n', count);
|
|
if (p)
|
|
count = p - buf;
|
|
|
|
pr_info("Changing sysevent fw_name to %s\n", buf);
|
|
mutex_lock(&track->lock);
|
|
strlcpy(sysevent->desc->fw_name, buf,
|
|
min(count + 1, sizeof(sysevent->desc->fw_name)));
|
|
mutex_unlock(&track->lock);
|
|
return orig_count;
|
|
}
|
|
static DEVICE_ATTR_RW(firmware_name);
|
|
|
|
int sysevent_get_restart_level(struct sysevent_device *dev)
|
|
{
|
|
return dev->restart_level;
|
|
}
|
|
EXPORT_SYMBOL(sysevent_get_restart_level);
|
|
|
|
static void sysevent_set_state(struct sysevent_device *sysevent,
|
|
enum sysevent_state state)
|
|
{
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&sysevent->track.s_lock, flags);
|
|
if (sysevent->track.state != state) {
|
|
sysevent->track.state = state;
|
|
spin_unlock_irqrestore(&sysevent->track.s_lock, flags);
|
|
sysfs_notify(&sysevent->dev.kobj, NULL, "state");
|
|
return;
|
|
}
|
|
spin_unlock_irqrestore(&sysevent->track.s_lock, flags);
|
|
}
|
|
|
|
/**
|
|
* subsytem_default_online() - Mark a sysevent as online by default
|
|
* @dev: sysevent to mark as online
|
|
*
|
|
* Marks a sysevent as "online" without increasing the reference count
|
|
* on the sysevent. This is typically used by sysevents that are already
|
|
* online when the kernel boots up.
|
|
*/
|
|
void sysevent_default_online(struct sysevent_device *dev)
|
|
{
|
|
sysevent_set_state(dev, SYSTEM_EVENT_ONLINE);
|
|
}
|
|
EXPORT_SYMBOL(sysevent_default_online);
|
|
|
|
static struct attribute *sysevent_attrs[] = {
|
|
&dev_attr_name.attr,
|
|
&dev_attr_state.attr,
|
|
&dev_attr_crash_count.attr,
|
|
&dev_attr_crash_reason.attr,
|
|
&dev_attr_crash_timestamp.attr,
|
|
&dev_attr_restart_level.attr,
|
|
&dev_attr_firmware_name.attr,
|
|
NULL,
|
|
};
|
|
|
|
ATTRIBUTE_GROUPS(sysevent);
|
|
|
|
struct bus_type sysevent_bus_type = {
|
|
.name = "exynos_sysevent",
|
|
.dev_groups = sysevent_groups,
|
|
};
|
|
EXPORT_SYMBOL(sysevent_bus_type);
|
|
|
|
static DEFINE_IDA(sysevent_ida);
|
|
|
|
static int enable_ramdumps;
|
|
module_param(enable_ramdumps, int, 0644);
|
|
|
|
struct workqueue_struct *sysevent_wq;
|
|
static struct class *char_class;
|
|
|
|
static LIST_HEAD(restart_log_list);
|
|
static LIST_HEAD(sysevent_list);
|
|
static DEFINE_MUTEX(restart_log_mutex);
|
|
static DEFINE_MUTEX(sysevent_list_lock);
|
|
static DEFINE_MUTEX(char_device_lock);
|
|
|
|
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 sysevent_device *dev)
|
|
{
|
|
int n = 0;
|
|
struct timespec64 *time_first = NULL, *curr_time;
|
|
struct timespec64 now;
|
|
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;
|
|
|
|
ktime_get_real_ts64(&now);
|
|
r_log->time = now;
|
|
|
|
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("sysevents 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 sysevent_device *dev)
|
|
{
|
|
return enable_ramdumps;
|
|
}
|
|
|
|
static int for_each_sysevent_device(struct sysevent_device **list,
|
|
unsigned int count, void *data,
|
|
int (*fn)(struct sysevent_device *, void *))
|
|
{
|
|
int ret;
|
|
|
|
while (count--) {
|
|
struct sysevent_device *dev = *list++;
|
|
|
|
if (!dev)
|
|
continue;
|
|
ret = fn(dev, data);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void sysevent_notif_uevent(struct sysevent_desc *desc,
|
|
enum sysevent_notif_type notif)
|
|
{
|
|
char *envp[3];
|
|
|
|
if (notif == SYSTEM_EVENT_AFTER_POWERUP) {
|
|
envp[0] = kasprintf(GFP_KERNEL, "SYSTEM_EVENT=%s", desc->name);
|
|
envp[1] = kasprintf(GFP_KERNEL, "NOTIFICATION=%d", notif);
|
|
envp[2] = NULL;
|
|
kobject_uevent_env(&desc->dev->kobj, KOBJ_CHANGE, envp);
|
|
pr_debug("%s %s sent\n", envp[0], envp[1]);
|
|
kfree(envp[1]);
|
|
kfree(envp[0]);
|
|
}
|
|
}
|
|
|
|
static void notify_each_sysevent_device(struct sysevent_device **list,
|
|
unsigned int count,
|
|
enum sysevent_notif_type notif, void *data)
|
|
{
|
|
while (count--) {
|
|
struct sysevent_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;
|
|
|
|
notif_data.crashed = sysevent_get_crash_status(dev);
|
|
notif_data.enable_ramdump = is_ramdump_enabled(dev);
|
|
notif_data.no_auth = dev->desc->no_auth;
|
|
notif_data.pdev = pdev;
|
|
|
|
sysevent_notif_queue_notification(dev->notify, notif,
|
|
¬if_data);
|
|
if (0)
|
|
sysevent_notif_uevent(dev->desc, notif);
|
|
}
|
|
}
|
|
|
|
static void enable_all_irqs(struct sysevent_device *dev)
|
|
{
|
|
if (dev->desc->watchdog_irq && dev->desc->watchdog_handler) {
|
|
enable_irq(dev->desc->watchdog_irq);
|
|
irq_set_irq_wake(dev->desc->watchdog_irq, 1);
|
|
}
|
|
if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
|
|
enable_irq(dev->desc->err_fatal_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 sysevent_device *dev)
|
|
{
|
|
if (dev->desc->watchdog_irq && dev->desc->watchdog_handler) {
|
|
disable_irq(dev->desc->watchdog_irq);
|
|
irq_set_irq_wake(dev->desc->watchdog_irq, 0);
|
|
}
|
|
if (dev->desc->err_fatal_irq && dev->desc->err_fatal_handler)
|
|
disable_irq(dev->desc->err_fatal_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 sysevent_shutdown(struct sysevent_device *dev, void *data)
|
|
{
|
|
const char *name = dev->desc->name;
|
|
char *timestamp = dev->desc->last_crash_timestamp;
|
|
int ret;
|
|
struct timespec64 ts_rtc;
|
|
struct rtc_time tm;
|
|
unsigned long flags;
|
|
|
|
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_sysevent_failure) {
|
|
panic("sysevent-restart: [%s:%d]: Failed to shutdown %s!",
|
|
current->comm, current->pid, name);
|
|
} else {
|
|
pr_err("Shutdown failure on %s\n", name);
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
spin_lock_irqsave(&dev->desc->sysevent_sysfs_lock, flags);
|
|
/* record crash time */
|
|
ktime_get_real_ts64(&ts_rtc);
|
|
rtc_time64_to_tm(ts_rtc.tv_sec - (sys_tz.tz_minuteswest * 60), &tm);
|
|
snprintf(timestamp, MAX_CRASH_TIMESTAMP_LEN,
|
|
"%d-%02d-%02d_%02d-%02d-%02d",
|
|
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
|
|
tm.tm_hour, tm.tm_min, tm.tm_sec);
|
|
spin_unlock_irqrestore(&dev->desc->sysevent_sysfs_lock, flags);
|
|
|
|
dev->crash_count++;
|
|
sysevent_set_state(dev, SYSTEM_EVENT_OFFLINE);
|
|
disable_all_irqs(dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysevent_ramdump(struct sysevent_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 sysevent_free_memory(struct sysevent_device *dev, void *data)
|
|
{
|
|
if (dev->desc->free_memory)
|
|
dev->desc->free_memory(dev->desc);
|
|
return 0;
|
|
}
|
|
|
|
static int sysevent_powerup(struct sysevent_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);
|
|
|
|
ret = dev->desc->powerup(dev->desc);
|
|
if (ret < 0) {
|
|
notify_each_sysevent_device(&dev, 1, SYSTEM_EVENT_POWERUP_FAILURE,
|
|
NULL);
|
|
if (system_state == SYSTEM_RESTART
|
|
|| system_state == SYSTEM_POWER_OFF)
|
|
WARN(1, "sysevent aborted: %s, system reboot/shutdown is under way\n",
|
|
name);
|
|
else {
|
|
if (!dev->desc->ignore_sysevent_failure) {
|
|
/*
|
|
* There is a slight window between reboot and
|
|
* system_state changing to SYSTEM_RESTART or
|
|
* SYSTEM_POWER_OFF. Add a delay before panic
|
|
* to ensure sysevent that happens during reboot
|
|
* will not result in a kernel panic.
|
|
*/
|
|
msleep(3000);
|
|
if (system_state != SYSTEM_RESTART
|
|
&& system_state != SYSTEM_POWER_OFF)
|
|
panic("[%s:%d]: Powerup error: %s!",
|
|
current->comm,
|
|
current->pid, name);
|
|
}
|
|
pr_err("Powerup failure on %s\n", name);
|
|
}
|
|
return ret;
|
|
}
|
|
enable_all_irqs(dev);
|
|
sysevent_set_state(dev, SYSTEM_EVENT_ONLINE);
|
|
sysevent_set_crash_status(dev, CRASH_STATUS_NO_CRASH);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int __find_sysevent_device(struct device *dev, const void *data)
|
|
{
|
|
struct sysevent_device *sysevent = to_sysevent(dev);
|
|
|
|
return !strcmp(sysevent->desc->name, data);
|
|
}
|
|
|
|
struct sysevent_device *find_sysevent_device(const char *str)
|
|
{
|
|
struct device *dev;
|
|
|
|
if (!str)
|
|
return NULL;
|
|
|
|
dev = bus_find_device(&sysevent_bus_type, NULL, (void *)str,
|
|
__find_sysevent_device);
|
|
return dev ? to_sysevent(dev) : NULL;
|
|
}
|
|
EXPORT_SYMBOL(find_sysevent_device);
|
|
|
|
static int sysevent_start(struct sysevent_device *sysevent)
|
|
{
|
|
int ret;
|
|
|
|
notify_each_sysevent_device(&sysevent, 1, SYSTEM_EVENT_BEFORE_POWERUP,
|
|
NULL);
|
|
|
|
ret = sysevent->desc->powerup(sysevent->desc);
|
|
if (ret) {
|
|
notify_each_sysevent_device(&sysevent, 1, SYSTEM_EVENT_POWERUP_FAILURE,
|
|
NULL);
|
|
return ret;
|
|
}
|
|
enable_all_irqs(sysevent);
|
|
|
|
sysevent_set_state(sysevent, SYSTEM_EVENT_ONLINE);
|
|
sysevent_set_crash_status(sysevent, CRASH_STATUS_NO_CRASH);
|
|
|
|
notify_each_sysevent_device(&sysevent, 1, SYSTEM_EVENT_AFTER_POWERUP,
|
|
NULL);
|
|
return ret;
|
|
}
|
|
|
|
static void sysevent_stop(struct sysevent_device *sysevent)
|
|
{
|
|
notify_each_sysevent_device(&sysevent, 1, SYSTEM_EVENT_BEFORE_SHUTDOWN, NULL);
|
|
sysevent->desc->shutdown(sysevent->desc, false);
|
|
sysevent_set_state(sysevent, SYSTEM_EVENT_OFFLINE);
|
|
disable_all_irqs(sysevent);
|
|
notify_each_sysevent_device(&sysevent, 1, SYSTEM_EVENT_AFTER_SHUTDOWN, NULL);
|
|
}
|
|
|
|
int sysevent_set_fwname(const char *name, const char *fw_name)
|
|
{
|
|
struct sysevent_device *sysevent;
|
|
|
|
if (!name)
|
|
return -EINVAL;
|
|
|
|
if (!fw_name)
|
|
return -EINVAL;
|
|
|
|
sysevent = find_sysevent_device(name);
|
|
if (!sysevent)
|
|
return -EINVAL;
|
|
|
|
pr_debug("Changing sysevent [%s] fw_name to [%s]\n", name, fw_name);
|
|
strlcpy(sysevent->desc->fw_name, fw_name,
|
|
sizeof(sysevent->desc->fw_name));
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(sysevent_set_fwname);
|
|
|
|
void *__sysevent_get(const char *name, const char *fw_name)
|
|
{
|
|
struct sysevent_device *sysevent;
|
|
int ret;
|
|
void *retval;
|
|
struct sysevent_tracking *track;
|
|
|
|
if (!name)
|
|
return NULL;
|
|
|
|
sysevent = retval = find_sysevent_device(name);
|
|
if (!sysevent)
|
|
return ERR_PTR(-ENODEV);
|
|
if (!try_module_get(sysevent->owner)) {
|
|
retval = ERR_PTR(-ENODEV);
|
|
goto err_module;
|
|
}
|
|
|
|
track = sysevent_get_track(sysevent);
|
|
mutex_lock(&track->lock);
|
|
if (!sysevent->count) {
|
|
if (fw_name) {
|
|
pr_info("Changing sysevent fw_name to %s\n", fw_name);
|
|
strlcpy(sysevent->desc->fw_name, fw_name,
|
|
sizeof(sysevent->desc->fw_name));
|
|
}
|
|
ret = sysevent_start(sysevent);
|
|
if (ret) {
|
|
retval = ERR_PTR(ret);
|
|
goto err_start;
|
|
}
|
|
}
|
|
sysevent->count++;
|
|
mutex_unlock(&track->lock);
|
|
return retval;
|
|
err_start:
|
|
mutex_unlock(&track->lock);
|
|
module_put(sysevent->owner);
|
|
err_module:
|
|
put_device(&sysevent->dev);
|
|
return retval;
|
|
}
|
|
|
|
/**
|
|
* subsytem_get() - Boot a sysevent
|
|
* @name: pointer to a string containing the name of the sysevent 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 *sysevent_get(const char *name)
|
|
{
|
|
return __sysevent_get(name, NULL);
|
|
}
|
|
EXPORT_SYMBOL(sysevent_get);
|
|
|
|
/**
|
|
* sysevent_get_with_fwname() - Boot a sysevent using the firmware name passed
|
|
* @name: pointer to a string containing the name of the sysevent to boot
|
|
* @fw_name: pointer to a string containing the sysevent 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 *sysevent_get_with_fwname(const char *name, const char *fw_name)
|
|
{
|
|
return __sysevent_get(name, fw_name);
|
|
}
|
|
EXPORT_SYMBOL(sysevent_get_with_fwname);
|
|
|
|
/**
|
|
* sysevent_put() - Shutdown a sysevent
|
|
* @peripheral_handle: pointer from a previous call to sysevent_get()
|
|
*
|
|
* This doesn't imply that a sysevent is shutdown until all callers of
|
|
* sysevent_get() have called sysevent_put().
|
|
*/
|
|
void sysevent_put(void *sys)
|
|
{
|
|
struct sysevent_device *sysevent = sys;
|
|
struct sysevent_tracking *track;
|
|
|
|
if (IS_ERR_OR_NULL(sysevent))
|
|
return;
|
|
|
|
track = sysevent_get_track(sysevent);
|
|
mutex_lock(&track->lock);
|
|
if (WARN(!sysevent->count, "%s: %s: Reference count mismatch\n",
|
|
sysevent->desc->name, __func__))
|
|
goto err_out;
|
|
if (!--sysevent->count) {
|
|
sysevent_stop(sysevent);
|
|
if (sysevent->do_ramdump_on_put)
|
|
sysevent_ramdump(sysevent, NULL);
|
|
sysevent_free_memory(sysevent, NULL);
|
|
}
|
|
mutex_unlock(&track->lock);
|
|
|
|
module_put(sysevent->owner);
|
|
put_device(&sysevent->dev);
|
|
return;
|
|
err_out:
|
|
mutex_unlock(&track->lock);
|
|
}
|
|
EXPORT_SYMBOL(sysevent_put);
|
|
|
|
static void sysevent_restart_wq_func(struct work_struct *work)
|
|
{
|
|
struct sysevent_device *dev = container_of(work,
|
|
struct sysevent_device, work);
|
|
struct sysevent_device **list;
|
|
struct sysevent_desc *desc = dev->desc;
|
|
struct sysevent_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 sysevent list inside the relevant
|
|
* restart order is not being traversed.
|
|
*/
|
|
list = &dev;
|
|
count = 1;
|
|
track = &dev->track;
|
|
|
|
/*
|
|
* If a system reboot/shutdown is under way, ignore sysevent errors.
|
|
* However, print a message so that we know that a sysevent behaved
|
|
* unexpectedly here.
|
|
*/
|
|
if (system_state == SYSTEM_RESTART
|
|
|| system_state == SYSTEM_POWER_OFF) {
|
|
WARN(1, "sysevent aborted: %s, system reboot/shutdown is under way\n",
|
|
desc->name);
|
|
return;
|
|
}
|
|
|
|
mutex_lock(&track->lock);
|
|
do_epoch_check(dev);
|
|
|
|
if (dev->track.state == SYSTEM_EVENT_OFFLINE) {
|
|
mutex_unlock(&track->lock);
|
|
WARN(1, "sysevent aborted: %s sysevent not online\n", desc->name);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* It's necessary to take the registration lock because the sysevent
|
|
* list in the SoC restart order will be traversed and it shouldn't be
|
|
* changed until _this_ restart sequence completes.
|
|
*/
|
|
pr_debug("[%s:%d]: Starting restart sequence for %s\n",
|
|
current->comm, current->pid, desc->name);
|
|
notify_each_sysevent_device(list, count, SYSTEM_EVENT_BEFORE_SHUTDOWN, NULL);
|
|
ret = for_each_sysevent_device(list, count, NULL, sysevent_shutdown);
|
|
if (ret)
|
|
goto err;
|
|
notify_each_sysevent_device(list, count, SYSTEM_EVENT_AFTER_SHUTDOWN, NULL);
|
|
|
|
notify_each_sysevent_device(list, count, SYSTEM_EVENT_RAMDUMP_NOTIFICATION,
|
|
NULL);
|
|
|
|
spin_lock_irqsave(&track->s_lock, flags);
|
|
track->p_state = SYSTEM_EVENT_RESTARTING;
|
|
spin_unlock_irqrestore(&track->s_lock, flags);
|
|
|
|
/* Collect ram dumps for all sysevents in order here */
|
|
for_each_sysevent_device(list, count, NULL, sysevent_ramdump);
|
|
|
|
for_each_sysevent_device(list, count, NULL, sysevent_free_memory);
|
|
|
|
notify_each_sysevent_device(list, count, SYSTEM_EVENT_BEFORE_POWERUP, NULL);
|
|
ret = for_each_sysevent_device(list, count, NULL, sysevent_powerup);
|
|
if (ret)
|
|
goto err;
|
|
notify_each_sysevent_device(list, count, SYSTEM_EVENT_AFTER_POWERUP, NULL);
|
|
|
|
pr_info("[%s:%d]: Restart sequence for %s completed.\n",
|
|
current->comm, current->pid, desc->name);
|
|
|
|
err:
|
|
/* Reset sysevent count */
|
|
if (ret)
|
|
dev->count = 0;
|
|
|
|
mutex_unlock(&track->lock);
|
|
|
|
spin_lock_irqsave(&track->s_lock, flags);
|
|
track->p_state = SYSTEM_EVENT_NORMAL;
|
|
__pm_relax(dev->sysevent_wlock);
|
|
spin_unlock_irqrestore(&track->s_lock, flags);
|
|
}
|
|
|
|
static void __sysevent_restart_dev(struct sysevent_device *dev)
|
|
{
|
|
struct sysevent_desc *desc = dev->desc;
|
|
const char *name = dev->desc->name;
|
|
struct sysevent_tracking *track;
|
|
unsigned long flags;
|
|
|
|
pr_debug("Restarting %s [level=%s]!\n", desc->name,
|
|
restart_levels[dev->restart_level]);
|
|
|
|
track = sysevent_get_track(dev);
|
|
/*
|
|
* Allow drivers to call sysevent_restart{_dev}() as many times as
|
|
* they want up until the point where the sysevent is shutdown.
|
|
*/
|
|
spin_lock_irqsave(&track->s_lock, flags);
|
|
if (track->p_state != SYSTEM_EVENT_CRASHED &&
|
|
dev->track.state == SYSTEM_EVENT_ONLINE) {
|
|
if (track->p_state != SYSTEM_EVENT_RESTARTING) {
|
|
track->p_state = SYSTEM_EVENT_CRASHED;
|
|
__pm_stay_awake(dev->sysevent_wlock);
|
|
queue_work(sysevent_wq, &dev->work);
|
|
} else {
|
|
panic("sysevent %s crashed during sysevent!", name);
|
|
}
|
|
} else
|
|
WARN(dev->track.state == SYSTEM_EVENT_OFFLINE,
|
|
"sysevent aborted: %s sysevent not online\n", name);
|
|
spin_unlock_irqrestore(&track->s_lock, flags);
|
|
}
|
|
|
|
static void device_restart_work_hdlr(struct work_struct *work)
|
|
{
|
|
struct sysevent_device *dev = container_of(work, struct sysevent_device,
|
|
device_restart_work);
|
|
|
|
notify_each_sysevent_device(&dev, 1, SYSTEM_EVENT_SOC_RESET, NULL);
|
|
/*
|
|
* Temporary workaround until ramdump userspace application calls
|
|
* sync() and fclose() on atpting the dump.
|
|
*/
|
|
msleep(100);
|
|
panic("sysevent-restart: Resetting the SoC - %s crashed.",
|
|
dev->desc->name);
|
|
}
|
|
|
|
int sysevent_restart_dev(struct sysevent_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;
|
|
|
|
send_early_notifications(dev->early_notify);
|
|
|
|
/*
|
|
* If a system reboot/shutdown is underway, ignore sysevent errors.
|
|
* However, print a message so that we know that a sysevent 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]);
|
|
|
|
|
|
/* TODO - If sysevent is not support, This function must be needed */
|
|
if (disable_sysevent_support == true) {
|
|
pr_warn("sysevent-restart: Ignoring restart request for %s\n",
|
|
name);
|
|
return 0;
|
|
}
|
|
|
|
/* TODO - modify for exynos */
|
|
switch (dev->restart_level) {
|
|
|
|
case RESET_SYSTEM_EVENT_COUPLED:
|
|
__sysevent_restart_dev(dev);
|
|
break;
|
|
case RESET_SOC:
|
|
__pm_stay_awake(dev->sysevent_wlock);
|
|
schedule_work(&dev->device_restart_work);
|
|
return 0;
|
|
default:
|
|
panic("sysevent-restart: Unknown restart level!\n");
|
|
break;
|
|
}
|
|
/* Till here */
|
|
|
|
module_put(dev->owner);
|
|
put_device(&dev->dev);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(sysevent_restart_dev);
|
|
|
|
int sysevent_restart(const char *name)
|
|
{
|
|
int ret;
|
|
struct sysevent_device *dev = find_sysevent_device(name);
|
|
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
ret = sysevent_restart_dev(dev);
|
|
put_device(&dev->dev);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(sysevent_restart);
|
|
|
|
int sysevent_crashed(const char *name)
|
|
{
|
|
struct sysevent_device *dev = find_sysevent_device(name);
|
|
struct sysevent_tracking *track;
|
|
|
|
if (!dev)
|
|
return -ENODEV;
|
|
|
|
if (!get_device(&dev->dev))
|
|
return -ENODEV;
|
|
|
|
track = sysevent_get_track(dev);
|
|
|
|
mutex_lock(&track->lock);
|
|
dev->do_ramdump_on_put = true;
|
|
/*
|
|
* TODO: Make this work with multiple consumers where one is calling
|
|
* sysevent_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(sysevent_crashed);
|
|
|
|
void sysevent_set_crash_status(struct sysevent_device *dev,
|
|
enum crash_status crashed)
|
|
{
|
|
dev->crashed = crashed;
|
|
}
|
|
EXPORT_SYMBOL(sysevent_set_crash_status);
|
|
|
|
enum crash_status sysevent_get_crash_status(struct sysevent_device *dev)
|
|
{
|
|
return dev->crashed;
|
|
}
|
|
|
|
static int sysevent_device_open(struct inode *inode, struct file *file)
|
|
{
|
|
struct sysevent_device *device, *sysevent_dev = 0;
|
|
void *retval;
|
|
|
|
mutex_lock(&sysevent_list_lock);
|
|
list_for_each_entry(device, &sysevent_list, list)
|
|
if (MINOR(device->dev_no) == iminor(inode))
|
|
sysevent_dev = device;
|
|
mutex_unlock(&sysevent_list_lock);
|
|
|
|
if (!sysevent_dev)
|
|
return -EINVAL;
|
|
|
|
retval = sysevent_get_with_fwname(sysevent_dev->desc->name,
|
|
sysevent_dev->desc->fw_name);
|
|
if (IS_ERR(retval))
|
|
return PTR_ERR(retval);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysevent_device_close(struct inode *inode, struct file *file)
|
|
{
|
|
struct sysevent_device *device, *sysevent_dev = 0;
|
|
|
|
mutex_lock(&sysevent_list_lock);
|
|
list_for_each_entry(device, &sysevent_list, list)
|
|
if (MINOR(device->dev_no) == iminor(inode))
|
|
sysevent_dev = device;
|
|
mutex_unlock(&sysevent_list_lock);
|
|
|
|
if (!sysevent_dev)
|
|
return -EINVAL;
|
|
|
|
sysevent_put(sysevent_dev);
|
|
return 0;
|
|
}
|
|
|
|
static const struct file_operations sysevent_device_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = sysevent_device_open,
|
|
.release = sysevent_device_close,
|
|
};
|
|
|
|
static void sysevent_device_release(struct device *dev)
|
|
{
|
|
struct sysevent_device *sysevent = to_sysevent(dev);
|
|
|
|
wakeup_source_unregister(sysevent->sysevent_wlock);
|
|
|
|
mutex_destroy(&sysevent->track.lock);
|
|
ida_simple_remove(&sysevent_ida, sysevent->id);
|
|
kfree(sysevent);
|
|
}
|
|
|
|
static int sysevent_char_device_add(struct sysevent_device *sysevent_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, "sysevent");
|
|
if (ret < 0) {
|
|
pr_err("Failed to alloc sysevent_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, sysevent_dev->desc->dev, dev_no,
|
|
NULL, "sysevent_%s", sysevent_dev->desc->name)) {
|
|
pr_err("Failed to create sysevent_%s device\n",
|
|
sysevent_dev->desc->name);
|
|
goto fail_unregister_cdev_region;
|
|
}
|
|
|
|
cdev_init(&sysevent_dev->char_dev, &sysevent_device_fops);
|
|
sysevent_dev->char_dev.owner = THIS_MODULE;
|
|
ret = cdev_add(&sysevent_dev->char_dev, dev_no, 1);
|
|
if (ret < 0)
|
|
goto fail_destroy_device;
|
|
|
|
sysevent_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 sysevent_char_device_remove(struct sysevent_device *sysevent_dev)
|
|
{
|
|
cdev_del(&sysevent_dev->char_dev);
|
|
device_destroy(char_class, sysevent_dev->dev_no);
|
|
unregister_chrdev_region(sysevent_dev->dev_no, 1);
|
|
}
|
|
|
|
static int __get_irq(struct sysevent_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 sysevent_parse_devicetree(struct sysevent_desc *desc)
|
|
{
|
|
int ret;
|
|
struct platform_device *pdev = container_of(desc->dev,
|
|
struct platform_device, dev);
|
|
|
|
ret = __get_irq(desc, "samsung,err-fatal", &desc->err_fatal_irq);
|
|
if (ret && ret != -ENOENT)
|
|
return ret;
|
|
|
|
ret = __get_irq(desc, "samsung,watchdog", &desc->watchdog_irq);
|
|
if (ret && ret != -ENOENT)
|
|
return ret;
|
|
|
|
if (of_property_read_bool(pdev->dev.of_node,
|
|
"samsung,sysevent-generic-irq-handler")) {
|
|
ret = platform_get_irq(pdev, 0);
|
|
if (ret > 0)
|
|
desc->generic_irq = ret;
|
|
}
|
|
|
|
desc->ignore_sysevent_failure = of_property_read_bool(pdev->dev.of_node,
|
|
"samsung,ignore-sysevent-failure");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int sysevent_setup_irqs(struct sysevent_device *sysevent)
|
|
{
|
|
struct sysevent_desc *desc = sysevent->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->watchdog_irq && desc->watchdog_handler) {
|
|
ret = devm_request_irq(desc->dev, desc->watchdog_irq,
|
|
desc->watchdog_handler,
|
|
IRQF_TRIGGER_RISING, desc->name, desc);
|
|
if (ret < 0) {
|
|
dev_err(desc->dev, "[%s]: Unable to register watchdog bite handler: %d\n",
|
|
desc->name, ret);
|
|
return ret;
|
|
}
|
|
disable_irq(desc->watchdog_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);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void sysevent_free_irqs(struct sysevent_device *sysevent)
|
|
{
|
|
struct sysevent_desc *desc = sysevent->desc;
|
|
|
|
if (desc->err_fatal_irq && desc->err_fatal_handler)
|
|
devm_free_irq(desc->dev, desc->err_fatal_irq, desc);
|
|
if (desc->watchdog_irq && desc->watchdog_handler)
|
|
devm_free_irq(desc->dev, desc->watchdog_irq, desc);
|
|
}
|
|
|
|
struct sysevent_device *sysevent_register(struct sysevent_desc *desc)
|
|
{
|
|
struct sysevent_device *sysevent;
|
|
struct device_node *ofnode = desc->dev->of_node;
|
|
int ret;
|
|
|
|
sysevent = kzalloc(sizeof(*sysevent), GFP_KERNEL);
|
|
if (!sysevent)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
sysevent->desc = desc;
|
|
sysevent->owner = desc->owner;
|
|
sysevent->dev.parent = desc->dev;
|
|
sysevent->dev.bus = &sysevent_bus_type;
|
|
sysevent->dev.release = sysevent_device_release;
|
|
sysevent->notif_state = -1;
|
|
strlcpy(sysevent->desc->fw_name, desc->name,
|
|
sizeof(sysevent->desc->fw_name));
|
|
|
|
sysevent->notify = sysevent_notif_add_sysevent(desc->name);
|
|
sysevent->early_notify = sysevent_get_early_notif_info(desc->name);
|
|
|
|
snprintf(sysevent->wlname, sizeof(sysevent->wlname), "sysevent(%s)", desc->name);
|
|
sysevent->sysevent_wlock = wakeup_source_register(&sysevent->dev, sysevent->wlname);
|
|
|
|
INIT_WORK(&sysevent->work, sysevent_restart_wq_func);
|
|
INIT_WORK(&sysevent->device_restart_work, device_restart_work_hdlr);
|
|
spin_lock_init(&sysevent->track.s_lock);
|
|
spin_lock_init(&sysevent->desc->sysevent_sysfs_lock);
|
|
|
|
sysevent->id = ida_simple_get(&sysevent_ida, 0, 0, GFP_KERNEL);
|
|
if (sysevent->id < 0) {
|
|
ret = sysevent->id;
|
|
kfree(sysevent);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
dev_set_name(&sysevent->dev, "sysevent%d", sysevent->id);
|
|
|
|
mutex_init(&sysevent->track.lock);
|
|
|
|
ret = device_register(&sysevent->dev);
|
|
if (ret) {
|
|
put_device(&sysevent->dev);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
ret = sysevent_char_device_add(sysevent);
|
|
if (ret)
|
|
goto err_register;
|
|
|
|
if (ofnode) {
|
|
ret = sysevent_parse_devicetree(desc);
|
|
if (ret)
|
|
goto err_register;
|
|
}
|
|
|
|
mutex_lock(&sysevent_list_lock);
|
|
INIT_LIST_HEAD(&sysevent->list);
|
|
list_add_tail(&sysevent->list, &sysevent_list);
|
|
mutex_unlock(&sysevent_list_lock);
|
|
|
|
if (ofnode) {
|
|
ret = sysevent_setup_irqs(sysevent);
|
|
if (ret < 0)
|
|
goto err_setup_irqs;
|
|
}
|
|
|
|
return sysevent;
|
|
err_setup_irqs:
|
|
err_register:
|
|
device_unregister(&sysevent->dev);
|
|
return ERR_PTR(ret);
|
|
}
|
|
EXPORT_SYMBOL(sysevent_register);
|
|
|
|
void sysevent_unregister(struct sysevent_device *sysevent)
|
|
{
|
|
struct sysevent_device *sysevent_dev, *tmp;
|
|
struct device_node *device = sysevent->desc->dev->of_node;
|
|
|
|
if (IS_ERR_OR_NULL(sysevent))
|
|
return;
|
|
|
|
if (get_device(&sysevent->dev)) {
|
|
mutex_lock(&sysevent_list_lock);
|
|
list_for_each_entry_safe(sysevent_dev, tmp, &sysevent_list, list)
|
|
if (sysevent_dev == sysevent)
|
|
list_del(&sysevent->list);
|
|
mutex_unlock(&sysevent_list_lock);
|
|
|
|
if (device)
|
|
sysevent_free_irqs(sysevent);
|
|
|
|
mutex_lock(&sysevent->track.lock);
|
|
WARN_ON(sysevent->count);
|
|
device_unregister(&sysevent->dev);
|
|
mutex_unlock(&sysevent->track.lock);
|
|
sysevent_char_device_remove(sysevent);
|
|
put_device(&sysevent->dev);
|
|
}
|
|
}
|
|
EXPORT_SYMBOL(sysevent_unregister);
|
|
|
|
static int sysevent_panic(struct device *dev, void *data)
|
|
{
|
|
struct sysevent_device *sysevent = to_sysevent(dev);
|
|
|
|
if (sysevent->desc->crash_shutdown)
|
|
sysevent->desc->crash_shutdown(sysevent->desc);
|
|
return 0;
|
|
}
|
|
|
|
static int sysevent_panic_handler(struct notifier_block *this,
|
|
unsigned long event, void *ptr)
|
|
{
|
|
bus_for_each_dev(&sysevent_bus_type, NULL, NULL, sysevent_panic);
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
static struct notifier_block panic_nb = {
|
|
.notifier_call = sysevent_panic_handler,
|
|
};
|
|
|
|
static int __init sysevent_init(void)
|
|
{
|
|
int ret;
|
|
|
|
sysevent_wq = alloc_workqueue("sysevent_wq",
|
|
WQ_UNBOUND | WQ_HIGHPRI | WQ_CPU_INTENSIVE, 0);
|
|
BUG_ON(!sysevent_wq);
|
|
|
|
ret = bus_register(&sysevent_bus_type);
|
|
if (ret)
|
|
goto err_bus;
|
|
|
|
char_class = class_create(THIS_MODULE, "sysevent");
|
|
if (IS_ERR(char_class)) {
|
|
ret = -ENOMEM;
|
|
pr_err("Failed to create sysevent_dev class\n");
|
|
goto err_class;
|
|
}
|
|
|
|
ret = atomic_notifier_chain_register(&panic_notifier_list,
|
|
&panic_nb);
|
|
if (ret)
|
|
goto err_soc;
|
|
|
|
return 0;
|
|
|
|
err_soc:
|
|
class_destroy(char_class);
|
|
err_class:
|
|
bus_unregister(&sysevent_bus_type);
|
|
err_bus:
|
|
destroy_workqueue(sysevent_wq);
|
|
return ret;
|
|
}
|
|
arch_initcall(sysevent_init);
|
|
|
|
MODULE_AUTHOR("Hosung Kim <hosung0.kim@samsung.com>");
|
|
MODULE_AUTHOR("Changki Kim <changki.kim@samsung.com>");
|
|
MODULE_AUTHOR("Donghyeok Choe <d7271.choe@samsung.com>");
|
|
MODULE_DESCRIPTION("System Event Framework Driver");
|
|
MODULE_LICENSE("GPL v2");
|