/*
 * Copyright (c) 2021-2021 Huawei Device Co., Ltd. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of
 *    conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list
 *    of conditions and the following disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used
 *    to endorse or promote products derived from this software without specific prior written
 *    permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/* ------------ includes ------------ */
#include "los_hidumper.h"
#ifdef LOSCFG_BLACKBOX
#include "los_blackbox.h"
#endif
#ifdef LOSCFG_CPUP_INCLUDE_IRQ
#include "los_cpup_pri.h"
#endif
#include "los_hwi_pri.h"
#include "los_init.h"
#include "los_mp.h"
#include "los_mux.h"
#include "los_printf.h"
#include "los_process_pri.h"
#include "los_task_pri.h"
#include "los_vm_dump.h"
#include "los_vm_lock.h"
#include "los_vm_map.h"
#ifdef LOSCFG_FS_VFS
#include "fs/file.h"
#endif
#include "fs/driver.h"
#include "securec.h"
#ifdef LOSCFG_LIB_LIBC
#include "unistd.h"
#endif
#include "user_copy.h"

/* ------------ local macroes ------------ */
#define CPUP_TYPE_COUNT      3
#define HIDUMPER_DEVICE      "/dev/hidumper"
#define HIDUMPER_DEVICE_MODE 0666
#define KERNEL_FAULT_ADDR    0x1
#define KERNEL_FAULT_VALUE   0x2
#define READ_BUF_SIZE        128
#define SYS_INFO_HEADER      "************ sys info ***********"
#define CPU_USAGE_HEADER     "************ cpu usage ***********"
#define MEM_USAGE_HEADER     "************ mem usage ***********"
#define PAGE_USAGE_HEADER    "************ physical page usage ***********"
#define TASK_INFO_HEADER     "************ task info ***********"
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(array)    (sizeof(array) / sizeof(array[0]))
#endif
#define REPLACE_INTERFACE(dst, src, type, func) {           \
    if (((type *)src)->func != NULL) {                      \
        ((type *)dst)->func = ((type *)src)->func;          \
    } else {                                                \
        PRINT_ERR("%s->%s is NULL!\n", #src, #func);        \
    }                                                       \
}
#define INVOKE_INTERFACE(adapter, type, func) {             \
    if (((type *)adapter)->func != NULL) {                  \
        ((type *)adapter)->func();                          \
    } else {                                                \
        PRINT_ERR("%s->%s is NULL!\n", #adapter, #func);    \
    }                                                       \
}

/* ------------ local prototypes ------------ */
/* ------------ local function declarations ------------ */
STATIC INT32 HiDumperOpen(struct file *filep);
STATIC INT32 HiDumperClose(struct file *filep);
STATIC INT32 HiDumperIoctl(struct file *filep, INT32 cmd, unsigned long arg);

/* ------------ global function declarations ------------ */
#ifdef LOSCFG_SHELL
extern VOID   OsShellCmdSystemInfoGet(VOID);
extern UINT32 OsShellCmdFree(INT32 argc, const CHAR *argv[]);
extern UINT32 OsShellCmdUname(INT32 argc, const CHAR *argv[]);
extern UINT32 OsShellCmdDumpPmm(VOID);
#endif

/* ------------ local variables ------------ */
static struct HiDumperAdapter g_adapter;
STATIC struct file_operations_vfs g_hidumperDevOps = {
    HiDumperOpen,  /* open */
    HiDumperClose, /* close */
    NULL,          /* read */
    NULL,          /* write */
    NULL,          /* seek */
    HiDumperIoctl, /* ioctl */
    NULL,          /* mmap */
#ifndef CONFIG_DISABLE_POLL
    NULL,          /* poll */
#endif
    NULL,          /* unlink */
};

/* ------------ function definitions ------------ */
STATIC INT32 HiDumperOpen(struct file *filep)
{
    (VOID)filep;
    return 0;
}

STATIC INT32 HiDumperClose(struct file *filep)
{
    (VOID)filep;
    return 0;
}

static void DumpSysInfo(void)
{
    PRINTK("\n%s\n", SYS_INFO_HEADER);
#ifdef LOSCFG_SHELL
    const char *argv[1] = {"-a"};
    (VOID)OsShellCmdUname(ARRAY_SIZE(argv), &argv[0]);
    (VOID)OsShellCmdSystemInfoGet();
#else
    PRINTK("\nUnsupported!\n");
#endif
}

