/*
 * Copyright (c) 2013-2019 Huawei Technologies Co., Ltd. All rights reserved.
 * Copyright (c) 2020-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.
 */

/**
 * @defgroup los_vm_dump virtual memory dump operation
 * @ingroup kernel
 */

#include "los_vm_dump.h"
#include "los_mmu_descriptor_v6.h"
#ifdef LOSCFG_FS_VFS
#include "fs/file.h"
#include "vnode.h"
#endif
#include "los_printf.h"
#include "los_vm_page.h"
#include "los_vm_phys.h"
#include "los_process_pri.h"
#include "los_atomic.h"
#include "los_vm_lock.h"
#include "los_memory_pri.h"


#ifdef LOSCFG_KERNEL_VM

#define     FLAG_SIZE               4
#define     FLAG_START              2

const CHAR *OsGetRegionNameOrFilePath(LosVmMapRegion *region)
{
    struct Vnode *vnode = NULL;
    if (region == NULL) {
        return "";
#ifdef LOSCFG_FS_VFS
    } else if (LOS_IsRegionFileValid(region)) {
        vnode = region->unTypeData.rf.vnode;
        return vnode->filePath;
#endif
    } else if (region->regionFlags & VM_MAP_REGION_FLAG_HEAP) {
        return "HEAP";
    } else if (region->regionFlags & VM_MAP_REGION_FLAG_STACK) {
        return "STACK";
    } else if (region->regionFlags & VM_MAP_REGION_FLAG_TEXT) {
        return "Text";
    } else if (region->regionFlags & VM_MAP_REGION_FLAG_VDSO) {
        return "VDSO";
    } else if (region->regionFlags & VM_MAP_REGION_FLAG_MMAP) {
        return "MMAP";
    } else if (region->regionFlags & VM_MAP_REGION_FLAG_SHM) {
        return "SHM";
    } else {
        return "";
    }
    return "";
}

INT32 OsRegionOverlapCheckUnlock(LosVmSpace *space, LosVmMapRegion *region)
{
    LosVmMapRegion *regionTemp = NULL;
    LosRbNode *pstRbNode = NULL;
    LosRbNode *pstRbNodeNext = NULL;

    /* search the region list */
    RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
        regionTemp = (LosVmMapRegion *)pstRbNode;
        if (region->range.base == regionTemp->range.base && region->range.size == regionTemp->range.size) {
            continue;
        }
        if (((region->range.base + region->range.size) > regionTemp->range.base) &&
            (region->range.base < (regionTemp->range.base + regionTemp->range.size))) {
            VM_ERR("overlap between regions:\n"
                   "flags:%#x base:%p size:%08x space:%p\n"
                   "flags:%#x base:%p size:%08x space:%p",
                   region->regionFlags, region->range.base, region->range.size, region->space,
                   regionTemp->regionFlags, regionTemp->range.base, regionTemp->range.size, regionTemp->space);
            return -1;
        }
    RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)

    return 0;
}

UINT32 OsShellCmdProcessVmUsage(LosVmSpace *space)
{
    LosVmMapRegion *region = NULL;
    LosRbNode *pstRbNode = NULL;
    LosRbNode *pstRbNodeNext = NULL;
    UINT32 used = 0;

    if (space == NULL) {
        return 0;
    }

    if (space == LOS_GetKVmSpace()) {
        OsShellCmdProcessPmUsage(space, NULL, &used);
        return used;
    }
    UINT32 ret = LOS_MuxAcquire(&space->regionMux);
    if (ret != 0) {
        return 0;
    }

    RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
        region = (LosVmMapRegion *)pstRbNode;
        used += region->range.size;
    RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)
    (VOID)LOS_MuxRelease(&space->regionMux);
    return used;
}

