blob: 3a121088f6417b8ccf5ce8982ab9795b0cffbce5 [file] [log] [blame]
/*
* Copyright (C) 2013 HTC Corporation. All rights reserved.
*
* @file /kernel/drivers/htc_debug/stability/reboot_params.c
*
* This software is distributed under dual licensing. These include
* the GNU General Public License version 2 and a commercial
* license of HTC. HTC reserves the right to change the license
* of future releases.
*
* Unless you and HTC execute a separate written software license
* agreement governing use of this software, this software is licensed
* to you under the terms of the GNU General Public License version 2,
* available at {link to GPL license term} (the "GPL").
*/
#include <linux/kernel.h>
#include <linux/ctype.h>
#include <linux/platform_device.h>
#include <linux/of_platform.h>
#include <linux/reboot.h>
#include <linux/of_fdt.h>
#include <linux/sched.h>
#include <linux/kdebug.h>
#include <linux/notifier.h>
#include <linux/kallsyms.h>
#include <asm/io.h>
/* These constants are used in bootloader to decide actions. */
#define RESTART_REASON_BOOT_BASE (0x77665500)
#define RESTART_REASON_BOOTLOADER (RESTART_REASON_BOOT_BASE | 0x00)
#define RESTART_REASON_REBOOT (RESTART_REASON_BOOT_BASE | 0x01)
#define RESTART_REASON_RECOVERY (RESTART_REASON_BOOT_BASE | 0x02)
#define RESTART_REASON_HTCBL (RESTART_REASON_BOOT_BASE | 0x03)
#define RESTART_REASON_OFFMODE (RESTART_REASON_BOOT_BASE | 0x04)
#define RESTART_REASON_RAMDUMP (RESTART_REASON_BOOT_BASE | 0xAA)
#define RESTART_REASON_HARDWARE (RESTART_REASON_BOOT_BASE | 0xA0)
#define RESTART_REASON_POWEROFF (RESTART_REASON_BOOT_BASE | 0xBB)
/*
* The RESTART_REASON_OEM_BASE is used for oem commands.
* The actual value is parsed from reboot commands.
* RIL FATAL will use oem-99 to restart a device.
*/
#define RESTART_REASON_OEM_BASE (0x6f656d00)
#define RESTART_REASON_RIL_FATAL (RESTART_REASON_OEM_BASE | 0x99)
#define SZ_DIAG_ERR_MSG (128)
struct htc_reboot_params {
u32 reboot_reason;
u32 radio_flag;
u32 battery_level;
char msg[SZ_DIAG_ERR_MSG];
char reserved[0];
};
static struct htc_reboot_params* reboot_params = NULL;
static void set_restart_reason(u32 reason)
{
reboot_params->reboot_reason = reason;
}
static void set_restart_msg(const char *msg)
{
char* buf;
size_t buf_len;
if (unlikely(!msg)) {
WARN(1, "%s: argument msg is NULL\n", __func__);
msg = "";
}
buf = reboot_params->msg;
buf_len = sizeof(reboot_params->msg);
pr_debug("copy buffer from %pK (%s) to %pK for %zu bytes\n",
msg, msg, buf, min(strlen(msg), buf_len - 1));
snprintf(buf, buf_len, "%s", msg);
}
static struct cmd_reason_map {
char* cmd;
u32 reason;
} cmd_reason_map[] = {
{ .cmd = "", .reason = RESTART_REASON_REBOOT },
{ .cmd = "bootloader", .reason = RESTART_REASON_BOOTLOADER },
{ .cmd = "recovery", .reason = RESTART_REASON_RECOVERY },
{ .cmd = "offmode", .reason = RESTART_REASON_OFFMODE },
{ .cmd = "poweroff", .reason = RESTART_REASON_POWEROFF },
{ .cmd = "force-hard", .reason = RESTART_REASON_RAMDUMP },
};
#define OEM_CMD_FMT "oem-%02x"
static void set_restart_command(const char* command)
{
int code;
int i;
if (unlikely(!command)) {
WARN(1, "%s: command is NULL\n", __func__);
command = "";
}
/* standard reboot command */
for (i = 0; i < ARRAY_SIZE(cmd_reason_map); i++)
if (!strcmp(command, cmd_reason_map[i].cmd)) {
set_restart_msg(cmd_reason_map[i].cmd);
set_restart_reason(cmd_reason_map[i].reason);
return;
}
/* oem reboot command */
if (1 == sscanf(command, OEM_CMD_FMT, &code)) {
/* oem-97, 98, 99 are RIL fatal */
if ((code == 0x97) || (code == 0x98))
code = 0x99;
set_restart_msg(command);
set_restart_reason(RESTART_REASON_OEM_BASE | code);
return;
}
/* unknown reboot command */
pr_warn("Unknown restart command: %s\n", command);
set_restart_msg("");
set_restart_reason(RESTART_REASON_REBOOT);
}
static int reboot_callback(struct notifier_block *nb,
unsigned long event, void *data)
{
/*
* NOTE: `data' is NULL when reboot w/o command or shutdown
*/
char* cmd;
cmd = (char*) (data ? data : "");
pr_debug("restart command: %s\n", data ? cmd : "<null>");
switch (event) {
case SYS_RESTART:
set_restart_command(cmd);
pr_info("syscall: reboot - current task: %s (%d:%d)\n",
current->comm, current->tgid, current->pid);
dump_stack();
break;
case SYS_HALT:
case SYS_POWER_OFF:
default:
/*
* - Clear reboot_params to prevent unnessary RAM issue.
* - Set it to 'offmode' instead of 'poweroff' since
* it is required to make device enter offmode charging
* if cable attached
*/
set_restart_command("offmode");
break;
}
return NOTIFY_DONE;
}
static struct notifier_block reboot_notifier = {
.notifier_call = reboot_callback,
};
static struct die_args *tombstone = NULL;
int die_notify(struct notifier_block *self,
unsigned long val, void *data)
{
static struct die_args args;
memcpy(&args, data, sizeof(args));
tombstone = &args;
pr_debug("saving oops: %p\n", (void*) tombstone);
return NOTIFY_DONE;
}
static struct notifier_block die_nb = {
.notifier_call = die_notify,
};
static int panic_event(struct notifier_block *this,
unsigned long event, void *ptr)
{
char msg_buf[SZ_DIAG_ERR_MSG];
if (tombstone) { // tamper the panic message for Oops
char pc_symn[KSYM_NAME_LEN] = "<unknown>";
char lr_symn[KSYM_NAME_LEN] = "<unknown>";
#if defined(CONFIG_ARM)
sprint_symbol(pc_symn, tombstone->regs->ARM_pc);
sprint_symbol(lr_symn, tombstone->regs->ARM_lr);
#elif defined(CONFIG_ARM64)
sprint_symbol(pc_symn, tombstone->regs->pc);
sprint_symbol(lr_symn, tombstone->regs->regs[30]);
#endif
snprintf(msg_buf, sizeof(msg_buf),
"KP: %s PC:%s LR:%s",
current->comm,
pc_symn, lr_symn);
} else {
snprintf(msg_buf, sizeof(msg_buf),
"KP: %s", (const char*) ptr);
}
set_restart_msg((const char*) msg_buf);
return NOTIFY_DONE;
}
static struct notifier_block panic_block = {
.notifier_call = panic_event,
};
static int htc_reboot_params_probe(struct platform_device *pdev)
{
struct resource *param;
param = platform_get_resource_byname(pdev, IORESOURCE_MEM, "reboot_params");
if (param && resource_size(param) >= sizeof(struct htc_reboot_params)) {
reboot_params = ioremap_nocache(param->start, resource_size(param));
if (reboot_params) {
dev_info(&pdev->dev, "got reboot_params buffer at %p\n", reboot_params);
/*
* reboot_params is initialized by bootloader, so it's ready to
* register the reboot / die / panic handlers.
*/
register_reboot_notifier(&reboot_notifier);
register_die_notifier(&die_nb);
atomic_notifier_chain_register(&panic_notifier_list, &panic_block);
} else
dev_warn(&pdev->dev,
"failed to map resource `reboot_params': %pR\n", param);
}
return 0;
}
#define MODULE_NAME "htc_reboot_params"
static struct of_device_id htc_reboot_params_dt_match_table[] = {
{
.compatible = MODULE_NAME
},
{},
};
static struct platform_driver htc_reboot_params_driver = {
.driver = {
.name = MODULE_NAME,
.of_match_table = htc_reboot_params_dt_match_table,
},
.probe = htc_reboot_params_probe,
};
static int __init htc_reboot_params_init(void)
{
return platform_driver_register(&htc_reboot_params_driver);
}
core_initcall(htc_reboot_params_init);