You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
kernel_samsung_sm7125/drivers/samsung/debug/sec_debug_partition.c

607 lines
16 KiB

// SPDX-License-Identifier: GPL-2.0
/*
* drivers/samsung/debug/sec_debug_partition.c
*
* COPYRIGHT(C) 2006-2019 Samsung Electronics Co., Ltd. All Right Reserved.
*
* 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) KBUILD_MODNAME ":%s() " fmt, __func__
#include <linux/module.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/file.h>
#include <linux/syscalls.h>
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/sec_debug.h>
#define PRINT_MSG_CYCLE 20
/* single global instance */
struct debug_partition_data_s sched_debug_data;
static int in_panic;
static int driver_initialized;
static int ap_health_initialized;
static struct delayed_work dbg_partition_notify_work;
static struct workqueue_struct *dbg_part_wq;
static struct delayed_work ap_health_work;
static ap_health_t ap_health_data;
static DEFINE_MUTEX(ap_health_work_lock);
static DEFINE_MUTEX(debug_partition_mutex);
static BLOCKING_NOTIFIER_HEAD(dbg_partition_notifier_list);
static char debugpartition_path[60];
static struct {
uint32_t offset;
uint32_t size;
uint32_t default_size;
} debug_part_table[DEBUG_PART_MAX_TABLE] = {
[debug_index_reset_header] = {.offset = SEC_DEBUG_RESET_HEADER_OFFSET,
.size = sizeof(struct debug_reset_header)},
[debug_index_reset_ex_info] = {.offset = SEC_DEBUG_EXTRA_INFO_OFFSET,
.size = SEC_DEBUG_EX_INFO_SIZE},
[debug_index_ap_health] = {.offset = SEC_DEBUG_AP_HEALTH_OFFSET,
.size = SEC_DEBUG_AP_HEALTH_SIZE},
[debug_index_lcd_debug_info] = {.offset = SEC_DEBUG_LCD_DEBUG_OFFSET,
.size = sizeof(struct lcd_debug_t)},
[debug_index_reset_history] = {.offset = SEC_DEBUG_RESET_HISTORY_OFFSET,
.size = SEC_DEBUG_RESET_HISTORY_SIZE},
[debug_index_onoff_history] = {.offset = SEC_DEBUG_ONOFF_HISTORY_OFFSET,
.size = sizeof(onoff_history_t)},
[debug_index_reset_tzlog] = {.offset = SEC_DEBUG_RESET_TZLOG_OFFSET,
.size = SEC_DEBUG_RESET_TZLOG_SIZE},
[debug_index_reset_extrc_info] = {.offset = SEC_DEBUG_RESET_EXTRC_OFFSET,
.size = SEC_DEBUG_RESET_EXTRC_SIZE},
[debug_index_auto_comment] = {.offset = SEC_DEBUG_AUTO_COMMENT_OFFSET,
.size = SEC_DEBUG_AUTO_COMMENT_SIZE},
[debug_index_reset_rkplog] = {.offset = SEC_DEBUG_RESET_ETRM_OFFSET,
.size = SEC_DEBUG_RESET_ETRM_SIZE},
[debug_index_modem_info] = {.offset = SEC_DEBUG_RESET_MODEM_OFFSET,
.size = sizeof(struct sec_debug_summary_data_modem)},
[debug_index_reset_klog] = {.offset = SEC_DEBUG_RESET_KLOG_OFFSET,
.size = SEC_DEBUG_RESET_KLOG_SIZE},
[debug_index_reset_lpm_klog] = {.offset = SEC_DEBUG_RESET_LPM_KLOG_OFFSET,
.size = SEC_DEBUG_RESET_LPM_KLOG_SIZE},
[debug_index_reset_summary] = {.offset = SEC_DEBUG_RESET_SUMMARY_OFFSET,
.default_size = SEC_DEBUG_RESET_SUMMARY_SIZE},
};
static int __init get_bootdevice(char *str)
{
snprintf(debugpartition_path, sizeof(debugpartition_path),
"/dev/block/platform/soc/%s/by-name/debug", str);
return 0;
}
early_param("androidboot.bootdevice", get_bootdevice);
static void debug_partition_operation(struct work_struct *work)
{
int ret;
struct file *filp;
mm_segment_t fs;
struct debug_partition_data_s *sched_data =
container_of(work, struct debug_partition_data_s,
debug_partition_work);
int flag = (sched_data->direction == PARTITION_WR) ?
(O_RDWR | O_SYNC) : O_RDONLY;
static unsigned int err_cnt;
if (!sched_data->value) {
pr_err("%p %x %d %d - value is NULL!!\n",
sched_data->value, sched_data->offset,
sched_data->size, sched_data->direction);
sched_data->error = -ENODATA;
goto sched_data_err;
}
fs = get_fs();
set_fs(KERNEL_DS);
sched_data->error = 0;
filp = filp_open(debugpartition_path, flag, 0);
if (IS_ERR(filp)) {
if (!(err_cnt++ % PRINT_MSG_CYCLE))
pr_err("filp_open failed: %ld[%u]\n",
PTR_ERR(filp), err_cnt);
sched_data->error = PTR_ERR(filp);
goto filp_err;
}
ret = vfs_llseek(filp, sched_data->offset, SEEK_SET);
if (ret < 0) {
pr_err("FAIL LLSEEK\n");
sched_data->error = ret;
goto llseek_err;
}
if (sched_data->direction == PARTITION_RD)
vfs_read(filp, (char __user *)sched_data->value,
sched_data->size, &filp->f_pos);
else if (sched_data->direction == PARTITION_WR)
vfs_write(filp, (char __user *)sched_data->value,
sched_data->size, &filp->f_pos);
llseek_err:
filp_close(filp, NULL);
filp_err:
set_fs(fs);
sched_data_err:
complete(&sched_data->work);
}
static void ap_health_work_write_fn(struct work_struct *work)
{
int ret;
struct file *filp;
mm_segment_t fs;
unsigned long delay = 5 * HZ;
static unsigned int err_cnt;
pr_info("start.\n");
if (!mutex_trylock(&ap_health_work_lock)) {
pr_err("already locked.\n");
delay = 2 * HZ;
goto occupied_retry;
}
fs = get_fs();
set_fs(KERNEL_DS);
filp = filp_open(debugpartition_path, (O_RDWR | O_SYNC), 0);
if (IS_ERR(filp)) {
if (!(err_cnt++ % PRINT_MSG_CYCLE))
pr_err("filp_open failed: %ld[%u]\n",
PTR_ERR(filp), err_cnt);
goto openfail_retry;
}
ret = vfs_llseek(filp, debug_part_table[debug_index_ap_health].offset, SEEK_SET);
if (ret < 0) {
pr_err("FAIL LLSEEK\n");
ret = false;
goto seekfail_retry;
}
vfs_write(filp, (char __user *)&ap_health_data,
sizeof(ap_health_t), &filp->f_pos);
if (--ap_health_data.header.need_write)
goto remained;
filp_close(filp, NULL);
set_fs(fs);
mutex_unlock(&ap_health_work_lock);
pr_info("end.\n");
return;
remained:
seekfail_retry:
filp_close(filp, NULL);
openfail_retry:
set_fs(fs);
mutex_unlock(&ap_health_work_lock);
occupied_retry:
queue_delayed_work(dbg_part_wq, &ap_health_work, delay);
pr_info("end, will retry, wr(%u).\n",
ap_health_data.header.need_write);
}
static bool init_lcd_debug_data(void)
{
int ret = true, retry = 0;
struct lcd_debug_t lcd_debug;
pr_info("start\n");
memset((void *)&lcd_debug, 0, sizeof(struct lcd_debug_t));
pr_info("lcd_debug size[%zu]\n", sizeof(struct lcd_debug_t));
do {
if (retry++) {
pr_err("will retry...\n");
msleep(1000);
}
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = &lcd_debug;
sched_debug_data.offset = debug_part_table[debug_index_lcd_debug_info].offset;
sched_debug_data.size = debug_part_table[debug_index_lcd_debug_info].size;
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
} while (sched_debug_data.error);
pr_info("end\n");
return ret;
}
static void init_ap_health_data(void)
{
pr_info("start\n");
memset((void *)&ap_health_data, 0, sizeof(ap_health_t));
ap_health_data.header.magic = AP_HEALTH_MAGIC;
ap_health_data.header.version = AP_HEALTH_VER;
ap_health_data.header.size = sizeof(ap_health_t);
ap_health_data.spare_magic1 = AP_HEALTH_MAGIC;
ap_health_data.spare_magic2 = AP_HEALTH_MAGIC;
ap_health_data.spare_magic3 = AP_HEALTH_MAGIC;
while (1) {
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = &ap_health_data;
sched_debug_data.offset = debug_part_table[debug_index_ap_health].offset;
sched_debug_data.size = debug_part_table[debug_index_ap_health].size;
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
if (!sched_debug_data.error)
break;
mutex_unlock(&debug_partition_mutex);
msleep(1000);
}
mutex_unlock(&debug_partition_mutex);
pr_info("end\n");
}
static void init_debug_partition(void)
{
struct debug_reset_header init_reset_header;
pr_info("start\n");
/*++ add here need init data ++*/
init_ap_health_data();
init_lcd_debug_data();
/*-- add here need init data --*/
while (1) {
mutex_lock(&debug_partition_mutex);
memset(&init_reset_header, 0,
sizeof(struct debug_reset_header));
init_reset_header.magic = DEBUG_PARTITION_MAGIC;
sched_debug_data.value = &init_reset_header;
sched_debug_data.offset = debug_part_table[debug_index_reset_header].offset;
sched_debug_data.size = debug_part_table[debug_index_reset_header].size;
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
if (!sched_debug_data.error)
break;
mutex_unlock(&debug_partition_mutex);
msleep(1000);
}
mutex_unlock(&debug_partition_mutex);
pr_info("end\n");
}
static int check_magic_data(void)
{
static int checked_magic;
struct debug_reset_header partition_header = {0,};
int ret = 0;
if (checked_magic)
return ret;
pr_info("start\n");
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = &partition_header;
sched_debug_data.offset = debug_part_table[debug_index_reset_header].offset;
sched_debug_data.size = debug_part_table[debug_index_reset_header].size;
sched_debug_data.direction = PARTITION_RD;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
ret = sched_debug_data.error;
mutex_unlock(&debug_partition_mutex);
if (ret)
goto out;
if (partition_header.magic != DEBUG_PARTITION_MAGIC)
init_debug_partition();
checked_magic = 1;
out:
pr_info("end - %d\n", ret);
return ret;
}
#define READ_DEBUG_PARTITION(_value, _offset, _size) \
{ \
mutex_lock(&debug_partition_mutex); \
sched_debug_data.value = _value; \
sched_debug_data.offset = _offset; \
sched_debug_data.size = _size; \
sched_debug_data.direction = PARTITION_RD; \
schedule_work(&sched_debug_data.debug_partition_work); \
wait_for_completion(&sched_debug_data.work); \
mutex_unlock(&debug_partition_mutex); \
}
bool read_debug_partition(enum debug_partition_index index, void *value)
{
if (check_magic_data())
return false;
if (index < debug_index_max) {
if (debug_part_table[index].size) {
READ_DEBUG_PARTITION(value,
debug_part_table[index].offset,
debug_part_table[index].size);
return true;
}
}
return false;
}
bool write_debug_partition(enum debug_partition_index index, void *value)
{
if (check_magic_data())
return false;
if (index < debug_index_max) {
if (index == debug_index_reset_klog_info
|| index == debug_index_reset_summary_info
|| index == debug_index_lcd_debug_info
|| index == debug_index_reset_klog
|| index == debug_index_reset_lpm_klog
|| index == debug_index_onoff_history) {
if (debug_part_table[index].size) {
mutex_lock(&debug_partition_mutex);
sched_debug_data.value = value;
sched_debug_data.offset = debug_part_table[index].offset;
sched_debug_data.size = debug_part_table[index].size;
sched_debug_data.direction = PARTITION_WR;
schedule_work(&sched_debug_data.debug_partition_work);
wait_for_completion(&sched_debug_data.work);
mutex_unlock(&debug_partition_mutex);
return true;
}
}
}
return false;
}
static int is_boot_recovery;
static int __init boot_recovery(char *str)
{
if (get_option(&str, &is_boot_recovery))
return 0;
return -EINVAL;
}
early_param("androidboot.boot_recovery", boot_recovery);
ap_health_t *ap_health_data_read(void)
{
if (!driver_initialized)
return NULL;
if (ap_health_initialized)
goto out;
if (in_interrupt()) {
pr_info("skip read opt.\n");
return NULL;
}
if (read_debug_partition(debug_index_ap_health, (void *)&ap_health_data) == false)
return NULL;
if (ap_health_data.header.magic != AP_HEALTH_MAGIC ||
ap_health_data.header.version != AP_HEALTH_VER ||
ap_health_data.header.size != sizeof(ap_health_t) ||
ap_health_data.spare_magic1 != AP_HEALTH_MAGIC ||
ap_health_data.spare_magic2 != AP_HEALTH_MAGIC ||
ap_health_data.spare_magic3 != AP_HEALTH_MAGIC ||
is_boot_recovery) {
init_ap_health_data();
init_lcd_debug_data();
}
ap_health_initialized = 1;
out:
return &ap_health_data;
}
int ap_health_data_write(ap_health_t *data)
{
if (!driver_initialized || !data || !ap_health_initialized)
return -ENODATA;
data->header.need_write++;
if (!in_panic)
queue_delayed_work(dbg_part_wq, &ap_health_work, 0);
return 0;
}
int dbg_partition_notifier_register(struct notifier_block *nb)
{
return blocking_notifier_chain_register(
&dbg_partition_notifier_list, nb);
}
static void debug_partition_do_notify(struct work_struct *work)
{
if (check_magic_data()) {
schedule_delayed_work(&dbg_partition_notify_work, 2 * HZ);
return;
}
blocking_notifier_call_chain(&dbg_partition_notifier_list,
DBG_PART_DRV_INIT_DONE, NULL);
}
static int dbg_partition_panic_prepare(struct notifier_block *nb,
unsigned long event, void *data)
{
in_panic = 1;
return NOTIFY_DONE;
}
static struct notifier_block dbg_partition_panic_notifier_block = {
.notifier_call = dbg_partition_panic_prepare,
};
uint32_t dbg_parttion_get_part_size(uint32_t idx)
{
if (idx < DEBUG_PART_MAX_TABLE)
return debug_part_table[idx].size;
return 0;
}
static int dbg_partition_fix_part_table(void)
{
int i;
for (i = 0; i < DEBUG_PART_MAX_TABLE; i++) {
if (!debug_part_table[i].size)
debug_part_table[i].size = debug_part_table[i].default_size;
}
return 0;
}
static int dbg_partition_make_part_table(void)
{
struct device_node *parent;
int i, ret = 0;
uint32_t offset, size;
parent = of_find_node_by_path("/sec_debug_partition");
if (!parent) {
pr_err("sec_debug_partition node is not in device tree\n");
return -ENODEV;
}
of_get_property(parent, "part-table", &size);
if (!size) {
pr_err("part-table node is not in device tree\n");
return -ENODEV;
}
if (size != DEBUG_PART_MAX_TABLE * 2 * sizeof(u32)) {
pr_err("part-table has wrong size\n");
return -EINVAL;
}
for (i = 0; i < DEBUG_PART_MAX_TABLE; i++) {
ret = of_property_read_u32_index(parent, "part-table", i * 2, &offset);
if (ret) {
pr_err("part-table %d offset read error - %d\n", i, ret);
return -EINVAL;
}
ret = of_property_read_u32_index(parent, "part-table", i * 2 + 1, &size);
if (ret) {
pr_err("part-table %d size read error - %d\n", i, ret);
return -EINVAL;
}
if (offset + size > SEC_DEBUG_PARTITION_SIZE) {
pr_err("part-table oversize 0x%x\n", offset + size);
return -EINVAL;
}
debug_part_table[i].offset = offset;
if (!debug_part_table[i].size)
debug_part_table[i].size = size;
}
return 0;
}
static int __init sec_debug_partition_init(void)
{
pr_info("start\n");
dbg_partition_make_part_table();
dbg_partition_fix_part_table();
sched_debug_data.offset = 0;
sched_debug_data.direction = 0;
sched_debug_data.size = 0;
sched_debug_data.value = NULL;
init_completion(&sched_debug_data.work);
INIT_WORK(&sched_debug_data.debug_partition_work,
debug_partition_operation);
INIT_DELAYED_WORK(&dbg_partition_notify_work,
debug_partition_do_notify);
INIT_DELAYED_WORK(&ap_health_work, ap_health_work_write_fn);
dbg_part_wq = create_singlethread_workqueue("dbg_part_wq");
if (!dbg_part_wq) {
pr_err("fail to create dbg_part_wq!\n");
return -EFAULT;
}
atomic_notifier_chain_register(&panic_notifier_list,
&dbg_partition_panic_notifier_block);
driver_initialized = DRV_INITIALIZED;
schedule_delayed_work(&dbg_partition_notify_work, 2 * HZ);
pr_info("end\n");
return 0;
}
static void __exit sec_debug_partition_exit(void)
{
driver_initialized = DRV_UNINITIALIZED;
cancel_work_sync(&sched_debug_data.debug_partition_work);
cancel_delayed_work_sync(&dbg_partition_notify_work);
pr_info("exit\n");
}
module_init(sec_debug_partition_init);
module_exit(sec_debug_partition_exit);