UINT32 OsKProcessPmUsage(LosVmSpace *kSpace, UINT32 *actualPm)
{
    UINT32 memUsed;
    UINT32 totalMem;
    UINT32 freeMem;
    UINT32 usedCount = 0;
    UINT32 totalCount = 0;
    LosVmSpace *space = NULL;
    LOS_DL_LIST *spaceList = NULL;
    UINT32 UProcessUsed = 0;

    if (actualPm == NULL) {
        return 0;
    }

    memUsed = LOS_MemTotalUsedGet(m_aucSysMem1);
    totalMem = LOS_MemPoolSizeGet(m_aucSysMem1);
    freeMem = totalMem - memUsed;

    OsVmPhysUsedInfoGet(&usedCount, &totalCount);
    /* Kernel resident memory, include default heap memory */
    memUsed = SYS_MEM_SIZE_DEFAULT - (totalCount << PAGE_SHIFT);

    spaceList = LOS_GetVmSpaceList();
    LosMux *vmSpaceListMux = OsGVmSpaceMuxGet();
    (VOID)LOS_MuxAcquire(vmSpaceListMux);
    LOS_DL_LIST_FOR_EACH_ENTRY(space, spaceList, LosVmSpace, node) {
        if (space == LOS_GetKVmSpace()) {
            continue;
        }
        UProcessUsed += OsUProcessPmUsage(space, NULL, NULL);
    }
    (VOID)LOS_MuxRelease(vmSpaceListMux);

    /* Kernel dynamic memory, include extended heap memory */
    memUsed += ((usedCount << PAGE_SHIFT) - UProcessUsed);
    /* Remaining heap memory */
    memUsed -= freeMem;

    *actualPm = memUsed;
    return memUsed;
}

UINT32 OsShellCmdProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm)
{
    if (space == NULL) {
        return 0;
    }

    if ((sharePm == NULL) && (actualPm == NULL)) {
        return 0;
    }

    if (space == LOS_GetKVmSpace()) {
        return OsKProcessPmUsage(space, actualPm);
    }
    return OsUProcessPmUsage(space, sharePm, actualPm);
}

UINT32 OsUProcessPmUsage(LosVmSpace *space, UINT32 *sharePm, UINT32 *actualPm)
{
    LosVmMapRegion *region = NULL;
    LosRbNode *pstRbNode = NULL;
    LosRbNode *pstRbNodeNext = NULL;
    LosVmPage *page = NULL;
    VADDR_T vaddr;
    size_t size;
    PADDR_T paddr;
    STATUS_T ret;
    INT32 shareRef;
    UINT32 pmSize = 0;

    if (sharePm != NULL) {
        *sharePm = 0;
    }

    ret = LOS_MuxAcquire(&space->regionMux);
    if (ret != 0) {
        return 0;
    }
    RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
        region = (LosVmMapRegion *)pstRbNode;
        vaddr = region->range.base;
        size = region->range.size;
        for (; size > 0; vaddr += PAGE_SIZE, size -= PAGE_SIZE) {
            ret = LOS_ArchMmuQuery(&space->archMmu, vaddr, &paddr, NULL);
            if (ret < 0) {
                continue;
            }
            page = LOS_VmPageGet(paddr);
            if (page == NULL) {
                continue;
            }

            shareRef = LOS_AtomicRead(&page->refCounts);
            if (shareRef > 1) {
                if (sharePm != NULL) {
                    *sharePm += PAGE_SIZE;
                }
                pmSize += PAGE_SIZE / shareRef;
            } else {
                pmSize += PAGE_SIZE;
            }
        }
    RB_SCAN_SAFE_END(&oldVmSpace->regionRbTree, pstRbNode, pstRbNodeNext)

    (VOID)LOS_MuxRelease(&space->regionMux);

    if (actualPm != NULL) {
        *actualPm = pmSize;
    }

    return pmSize;
}

LosProcessCB *OsGetPIDByAspace(const LosVmSpace *space)
{
    UINT32 pid;
    UINT32 intSave;
    LosProcessCB *processCB = NULL;

    SCHEDULER_LOCK(intSave);
    for (pid = 0; pid < g_processMaxNum; ++pid) {
        processCB = g_processCBArray + pid;
        if (OsProcessIsUnused(processCB)) {
            continue;
        }

        if (processCB->vmSpace == space) {
            SCHEDULER_UNLOCK(intSave);
            return processCB;
        }
    }
    SCHEDULER_UNLOCK(intSave);
    return NULL;
}

