984 lines
24 KiB
C
984 lines
24 KiB
C
/*
|
|
* Copyright (C) 2016 MediaTek Inc.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License 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 http://www.gnu.org/licenses/gpl-2.0.html for more details.
|
|
*/
|
|
|
|
#include <linux/version.h>
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/module.h>
|
|
#include <linux/init.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <linux/string.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/spinlock.h>
|
|
#include <linux/rtc.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/jiffies.h>
|
|
#ifdef CONFIG_MTK_AEE_IPANIC
|
|
#include <mt-plat/aee.h>
|
|
#endif
|
|
#ifdef CONFIG_MTK_AEE_IPANIC
|
|
#include <mt-plat/mtk_ram_console.h>
|
|
#endif
|
|
#include <linux/tick.h>
|
|
#include <mt-plat/mtk_gpt.h>
|
|
#include <ext_wd_drv.h>
|
|
#include <mt-plat/mtk_wd_api.h>
|
|
#include <linux/seq_file.h>
|
|
|
|
/*************************************************************************
|
|
* Feature configure region
|
|
*************************************************************************/
|
|
#define __ENABLE_WDT_SYSFS__
|
|
#define __ENABLE_WDT_AT_INIT__
|
|
#define KWDT_KICK_TIME_ALIGN
|
|
/* ------------------------------------------------------------------------ */
|
|
#define PFX "wdk: "
|
|
#define DEBUG_WDK 0
|
|
#if DEBUG_WDK
|
|
#define dbgmsg(msg...) pr_debug(PFX msg)
|
|
#else
|
|
#define dbgmsg(...)
|
|
#endif
|
|
#define msg(msg...) pr_info(PFX msg)
|
|
#define warnmsg(msg...) pr_info(PFX msg)
|
|
#define errmsg(msg...) pr_notice(PFX msg)
|
|
|
|
#define WK_MAX_MSG_SIZE (128)
|
|
#define MIN_KICK_INTERVAL 1
|
|
#define MAX_KICK_INTERVAL 30
|
|
#define SOFT_KICK_RANGE (100*1000) // 100ms
|
|
#define MRDUMP_SYSRESETB 0
|
|
#define MRDUMP_EINTRST 1
|
|
#define PROC_WK "wdk"
|
|
#define PROC_MRDUMP_RST "mrdump_rst"
|
|
|
|
__weak void mtk_wdt_cpu_callback(struct task_struct *wk_tsk,
|
|
unsigned long action, int hotcpu, int kicker_init)
|
|
{
|
|
}
|
|
|
|
__weak void mtk_timer_clkevt_aee_dump(void)
|
|
{
|
|
}
|
|
|
|
static int kwdt_thread(void *arg);
|
|
static int start_kicker(void);
|
|
|
|
static int g_kicker_init;
|
|
static int debug_sleep;
|
|
|
|
static DEFINE_SPINLOCK(lock);
|
|
|
|
#define CPU_NR (nr_cpu_ids)
|
|
struct task_struct *wk_tsk[16] = { 0 }; /* max cpu 16 */
|
|
static unsigned int wk_tsk_bind[16] = { 0 }; /* max cpu 16 */
|
|
static unsigned long long wk_tsk_bind_time[16] = { 0 }; /* max cpu 16 */
|
|
static char wk_tsk_buf[128] = { 0 };
|
|
|
|
static unsigned long kick_bit;
|
|
static unsigned long rtc_update;
|
|
|
|
enum ext_wdt_mode g_wk_wdt_mode = WDT_DUAL_MODE;
|
|
static struct wd_api *g_wd_api;
|
|
static int g_kinterval = -1;
|
|
static int g_timeout = -1;
|
|
static int g_need_config;
|
|
static int wdt_start;
|
|
int g_enable = 1;
|
|
static struct work_struct wdk_work;
|
|
static struct workqueue_struct *wdk_workqueue;
|
|
static unsigned int lasthpg_act;
|
|
static unsigned int lasthpg_cpu;
|
|
static unsigned long long lasthpg_t;
|
|
#ifdef KWDT_KICK_TIME_ALIGN
|
|
static unsigned long g_curInterval;
|
|
static unsigned long g_nxtKickTime;
|
|
#endif
|
|
static char cmd_buf[256];
|
|
|
|
|
|
static int wk_proc_cmd_read(struct seq_file *s, void *v)
|
|
{
|
|
seq_printf(s, "mode interval timeout enable\n%-4d %-9d %-8d %-7d\n",
|
|
g_wk_wdt_mode, g_kinterval, g_timeout, g_enable);
|
|
return 0;
|
|
}
|
|
|
|
static int wk_proc_cmd_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, wk_proc_cmd_read, NULL);
|
|
}
|
|
|
|
static ssize_t wk_proc_cmd_write(struct file *file, const char *buf,
|
|
size_t count, loff_t *data)
|
|
{
|
|
int ret;
|
|
int timeout;
|
|
int mode;
|
|
int kinterval;
|
|
int en; /* enable or disable ext wdt 1<-->enable 0<-->disable */
|
|
struct wd_api *my_wd_api = NULL;
|
|
|
|
ret = get_wd_api(&my_wd_api);
|
|
if (ret)
|
|
pr_debug("get public api error in wd common driver %d", ret);
|
|
|
|
if (count == 0)
|
|
return -1;
|
|
|
|
if (count > 255)
|
|
count = 255;
|
|
|
|
ret = copy_from_user(cmd_buf, buf, count);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
cmd_buf[count] = '\0';
|
|
|
|
pr_debug("Write %s\n", cmd_buf);
|
|
|
|
ret = sscanf(cmd_buf, "%d %d %d %d %d", &mode,
|
|
&kinterval, &timeout, &debug_sleep, &en);
|
|
|
|
pr_debug("[wdk] mode=%d interval=%d timeout=%d enable =%d\n",
|
|
mode, kinterval, timeout, en);
|
|
|
|
if (timeout < kinterval) {
|
|
pr_info("Interval(%d) need smaller than timeout value(%d)\n",
|
|
kinterval, timeout);
|
|
return -1;
|
|
}
|
|
|
|
if ((timeout < MIN_KICK_INTERVAL) || (timeout > MAX_KICK_INTERVAL)) {
|
|
pr_info("The timeout(%d) is invalid (%d - %d)\n", kinterval,
|
|
MIN_KICK_INTERVAL, MAX_KICK_INTERVAL);
|
|
return -1;
|
|
}
|
|
|
|
if ((kinterval < MIN_KICK_INTERVAL) ||
|
|
(kinterval > MAX_KICK_INTERVAL)) {
|
|
pr_info("The interval(%d) is invalid (%d - %d)\n", kinterval,
|
|
MIN_KICK_INTERVAL, MAX_KICK_INTERVAL);
|
|
return -1;
|
|
}
|
|
|
|
if (!((mode == WDT_IRQ_ONLY_MODE) ||
|
|
(mode == WDT_HW_REBOOT_ONLY_MODE) || (mode == WDT_DUAL_MODE))) {
|
|
pr_info("Tha watchdog kicker wdt mode is not correct %d\n",
|
|
mode);
|
|
return -1;
|
|
}
|
|
|
|
if (en == 1) {
|
|
mtk_wdt_enable(WK_WDT_EN);
|
|
#ifdef CONFIG_LOCAL_WDT
|
|
local_wdt_enable(WK_WDT_EN);
|
|
pr_debug("[wdk] enable local wdt\n");
|
|
#endif
|
|
pr_debug("[wdk] enable wdt\n");
|
|
}
|
|
if (en == 0) {
|
|
mtk_wdt_enable(WK_WDT_DIS);
|
|
#ifdef CONFIG_LOCAL_WDT
|
|
local_wdt_enable(WK_WDT_DIS);
|
|
pr_debug("[wdk] disable local wdt\n");
|
|
#endif
|
|
pr_debug("[wdk] disable wdt\n");
|
|
}
|
|
|
|
spin_lock(&lock);
|
|
|
|
g_enable = en;
|
|
g_kinterval = kinterval;
|
|
|
|
g_wk_wdt_mode = mode;
|
|
if (mode == 1) {
|
|
/* irq mode only useful to 75 */
|
|
mtk_wdt_swsysret_config(0x20000000, 1);
|
|
pr_debug("[wdk] use irq mod\n");
|
|
} else if (mode == 0) {
|
|
/* reboot mode only useful to 75 */
|
|
mtk_wdt_swsysret_config(0x20000000, 0);
|
|
pr_debug("[wdk] use reboot mod\n");
|
|
} else if (mode == 2)
|
|
my_wd_api->wd_set_mode(WDT_IRQ_ONLY_MODE);
|
|
else
|
|
pr_debug("[wdk] mode err\n");
|
|
|
|
g_timeout = timeout;
|
|
if (mode != 2)
|
|
g_need_config = 1;
|
|
|
|
spin_unlock(&lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
static int mrdump_proc_cmd_read(struct seq_file *s, void *v)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int mrdump_proc_cmd_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, mrdump_proc_cmd_read, NULL);
|
|
}
|
|
|
|
static ssize_t mrdump_proc_cmd_write(struct file *file,
|
|
const char *buf, size_t count, loff_t *data)
|
|
{
|
|
int ret = 0;
|
|
int mrdump_rst_source;
|
|
int en, mode;/* enable or disable ext wdt 1<-->enable 0<-->disable */
|
|
char mrdump_cmd_buf[256];
|
|
struct wd_api *my_wd_api = NULL;
|
|
|
|
ret = get_wd_api(&my_wd_api);
|
|
if (ret)
|
|
pr_debug("get public api error in wd common driver %d", ret);
|
|
|
|
if (count == 0)
|
|
return -1;
|
|
|
|
if (count > 255)
|
|
count = 255;
|
|
|
|
ret = copy_from_user(mrdump_cmd_buf, buf, count);
|
|
if (ret < 0)
|
|
return -1;
|
|
|
|
mrdump_cmd_buf[count] = '\0';
|
|
|
|
dbgmsg("Write %s\n", mrdump_cmd_buf);
|
|
|
|
ret = sscanf(mrdump_cmd_buf, "%d %d %d",
|
|
&mrdump_rst_source, &mode, &en);
|
|
if (ret != 3)
|
|
pr_debug("%s: expect 3 numbers\n", __func__);
|
|
|
|
pr_debug("[MRDUMP] rst_source=%d mode=%d enable=%d\n",
|
|
mrdump_rst_source, mode, en);
|
|
|
|
if (mrdump_rst_source > 1) {
|
|
errmsg("mrdump_rst_source(%d) value need smaller than 2\n",
|
|
mrdump_rst_source);
|
|
return -1;
|
|
}
|
|
|
|
if (mode > 1) {
|
|
errmsg("mrdump_rst_mode(%d) value should be smaller than 2\n",
|
|
mode);
|
|
return -1;
|
|
}
|
|
|
|
spin_lock(&lock);
|
|
if (mrdump_rst_source == MRDUMP_SYSRESETB) {
|
|
ret = my_wd_api->wd_debug_key_sysrst_config(en, mode);
|
|
} else if (mrdump_rst_source == MRDUMP_EINTRST) {
|
|
ret = my_wd_api->wd_debug_key_eint_config(en, mode);
|
|
} else {
|
|
pr_debug("[MRDUMP] invalid mrdump_rst_source\n");
|
|
ret = -1;
|
|
}
|
|
spin_unlock(&lock);
|
|
|
|
if (ret == 0)
|
|
pr_debug("[MRDUMP] MRDUMP External success\n");
|
|
else
|
|
pr_debug("[MRDUMP] MRDUMP External key not support!\n");
|
|
|
|
return count;
|
|
}
|
|
|
|
static int start_kicker_thread_with_default_setting(void)
|
|
{
|
|
int ret = 0;
|
|
|
|
spin_lock(&lock);
|
|
|
|
g_kinterval = 20; /* default interval: 20s */
|
|
|
|
g_need_config = 0;/* Note, we DO NOT want to call configure function */
|
|
|
|
wdt_start = 1; /* Start once only */
|
|
rtc_update = jiffies; /* update rtc_update time base*/
|
|
|
|
spin_unlock(&lock);
|
|
start_kicker();
|
|
|
|
pr_debug("[wdk] start_kicker_thread_with_default_setting done\n");
|
|
return ret;
|
|
}
|
|
|
|
static unsigned int cpus_kick_bit;
|
|
void wk_start_kick_cpu(int cpu)
|
|
{
|
|
if (IS_ERR(wk_tsk[cpu])) {
|
|
pr_debug("[wdk] wk_task[%d] is NULL\n", cpu);
|
|
} else {
|
|
kthread_bind(wk_tsk[cpu], cpu);
|
|
pr_info("[wdk] bind thread %d to cpu %d\n",
|
|
wk_tsk[cpu]->pid, cpu);
|
|
wake_up_process(wk_tsk[cpu]);
|
|
}
|
|
}
|
|
|
|
void dump_wdk_bind_info(void)
|
|
{
|
|
int i = 0;
|
|
|
|
#ifdef CONFIG_MTK_AEE_IPANIC
|
|
aee_sram_fiq_log("\n");
|
|
#endif
|
|
for (i = 0; i < CPU_NR; i++) {
|
|
if (wk_tsk[i] != NULL) {
|
|
/*
|
|
* pr_info("[wdk]CPU %d, %d, %lld, %lu, %d, %ld\n",
|
|
* i, wk_tsk_bind[i], wk_tsk_bind_time[i],
|
|
* wk_tsk[i]->cpus_allowed.bits[0],
|
|
* wk_tsk[i]->on_rq, wk_tsk[i]->state);
|
|
*/
|
|
memset(wk_tsk_buf, 0, sizeof(wk_tsk_buf));
|
|
snprintf(wk_tsk_buf, sizeof(wk_tsk_buf),
|
|
"[wdk]CPU %d, %d, %lld, %lu, %d, %ld\n",
|
|
i, wk_tsk_bind[i], wk_tsk_bind_time[i],
|
|
wk_tsk[i]->cpus_allowed.bits[0],
|
|
wk_tsk[i]->on_rq, wk_tsk[i]->state);
|
|
#ifdef CONFIG_MTK_AEE_IPANIC
|
|
aee_sram_fiq_log(wk_tsk_buf);
|
|
#endif
|
|
}
|
|
}
|
|
#ifdef CONFIG_MTK_AEE_IPANIC
|
|
aee_sram_fiq_log("\n");
|
|
#endif
|
|
mtk_timer_clkevt_aee_dump();
|
|
tick_broadcast_mtk_aee_dump();
|
|
}
|
|
|
|
void kicker_cpu_bind(int cpu)
|
|
{
|
|
if (IS_ERR(wk_tsk[cpu]))
|
|
pr_debug("[wdk]wk_task[%d] is NULL\n", cpu);
|
|
else {
|
|
/* kthread_bind(wk_tsk[cpu], cpu); */
|
|
WARN_ON_ONCE(set_cpus_allowed_ptr(wk_tsk[cpu],
|
|
cpumask_of(cpu)) < 0);
|
|
wake_up_process(wk_tsk[cpu]);
|
|
wk_tsk_bind[cpu] = 1;
|
|
wk_tsk_bind_time[cpu] = sched_clock();
|
|
}
|
|
}
|
|
|
|
void wk_cpu_update_bit_flag(int cpu, int plug_status)
|
|
{
|
|
if (plug_status == 1) { /* plug on */
|
|
spin_lock(&lock);
|
|
cpus_kick_bit |= (1 << cpu);
|
|
kick_bit = 0;
|
|
lasthpg_cpu = cpu;
|
|
lasthpg_act = plug_status;
|
|
lasthpg_t = sched_clock();
|
|
spin_unlock(&lock);
|
|
}
|
|
if (plug_status == 0) { /* plug off */
|
|
spin_lock(&lock);
|
|
cpus_kick_bit &= (~(1 << cpu));
|
|
kick_bit = 0;
|
|
lasthpg_cpu = cpu;
|
|
lasthpg_act = plug_status;
|
|
lasthpg_t = sched_clock();
|
|
wk_tsk_bind[cpu] = 0;
|
|
spin_unlock(&lock);
|
|
}
|
|
}
|
|
|
|
unsigned int wk_check_kick_bit(void)
|
|
{
|
|
return cpus_kick_bit;
|
|
}
|
|
|
|
static const struct file_operations wk_proc_cmd_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = wk_proc_cmd_open,
|
|
.read = seq_read,
|
|
.write = wk_proc_cmd_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
static const struct file_operations mrdump_rst_proc_cmd_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = mrdump_proc_cmd_open,
|
|
.read = seq_read,
|
|
.write = mrdump_proc_cmd_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
int wk_proc_init(void)
|
|
{
|
|
|
|
struct proc_dir_entry *de = NULL;
|
|
|
|
de = proc_create(PROC_WK, 0660, NULL, &wk_proc_cmd_fops);
|
|
|
|
if (!de)
|
|
pr_debug("[wk_proc_init]: create /proc/wdk failed\n");
|
|
|
|
de = proc_create(PROC_MRDUMP_RST, 0660, NULL,
|
|
&mrdump_rst_proc_cmd_fops);
|
|
if (!de)
|
|
pr_debug("[wk_proc_init]: create /proc/mrdump_rst failed\n");
|
|
|
|
pr_debug("[wdk] Initialize proc\n");
|
|
|
|
/* de->read_proc = wk_proc_cmd_read; */
|
|
/* de->write_proc = wk_proc_cmd_write; */
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
void wk_proc_exit(void)
|
|
{
|
|
|
|
remove_proc_entry(PROC_WK, NULL);
|
|
|
|
}
|
|
|
|
static void kwdt_print_utc(char *msg_buf, int msg_buf_size)
|
|
{
|
|
struct rtc_time tm;
|
|
struct timeval tv = { 0 };
|
|
/* android time */
|
|
struct rtc_time tm_android;
|
|
struct timeval tv_android = { 0 };
|
|
|
|
do_gettimeofday(&tv);
|
|
tv_android = tv;
|
|
rtc_time_to_tm(tv.tv_sec, &tm);
|
|
tv_android.tv_sec -= sys_tz.tz_minuteswest * 60;
|
|
rtc_time_to_tm(tv_android.tv_sec, &tm_android);
|
|
snprintf(msg_buf, msg_buf_size,
|
|
"[thread:%d][RT:%lld] %d-%02d-%02d %02d:%02d:%02d.%u UTC;"
|
|
"android time %d-%02d-%02d %02d:%02d:%02d.%03d\n",
|
|
current->pid, sched_clock(), tm.tm_year + 1900, tm.tm_mon + 1,
|
|
tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
|
|
(unsigned int)tv.tv_usec, tm_android.tm_year + 1900,
|
|
tm_android.tm_mon + 1, tm_android.tm_mday, tm_android.tm_hour,
|
|
tm_android.tm_min, tm_android.tm_sec,
|
|
(unsigned int)tv_android.tv_usec);
|
|
}
|
|
static void kwdt_process_kick(int local_bit, int cpu, char msg_buf[])
|
|
{
|
|
local_bit = kick_bit;
|
|
if ((local_bit & (1 << cpu)) == 0) {
|
|
/* pr_debug("[wdk] set kick_bit\n"); */
|
|
local_bit |= (1 << cpu);
|
|
/* aee_rr_rec_wdk_kick_jiffies(jiffies); */
|
|
}
|
|
|
|
/*
|
|
* do not print message with spinlock held to
|
|
* avoid bulk of delayed printk happens here
|
|
*/
|
|
snprintf(msg_buf, WK_MAX_MSG_SIZE,
|
|
"[wdk-c] cpu=%d,lbit=0x%x,cbit=0x%x,%d,%d,%lld,[%lld]\n",
|
|
cpu, local_bit, wk_check_kick_bit(), lasthpg_cpu, lasthpg_act,
|
|
lasthpg_t, sched_clock());
|
|
|
|
if (local_bit == wk_check_kick_bit()) {
|
|
msg_buf[5] = 'k';
|
|
mtk_wdt_restart(WD_TYPE_NORMAL);/* for KICK external wdt */
|
|
local_bit = 0;
|
|
}
|
|
|
|
kick_bit = local_bit;
|
|
spin_unlock(&lock);
|
|
|
|
/*
|
|
* [wdt-c]: mark local bit only.
|
|
* [wdt-k]: kick watchdog actaully, this log is more important thus
|
|
* using printk_deferred to ensure being printed.
|
|
*/
|
|
if (msg_buf[5] != 'k')
|
|
pr_info("%s", msg_buf);
|
|
else
|
|
printk_deferred("%s", msg_buf);
|
|
|
|
#ifdef CONFIG_LOCAL_WDT
|
|
printk_deferred("[wdk] cpu:%d, kick local wdt,RT[%lld]\n",
|
|
cpu, sched_clock());
|
|
/* kick local wdt */
|
|
mpcore_wdt_restart(WD_TYPE_NORMAL);
|
|
#endif
|
|
}
|
|
|
|
static int kwdt_thread(void *arg)
|
|
{
|
|
struct sched_param param = {.sched_priority = 99 };
|
|
int cpu = 0;
|
|
int local_bit = 0, loc_need_config = 0, loc_timeout = 0;
|
|
struct wd_api *loc_wk_wdt = NULL;
|
|
char msg_buf[WK_MAX_MSG_SIZE];
|
|
|
|
sched_setscheduler(current, SCHED_FIFO, ¶m);
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
|
|
|
for (;;) {
|
|
|
|
if (kthread_should_stop()) {
|
|
pr_info("[wdk] kthread_should_stop do !!\n");
|
|
break;
|
|
}
|
|
|
|
msg_buf[0] = '\0';
|
|
|
|
spin_lock(&lock);
|
|
loc_wk_wdt = g_wd_api;
|
|
loc_need_config = g_need_config;
|
|
loc_timeout = g_timeout;
|
|
spin_unlock(&lock);
|
|
|
|
/*
|
|
* pr_debug("[wdk] loc_wk_wdt(%x),loc_wk_wdt->ready(%d)\n",
|
|
* loc_wk_wdt ,loc_wk_wdt->ready);
|
|
*/
|
|
|
|
if (loc_wk_wdt && loc_wk_wdt->ready && g_enable) {
|
|
if (loc_need_config) {
|
|
/* daul mode */
|
|
loc_wk_wdt->wd_config(WDT_DUAL_MODE,
|
|
loc_timeout);
|
|
spin_lock(&lock);
|
|
g_need_config = 0;
|
|
spin_unlock(&lock);
|
|
}
|
|
/*
|
|
* pr_debug("[wdk] cpu-task=%d, current_pid=%d\n",
|
|
* wk_tsk[cpu]->pid, current->pid);
|
|
*/
|
|
|
|
spin_lock(&lock);
|
|
|
|
/* smp_processor_id does not
|
|
* allowed preemptible context
|
|
*/
|
|
cpu = smp_processor_id();
|
|
|
|
/* to avoid wk_tsk[cpu] had not created out */
|
|
if (wk_tsk[cpu] != 0) {
|
|
|
|
/* only process kicking info
|
|
* if thread-x is on cpu-x
|
|
*/
|
|
if (wk_tsk[cpu]->pid == current->pid) {
|
|
#ifdef KWDT_KICK_TIME_ALIGN
|
|
if (kick_bit == 0) {
|
|
g_nxtKickTime =
|
|
ktime_to_us(ktime_get())
|
|
+ g_kinterval*1000*1000;
|
|
g_curInterval =
|
|
g_kinterval*1000*1000;
|
|
} else {
|
|
g_curInterval = g_nxtKickTime
|
|
- ktime_to_us(ktime_get());
|
|
}
|
|
#endif
|
|
kwdt_process_kick(local_bit, cpu,
|
|
msg_buf);
|
|
} else
|
|
spin_unlock(&lock);
|
|
} else
|
|
spin_unlock(&lock);
|
|
} else if (g_enable == 0) {
|
|
pr_debug("[wdk] stop to kick\n");
|
|
} else {
|
|
pr_info("[wdk] no wdt driver is hooked\n");
|
|
WARN_ON(1);
|
|
}
|
|
|
|
/* to avoid wk_tsk[cpu] had not created out */
|
|
if (wk_tsk[cpu] != 0) {
|
|
if (wk_tsk[cpu]->pid == current->pid) {
|
|
#if (DEBUG_WDK == 1)
|
|
msleep_interruptible(debug_sleep * 1000);
|
|
pr_debug("[wdk] wdk woke up %d\n",
|
|
debug_sleep);
|
|
#endif
|
|
/* limit the rtc time update frequency */
|
|
spin_lock(&lock);
|
|
msg_buf[0] = '\0';
|
|
if (time_after(jiffies, rtc_update)) {
|
|
rtc_update = jiffies + (1 * HZ);
|
|
kwdt_print_utc(msg_buf,
|
|
WK_MAX_MSG_SIZE);
|
|
}
|
|
spin_unlock(&lock);
|
|
|
|
/*
|
|
* do not print message with spinlock held to
|
|
* avoid bulk of delayed printk happens here
|
|
*/
|
|
if (msg_buf[0] != '\0')
|
|
pr_info("%s", msg_buf);
|
|
}
|
|
}
|
|
|
|
#ifdef KWDT_KICK_TIME_ALIGN
|
|
/* to avoid interval too long (should not happen */
|
|
if (g_curInterval > g_kinterval*1000*1000)
|
|
g_curInterval = g_kinterval*1000*1000;
|
|
usleep_range(g_curInterval, g_curInterval + SOFT_KICK_RANGE);
|
|
#else
|
|
usleep_range(g_kinterval*1000*1000,
|
|
g_kinterval*1000*1000 + SOFT_KICK_RANGE);
|
|
#endif
|
|
#ifdef CONFIG_MTK_AEE_POWERKEY_HANG_DETECT
|
|
if ((cpu == 0) && (wk_tsk[cpu]->pid == current->pid)) {
|
|
/* only effect at cpu0 */
|
|
if (aee_kernel_wdt_kick_api(g_kinterval) ==
|
|
WDT_PWK_HANG_FORCE_HWT) {
|
|
printk_deferred("power key trigger HWT\n");
|
|
/* Try to force to HWT */
|
|
cpus_kick_bit = 0xFFFF;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
pr_debug("[wdk] wdk thread stop, cpu:%d, pid:%d\n", cpu, current->pid);
|
|
return 0;
|
|
}
|
|
|
|
static int start_kicker(void)
|
|
{
|
|
|
|
int i;
|
|
|
|
wk_cpu_update_bit_flag(0, 1);
|
|
for (i = 0; i < CPU_NR; i++) {
|
|
wk_tsk[i] = kthread_create(kwdt_thread,
|
|
(void *)(unsigned long)i, "wdtk-%d", i);
|
|
if (IS_ERR(wk_tsk[i])) {
|
|
int ret = PTR_ERR(wk_tsk[i]);
|
|
|
|
wk_tsk[i] = NULL;
|
|
pr_info("[wdk]kthread_create failed, wdtk-%d\n", i);
|
|
return ret;
|
|
}
|
|
/* wk_cpu_update_bit_flag(i,1); */
|
|
wk_start_kick_cpu(i);
|
|
}
|
|
g_kicker_init = 1;
|
|
pr_info("[wdk] WDT start kicker done CPU_NR=%d\n", CPU_NR);
|
|
return 0;
|
|
}
|
|
|
|
unsigned int get_check_bit(void)
|
|
{
|
|
return wk_check_kick_bit();
|
|
}
|
|
|
|
unsigned int get_kick_bit(void)
|
|
{
|
|
return kick_bit;
|
|
}
|
|
|
|
|
|
/******************************************************************************
|
|
* SYSFS support
|
|
*****************************************************************************/
|
|
#ifdef __ENABLE_WDT_SYSFS__
|
|
/*---------------------------------------------------------------------------*/
|
|
/*define sysfs entry for configuring debug level and sysrq*/
|
|
const struct sysfs_ops mtk_rgu_sysfs_ops = {
|
|
.show = mtk_rgu_attr_show,
|
|
.store = mtk_rgu_attr_store,
|
|
};
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
struct mtk_rgu_sys_entry {
|
|
struct attribute attr;
|
|
ssize_t (*show)(struct kobject *kobj, char *page);
|
|
ssize_t (*store)(struct kobject *kobj, const char *page, size_t size);
|
|
};
|
|
/*---------------------------------------------------------------------------*/
|
|
static struct mtk_rgu_sys_entry pause_wdt_entry = {
|
|
{.name = "pause", .mode = 0644},
|
|
mtk_rgu_pause_wdt_show,
|
|
mtk_rgu_pause_wdt_store,
|
|
};
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
struct attribute *mtk_rgu_attributes[] = {
|
|
&pause_wdt_entry.attr,
|
|
NULL,
|
|
};
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
struct kobj_type mtk_rgu_ktype = {
|
|
.sysfs_ops = &mtk_rgu_sysfs_ops,
|
|
.default_attrs = mtk_rgu_attributes,
|
|
};
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
static struct mtk_rgu_sysobj {
|
|
struct kobject kobj;
|
|
} rgu_sysobj;
|
|
/*---------------------------------------------------------------------------*/
|
|
int mtk_rgu_sysfs(void)
|
|
{
|
|
struct mtk_rgu_sysobj *obj = &rgu_sysobj;
|
|
|
|
memset(&obj->kobj, 0x00, sizeof(obj->kobj));
|
|
|
|
obj->kobj.parent = kernel_kobj;
|
|
if (kobject_init_and_add(&obj->kobj, &mtk_rgu_ktype, NULL, "mtk_rgu")) {
|
|
kobject_put(&obj->kobj);
|
|
return -ENOMEM;
|
|
}
|
|
kobject_uevent(&obj->kobj, KOBJ_ADD);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
ssize_t mtk_rgu_attr_show(struct kobject *kobj,
|
|
struct attribute *attr, char *buffer)
|
|
{
|
|
struct mtk_rgu_sys_entry *entry = NULL;
|
|
|
|
entry = container_of(attr, struct mtk_rgu_sys_entry, attr);
|
|
|
|
return entry->show(kobj, buffer);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
ssize_t mtk_rgu_attr_store(struct kobject *kobj,
|
|
struct attribute *attr, const char *buffer,
|
|
size_t size)
|
|
{
|
|
struct mtk_rgu_sys_entry *entry = NULL;
|
|
|
|
entry = container_of(attr, struct mtk_rgu_sys_entry, attr);
|
|
|
|
return entry->store(kobj, buffer, size);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
ssize_t mtk_rgu_pause_wdt_show(struct kobject *kobj, char *buffer)
|
|
{
|
|
int remain = PAGE_SIZE;
|
|
int len = 0;
|
|
char *ptr = buffer;
|
|
|
|
ptr += len;
|
|
remain -= len;
|
|
|
|
return (PAGE_SIZE - remain);
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
ssize_t mtk_rgu_pause_wdt_store(struct kobject *kobj,
|
|
const char *buffer, size_t size)
|
|
{
|
|
char pause_wdt;
|
|
int pause_wdt_b;
|
|
int res = sscanf(buffer, "%c", &pause_wdt);
|
|
|
|
pause_wdt_b = pause_wdt;
|
|
|
|
if (res != 1) {
|
|
pr_info("%s: expect 1 numbers\n", __func__);
|
|
} else {
|
|
/* For real case, pause wdt if get value is not zero.
|
|
* Suspend and resume may enable wdt again
|
|
*/
|
|
if (pause_wdt_b)
|
|
mtk_wdt_enable(WK_WDT_DIS);
|
|
}
|
|
return size;
|
|
}
|
|
|
|
/*---------------------------------------------------------------------------*/
|
|
#endif /*__ENABLE_WDT_SYSFS__*/
|
|
/*---------------------------------------------------------------------------*/
|
|
|
|
static int wk_cpu_callback(struct notifier_block *nfb,
|
|
unsigned long action, void *hcpu)
|
|
{
|
|
int hotcpu = (unsigned long)hcpu;
|
|
|
|
switch (action) {
|
|
case CPU_UP_PREPARE:
|
|
case CPU_UP_PREPARE_FROZEN:
|
|
/* Update CPU mask in WDT driver */
|
|
wk_cpu_update_bit_flag(hotcpu, 1);
|
|
|
|
/*
|
|
* Kick WDT here because this CPU may be blocked awhile
|
|
* between PREPARE and ONLINE states. If HWT happens
|
|
* during this period, we may be confused by WDT CPU
|
|
* kicking status: it will indicate "2 CPUs did not
|
|
* kick WDT". This makes us hard to debug.
|
|
*/
|
|
mtk_wdt_restart(WD_TYPE_NORMAL);
|
|
|
|
#ifdef CONFIG_LOCAL_WDT
|
|
pr_debug("[wdk]cpu %d plug on kick local wdt\n", hotcpu);
|
|
/* kick local wdt */
|
|
mpcore_wdt_restart(WD_TYPE_NORMAL);
|
|
#endif
|
|
/* pr_info("[wdk]cpu %d plug on kick wdt\n", hotcpu); */
|
|
break;
|
|
case CPU_ONLINE:
|
|
case CPU_ONLINE_FROZEN:
|
|
/*
|
|
* Bind WDK thread to this CPU.
|
|
* NOTE: Thread binding must be executed after CPU is ready
|
|
* (online).
|
|
*/
|
|
if (g_kicker_init == 1)
|
|
kicker_cpu_bind(hotcpu);
|
|
|
|
/*
|
|
* DO NOT kick WDT here because we do not expect long execution
|
|
* time between PREPARE and ONLINE.
|
|
*
|
|
* Long execution time between PREPARE and ONLINE may cause HWT
|
|
* after CPU is ONLINE, i.e., after thread binding. In this
|
|
* case, HWT is required to report this unexpected issue.
|
|
*/
|
|
break;
|
|
#ifdef CONFIG_HOTPLUG_CPU
|
|
#ifdef CONFIG_LOCAL_WDT
|
|
/* must kick local wdt in per cpu */
|
|
case CPU_DYING:
|
|
/* fall-through */
|
|
#endif
|
|
case CPU_UP_CANCELED:
|
|
/* fall-through */
|
|
case CPU_UP_CANCELED_FROZEN:
|
|
/* fall-through */
|
|
case CPU_DEAD:
|
|
/* fall-through */
|
|
case CPU_DEAD_FROZEN:
|
|
mtk_wdt_restart(WD_TYPE_NORMAL);/* for KICK external wdt */
|
|
#ifdef CONFIG_LOCAL_WDT
|
|
pr_debug("[wdk]cpu %d plug off kick local wdt\n", hotcpu);
|
|
/* kick local wdt */
|
|
/* mpcore_wdt_restart(WD_TYPE_NORMAL); */
|
|
/* disable local watchdog */
|
|
mpcore_wk_wdt_stop();
|
|
#endif
|
|
wk_cpu_update_bit_flag(hotcpu, 0);
|
|
/* pr_info("[wdk]cpu %d plug off, kick wdt\n", hotcpu); */
|
|
break;
|
|
#endif /* CONFIG_HOTPLUG_CPU */
|
|
default:
|
|
return NOTIFY_DONE;
|
|
}
|
|
|
|
mtk_wdt_cpu_callback(wk_tsk[hotcpu], action, hotcpu, g_kicker_init);
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block cpu_nfb = {
|
|
.notifier_call = wk_cpu_callback,
|
|
.priority = 6
|
|
};
|
|
|
|
static void wdk_work_callback(struct work_struct *work)
|
|
{
|
|
int res = 0;
|
|
int i = 0;
|
|
/* init api */
|
|
wd_api_init();
|
|
/* */
|
|
res = get_wd_api(&g_wd_api);
|
|
if (res)
|
|
pr_info("get public api error in wd common driver %d", res);
|
|
|
|
#ifdef __ENABLE_WDT_SYSFS__
|
|
mtk_rgu_sysfs();
|
|
#endif
|
|
|
|
cpu_hotplug_disable();
|
|
|
|
wk_proc_init();
|
|
register_cpu_notifier(&cpu_nfb);
|
|
|
|
for (i = 0; i < CPU_NR; i++) {
|
|
if (cpu_online(i)) {
|
|
wk_cpu_update_bit_flag(i, 1);
|
|
pr_debug("[wdk]init cpu online %d\n", i);
|
|
} else {
|
|
wk_cpu_update_bit_flag(i, 0);
|
|
pr_debug("[wdk]init cpu offline %d\n", i);
|
|
}
|
|
}
|
|
mtk_wdt_restart(WD_TYPE_NORMAL); /* for KICK external wdt */
|
|
cpu_hotplug_enable();
|
|
|
|
#ifdef __ENABLE_WDT_AT_INIT__
|
|
start_kicker_thread_with_default_setting();
|
|
#endif
|
|
|
|
pr_info("[wdk]init_wk done late_initcall cpus_kick_bit=0x%x -----\n",
|
|
cpus_kick_bit);
|
|
|
|
}
|
|
|
|
static int __init init_wk(void)
|
|
{
|
|
int res = 0;
|
|
|
|
wdk_workqueue = create_singlethread_workqueue("mt-wdk");
|
|
INIT_WORK(&wdk_work, wdk_work_callback);
|
|
|
|
res = queue_work(wdk_workqueue, &wdk_work);
|
|
|
|
if (!res)
|
|
pr_info("[wdk]wdk_work start return:%d!\n", res);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void __exit exit_wk(void)
|
|
{
|
|
wk_proc_exit();
|
|
kthread_stop((struct task_struct *)wk_tsk);
|
|
}
|
|
|
|
static int __init init_wk_check_bit(void)
|
|
{
|
|
int i = 0;
|
|
|
|
pr_debug("[wdk]arch init check_bit=0x%x+++++\n", cpus_kick_bit);
|
|
for (i = 0; i < CPU_NR; i++)
|
|
wk_cpu_update_bit_flag(i, 1);
|
|
|
|
pr_debug("[wdk]arch init check_bit=0x%x-----\n", cpus_kick_bit);
|
|
return 0;
|
|
}
|
|
|
|
late_initcall(init_wk);
|
|
arch_initcall(init_wk_check_bit);
|
|
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_AUTHOR("Mediatek inc.");
|
|
MODULE_DESCRIPTION("The watchdog kicker");
|