#ifdef LOSCFG_KERNEL_CPUP
static void DoDumpCpuUsageUnsafe(CPUP_INFO_S *processCpupAll,
    CPUP_INFO_S *processCpup10s,
    CPUP_INFO_S *processCpup1s)
{
    UINT32 pid;

    PRINTK("%-32s PID CPUUSE CPUUSE10S CPUUSE1S\n", "PName");
    for (pid = 0; pid < g_processMaxNum; pid++) {
        LosProcessCB *processCB = g_processCBArray + pid;
        if (OsProcessIsUnused(processCB)) {
            continue;
        }
        PRINTK("%-32s %u %5u.%1u%8u.%1u%7u.%-1u\n",
            processCB->processName, processCB->processID,
            processCpupAll[pid].usage / LOS_CPUP_PRECISION_MULT,
            processCpupAll[pid].usage % LOS_CPUP_PRECISION_MULT,
            processCpup10s[pid].usage / LOS_CPUP_PRECISION_MULT,
            processCpup10s[pid].usage % LOS_CPUP_PRECISION_MULT,
            processCpup1s[pid].usage / LOS_CPUP_PRECISION_MULT,
            processCpup1s[pid].usage % LOS_CPUP_PRECISION_MULT);
    }
}
#endif

static void DumpCpuUsageUnsafe(void)
{
    PRINTK("\n%s\n", CPU_USAGE_HEADER);
#ifdef LOSCFG_KERNEL_CPUP
    UINT32 size;
    CPUP_INFO_S *processCpup = NULL;
    CPUP_INFO_S *processCpupAll = NULL;
    CPUP_INFO_S *processCpup10s = NULL;
    CPUP_INFO_S *processCpup1s = NULL;

    size = sizeof(*processCpup) * g_processMaxNum * CPUP_TYPE_COUNT;
    processCpup = LOS_MemAlloc(m_aucSysMem1, size);
    if (processCpup == NULL) {
        PRINT_ERR("func: %s, LOS_MemAlloc failed, Line: %d\n", __func__, __LINE__);
        return;
    }
    processCpupAll = processCpup;
    processCpup10s = processCpupAll + g_processMaxNum;
    processCpup1s = processCpup10s + g_processMaxNum;
    (VOID)memset_s(processCpup, size, 0, size);
    LOS_GetAllProcessCpuUsage(CPUP_ALL_TIME, processCpupAll, g_processMaxNum * sizeof(CPUP_INFO_S));
    LOS_GetAllProcessCpuUsage(CPUP_LAST_TEN_SECONDS, processCpup10s, g_processMaxNum * sizeof(CPUP_INFO_S));
    LOS_GetAllProcessCpuUsage(CPUP_LAST_ONE_SECONDS, processCpup1s, g_processMaxNum * sizeof(CPUP_INFO_S));
    DoDumpCpuUsageUnsafe(processCpupAll, processCpup10s, processCpup1s);
    (VOID)LOS_MemFree(m_aucSysMem1, processCpup);
#else
    PRINTK("\nUnsupported!\n");
#endif
}

static void DumpMemUsage(void)
{
    PRINTK("\n%s\n", MEM_USAGE_HEADER);
#ifdef LOSCFG_SHELL
    PRINTK("Unit: KB\n");
    const char *argv[1] = {"-k"};
    (VOID)OsShellCmdFree(ARRAY_SIZE(argv), &argv[0]);
    PRINTK("%s\n", PAGE_USAGE_HEADER);
    (VOID)OsShellCmdDumpPmm();
#else
    PRINTK("\nUnsupported!\n");
#endif
}

static void DumpTaskInfo(void)
{
    PRINTK("\n%s\n", TASK_INFO_HEADER);
#ifdef LOSCFG_SHELL
    (VOID)OsShellCmdTskInfoGet(OS_ALL_TASK_MASK, NULL, OS_PROCESS_INFO_ALL);
#else
    PRINTK("\nUnsupported!\n");
#endif
}

#ifdef LOSCFG_BLACKBOX
static void PrintFileData(INT32 fd)
{
#ifdef LOSCFG_FS_VFS
    CHAR buf[READ_BUF_SIZE];

    if (fd < 0) {
        PRINT_ERR("fd: %d!\n", fd);
        return;
    }

    (void)memset_s(buf, sizeof(buf), 0, sizeof(buf));
    while (read(fd, buf, sizeof(buf) - 1) > 0) {
        PRINTK("%s", buf);
        (void)memset_s(buf, sizeof(buf), 0, sizeof(buf));
    }
#else
    (VOID)fd;
    PRINT_ERR("LOSCFG_FS_VFS isn't defined!\n");
#endif
}