UINT32 OsCountRegionPages(LosVmSpace *space, LosVmMapRegion *region, UINT32 *pssPages)
{
    UINT32 regionPages = 0;
    PADDR_T paddr;
    VADDR_T vaddr;
    UINT32 ref;
    STATUS_T status;
    float pss = 0;
    LosVmPage *page = NULL;

    for (vaddr = region->range.base; vaddr < region->range.base + region->range.size; vaddr = vaddr + PAGE_SIZE) {
        status = LOS_ArchMmuQuery(&space->archMmu, vaddr, &paddr, NULL);
        if (status == LOS_OK) {
            regionPages++;
            if (pssPages == NULL) {
                continue;
            }
            page = LOS_VmPageGet(paddr);
            if (page != NULL) {
                ref = LOS_AtomicRead(&page->refCounts);
                pss += ((ref > 0) ? (1.0 / ref) : 1);
            } else {
                pss += 1;
            }
        }
    }

    if (pssPages != NULL) {
        *pssPages = (UINT32)(pss + 0.5);    /* 0.5, for page alignment */
    }

    return regionPages;
}

UINT32 OsCountAspacePages(LosVmSpace *space)
{
    UINT32 spacePages = 0;
    LosVmMapRegion *region = NULL;
    LosRbNode *pstRbNode = NULL;
    LosRbNode *pstRbNodeNext = NULL;

    RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
        region = (LosVmMapRegion *)pstRbNode;
        spacePages += OsCountRegionPages(space, region, NULL);
    RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)
    return spacePages;
}

CHAR *OsArchFlagsToStr(const UINT32 archFlags)
{
    UINT32 index;
    UINT32 cacheFlags = archFlags & VM_MAP_REGION_FLAG_CACHE_MASK;
    UINT32 flagSize = FLAG_SIZE * BITMAP_BITS_PER_WORD * sizeof(CHAR);
    CHAR *archMmuFlagsStr = (CHAR *)LOS_MemAlloc(m_aucSysMem0, flagSize);
    if (archMmuFlagsStr == NULL) {
        return NULL;
    }
    (VOID)memset_s(archMmuFlagsStr, flagSize, 0, flagSize);
    switch (cacheFlags) {
        case 0UL:
            (VOID)strcat_s(archMmuFlagsStr, flagSize, " CH\0");
            break;
        case 1UL:
            (VOID)strcat_s(archMmuFlagsStr, flagSize, " UC\0");
            break;
        case 2UL:
            (VOID)strcat_s(archMmuFlagsStr, flagSize, " UD\0");
            break;
        case 3UL:
            (VOID)strcat_s(archMmuFlagsStr, flagSize, " WC\0");
            break;
        default:
            break;
    }

    static const CHAR FLAGS[BITMAP_BITS_PER_WORD][FLAG_SIZE] = {
        [0 ... (__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_USER) - 2)] = "???\0",
        [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_USER) - 1] = " US\0",
        [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_READ) - 1] = " RD\0",
        [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_WRITE) - 1] = " WR\0",
        [__builtin_ffsl(VM_MAP_REGION_FLAG_PERM_EXECUTE) - 1] = " EX\0",
        [__builtin_ffsl(VM_MAP_REGION_FLAG_NS) - 1] = " NS\0",
        [__builtin_ffsl(VM_MAP_REGION_FLAG_INVALID) - 1] = " IN\0",
        [__builtin_ffsl(VM_MAP_REGION_FLAG_INVALID) ... (BITMAP_BITS_PER_WORD - 1)] = "???\0",
    };

    for (index = FLAG_START; index < BITMAP_BITS_PER_WORD; index++) {
        if (FLAGS[index][0] == '?') {
            continue;
        }

        if (archFlags & (1UL << index)) {
            UINT32 status = strcat_s(archMmuFlagsStr, flagSize, FLAGS[index]);
            if (status != 0) {
                PRINTK("error\n");
            }
        }
    }

    return archMmuFlagsStr;
}

VOID OsDumpRegion2(LosVmSpace *space, LosVmMapRegion *region)
{
    UINT32 pssPages = 0;
    UINT32 regionPages;

    regionPages = OsCountRegionPages(space, region, &pssPages);
    CHAR *flagsStr = OsArchFlagsToStr(region->regionFlags);
    if (flagsStr == NULL) {
        return;
    }
    PRINTK("\t %#010x  %-32.32s %#010x %#010x %-15.15s %4d    %4d\n",
        region, OsGetRegionNameOrFilePath(region), region->range.base,
        region->range.size, flagsStr, regionPages, pssPages);
    (VOID)LOS_MemFree(m_aucSysMem0, flagsStr);
}

VOID OsDumpAspace(LosVmSpace *space)
{
    LosVmMapRegion *region = NULL;
    LosRbNode *pstRbNode = NULL;
    LosRbNode *pstRbNodeNext = NULL;
    UINT32 spacePages;
    LosProcessCB *pcb = OsGetPIDByAspace(space);

    if (pcb == NULL) {
        return;
    }

    spacePages = OsCountAspacePages(space);
    PRINTK("\r\n PID    aspace     name       base       size     pages \n");
    PRINTK(" ----   ------     ----       ----       -----     ----\n");
    PRINTK(" %-4d %#010x %-10.10s %#010x %#010x     %d\n", pcb->processID, space, pcb->processName,
        space->base, space->size, spacePages);
    PRINTK("\r\n\t region      name                base       size       mmu_flags      pages   pg/ref\n");
    PRINTK("\t ------      ----                ----       ----       ---------      -----   -----\n");
    RB_SCAN_SAFE(&space->regionRbTree, pstRbNode, pstRbNodeNext)
        region = (LosVmMapRegion *)pstRbNode;
        if (region != NULL) {
            OsDumpRegion2(space, region);
            (VOID)OsRegionOverlapCheck(space, region);
        } else {
            PRINTK("region is NULL\n");
        }
    RB_SCAN_SAFE_END(&space->regionRbTree, pstRbNode, pstRbNodeNext)
    return;
}

VOID OsDumpAllAspace(VOID)
{
    LosVmSpace *space = NULL;
    LOS_DL_LIST *aspaceList = LOS_GetVmSpaceList();
    LOS_DL_LIST_FOR_EACH_ENTRY(space, aspaceList, LosVmSpace, node) {
        (VOID)LOS_MuxAcquire(&space->regionMux);
        OsDumpAspace(space);
        (VOID)LOS_MuxRelease(&space->regionMux);
    }
    return;
}

STATUS_T OsRegionOverlapCheck(LosVmSpace *space, LosVmMapRegion *region)
{
    int ret;

    if (space == NULL || region == NULL) {
        return -1;
    }

    (VOID)LOS_MuxAcquire(&space->regionMux);
    ret = OsRegionOverlapCheckUnlock(space, region);
    (VOID)LOS_MuxRelease(&space->regionMux);
    return ret;
}

VOID OsDumpPte(VADDR_T vaddr)
{
    UINT32 l1Index = vaddr >> MMU_DESCRIPTOR_L1_SMALL_SHIFT;
    LosVmSpace *space = LOS_SpaceGet(vaddr);
    UINT32 ttEntry;
    LosVmPage *page = NULL;
    PTE_T *l2Table = NULL;
    UINT32 l2Index;

    if (space == NULL) {
        return;
    }

    ttEntry = space->archMmu.virtTtb[l1Index];
    if (ttEntry) {
        l2Table = LOS_PaddrToKVaddr(MMU_DESCRIPTOR_L1_PAGE_TABLE_ADDR(ttEntry));
        l2Index = (vaddr % MMU_DESCRIPTOR_L1_SMALL_SIZE) >> PAGE_SHIFT;
        if (l2Table == NULL) {
            goto ERR;
        }
        page = LOS_VmPageGet(l2Table[l2Index] & ~(PAGE_SIZE - 1));
        if (page == NULL) {
            goto ERR;
        }
        PRINTK("vaddr %p, l1Index %d, ttEntry %p, l2Table %p, l2Index %d, pfn %p count %d\n",
               vaddr, l1Index, ttEntry, l2Table, l2Index, l2Table[l2Index], LOS_AtomicRead(&page->refCounts));
    } else {
        PRINTK("vaddr %p, l1Index %d, ttEntry %p\n", vaddr, l1Index, ttEntry);
    }
    return;
ERR:
    PRINTK("%s, error vaddr: %#x, l2Table: %#x, l2Index: %#x\n", __FUNCTION__, vaddr, l2Table, l2Index);
}