static void PrintFile(const char *filePath, const char *pHeader)
{
#ifdef LOSCFG_FS_VFS
    int fd;

    if (filePath == NULL || pHeader == NULL) {
        PRINT_ERR("filePath: %p, pHeader: %p\n", filePath, pHeader);
        return;
    }

    fd = open(filePath, O_RDONLY);
    if (fd >= 0) {
        PRINTK("\n%s\n", pHeader);
        PrintFileData(fd);
        (void)close(fd);
    } else {
        PRINT_ERR("Open [%s] failed or there's no fault log!\n", filePath);
    }
#else
    (VOID)filePath;
    (VOID)pHeader;
    PRINT_ERR("LOSCFG_FS_VFS isn't defined!\n");
#endif
}
#endif

static void DumpFaultLog(void)
{
#ifdef LOSCFG_BLACKBOX
    PrintFile(KERNEL_FAULT_LOG_PATH, "************kernel fault info************");
    PrintFile(USER_FAULT_LOG_PATH, "************user fault info************");
#endif
}

static void DumpMemData(struct MemDumpParam *param)
{
    PRINTK("\nDumpType: %d\n", param->type);
    PRINTK("Unsupported now!\n");
}

static void InjectKernelCrash(void)
{
#ifdef LOSCFG_DEBUG_VERSION
    *((INT32 *)KERNEL_FAULT_ADDR) = KERNEL_FAULT_VALUE;
#else
    PRINTK("\nUnsupported!\n");
#endif
}

static INT32 HiDumperIoctl(struct file *filep, INT32 cmd, unsigned long arg)
{
    INT32 ret = 0;

    switch (cmd) {
        case HIDUMPER_DUMP_ALL:
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpSysInfo);
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpCpuUsage);
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpMemUsage);
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpTaskInfo);
            break;
        case HIDUMPER_CPU_USAGE:
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpCpuUsage);
            break;
        case HIDUMPER_MEM_USAGE:
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpMemUsage);
            break;
        case HIDUMPER_TASK_INFO:
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpTaskInfo);
            break;
        case HIDUMPER_INJECT_KERNEL_CRASH:
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, InjectKernelCrash);
            break;
        case HIDUMPER_DUMP_FAULT_LOG:
            INVOKE_INTERFACE(&g_adapter, struct HiDumperAdapter, DumpFaultLog);
            break;
        case HIDUMPER_MEM_DATA:
            if (g_adapter.DumpMemData != NULL) {
                g_adapter.DumpMemData((struct MemDumpParam *)arg);
            }
            break;
        default:
            ret = EPERM;
            PRINTK("Invalid CMD: 0x%x\n", (UINT32)cmd);
            break;
    }

    return ret;
}

static void RegisterCommonAdapter(void)
{
    struct HiDumperAdapter adapter;

    adapter.DumpSysInfo = DumpSysInfo;
    adapter.DumpCpuUsage = DumpCpuUsageUnsafe;
    adapter.DumpMemUsage = DumpMemUsage;
    adapter.DumpTaskInfo = DumpTaskInfo;
    adapter.DumpFaultLog = DumpFaultLog;
    adapter.DumpMemData = DumpMemData;
    adapter.InjectKernelCrash = InjectKernelCrash;
    HiDumperRegisterAdapter(&adapter);
}

int HiDumperRegisterAdapter(struct HiDumperAdapter *pAdapter)
{
    if (pAdapter == NULL) {
        PRINT_ERR("pAdapter: %p\n", pAdapter);
        return -1;
    }

    REPLACE_INTERFACE(&g_adapter, pAdapter, struct HiDumperAdapter, DumpSysInfo);
    REPLACE_INTERFACE(&g_adapter, pAdapter, struct HiDumperAdapter, DumpCpuUsage);
    REPLACE_INTERFACE(&g_adapter, pAdapter, struct HiDumperAdapter, DumpMemUsage);
    REPLACE_INTERFACE(&g_adapter, pAdapter, struct HiDumperAdapter, DumpTaskInfo);
    REPLACE_INTERFACE(&g_adapter, pAdapter, struct HiDumperAdapter, DumpFaultLog);
    REPLACE_INTERFACE(&g_adapter, pAdapter, struct HiDumperAdapter, DumpMemData);
    REPLACE_INTERFACE(&g_adapter, pAdapter, struct HiDumperAdapter, InjectKernelCrash);

    return 0;
}

int OsHiDumperDriverInit(void)
{
    INT32 ret;

#ifdef LOSCFG_DEBUG_VERSION
    RegisterCommonAdapter();
    ret = register_driver(HIDUMPER_DEVICE, &g_hidumperDevOps, HIDUMPER_DEVICE_MODE, NULL);
    if (ret != 0) {
        PRINT_ERR("Hidumper register driver failed!\n");
        return -1;
    }
#endif

    return 0;
}
LOS_MODULE_INIT(OsHiDumperDriverInit, LOS_INIT_LEVEL_KMOD_EXTENDED);