UINT32 OsVmPhySegPagesGet(LosVmPhysSeg *seg)
{
    UINT32 intSave;
    UINT32 flindex;
    UINT32 segFreePages = 0;

    LOS_SpinLockSave(&seg->freeListLock, &intSave);
    for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) {
        segFreePages += ((1 << flindex) * seg->freeList[flindex].listCnt);
    }
    LOS_SpinUnlockRestore(&seg->freeListLock, intSave);

    return segFreePages;
}

VOID OsVmPhysDump(VOID)
{
    LosVmPhysSeg *seg = NULL;
    UINT32 segFreePages;
    UINT32 totalFreePages = 0;
    UINT32 totalPages = 0;
    UINT32 segIndex;
    UINT32 intSave;
    UINT32 flindex;
    UINT32 listCount[VM_LIST_ORDER_MAX] = {0};

    for (segIndex = 0; segIndex < g_vmPhysSegNum; segIndex++) {
        seg = &g_vmPhysSeg[segIndex];
        if (seg->size > 0) {
            segFreePages = OsVmPhySegPagesGet(seg);
#ifdef LOSCFG_SHELL_CMD_DEBUG
            PRINTK("\r\n phys_seg      base         size        free_pages    \n");
            PRINTK(" --------      -------      ----------  ---------  \n");
#endif
            PRINTK(" 0x%08x    0x%08x   0x%08x   %8u  \n", seg, seg->start, seg->size, segFreePages);
            totalFreePages += segFreePages;
            totalPages += (seg->size >> PAGE_SHIFT);

            LOS_SpinLockSave(&seg->freeListLock, &intSave);
            for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) {
                listCount[flindex] = seg->freeList[flindex].listCnt;
            }
            LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
            for (flindex = 0; flindex < VM_LIST_ORDER_MAX; flindex++) {
                PRINTK("order = %d, free_count = %d\n", flindex, listCount[flindex]);
            }

            PRINTK("active   anon   %d\n", seg->lruSize[VM_LRU_ACTIVE_ANON]);
            PRINTK("inactive anon   %d\n", seg->lruSize[VM_LRU_INACTIVE_ANON]);
            PRINTK("active   file   %d\n", seg->lruSize[VM_LRU_ACTIVE_FILE]);
            PRINTK("inactive file   %d\n", seg->lruSize[VM_LRU_INACTIVE_FILE]);
        }
    }
    PRINTK("\n\rpmm pages: total = %u, used = %u, free = %u\n",
           totalPages, (totalPages - totalFreePages), totalFreePages);
}

VOID OsVmPhysUsedInfoGet(UINT32 *usedCount, UINT32 *totalCount)
{
    UINT32 index;
    UINT32 segFreePages;
    LosVmPhysSeg *physSeg = NULL;

    if (usedCount == NULL || totalCount == NULL) {
        return;
    }
    *usedCount = 0;
    *totalCount = 0;

    for (index = 0; index < g_vmPhysSegNum; index++) {
        physSeg = &g_vmPhysSeg[index];
        if (physSeg->size > 0) {
            *totalCount += physSeg->size >> PAGE_SHIFT;
            segFreePages = OsVmPhySegPagesGet(physSeg);
            *usedCount += (*totalCount - segFreePages);
        }
    }
}
#endif

