text: /*
   * 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.
   */
  
  #include "los_vm_phys.h"
  #include "los_vm_boot.h"
  #include "los_vm_common.h"
  #include "los_vm_map.h"
  #include "los_vm_dump.h"
  #include "los_process_pri.h"
  
  
  #ifdef LOSCFG_KERNEL_VM
  
  #define ONE_PAGE    1
  
  /* Physical memory area array */
  STATIC struct VmPhysArea g_physArea[] = {
      {
          .start = SYS_MEM_BASE,
          .size = SYS_MEM_SIZE_DEFAULT,
      },
  };
  
  struct VmPhysSeg g_vmPhysSeg[VM_PHYS_SEG_MAX];
  INT32 g_vmPhysSegNum = 0;
  
  LosVmPhysSeg *OsGVmPhysSegGet()
  {
      return g_vmPhysSeg;
  }
  
  STATIC VOID OsVmPhysLruInit(struct VmPhysSeg *seg)
  {
      INT32 i;
      UINT32 intSave;
      LOS_SpinInit(&seg->lruLock);
  
      LOS_SpinLockSave(&seg->lruLock, &intSave);
      for (i = 0; i < VM_NR_LRU_LISTS; i++) {
          seg->lruSize[i] = 0;
          LOS_ListInit(&seg->lruList[i]);
      }
      LOS_SpinUnlockRestore(&seg->lruLock, intSave);
  }
  
  STATIC INT32 OsVmPhysSegCreate(paddr_t start, size_t size)
  {
      struct VmPhysSeg *seg = NULL;
  
      if (g_vmPhysSegNum >= VM_PHYS_SEG_MAX) {
          return -1;
      }
  
      seg = &g_vmPhysSeg[g_vmPhysSegNum++];
      for (; (seg > g_vmPhysSeg) && ((seg - 1)->start > (start + size)); seg--) {
          *seg = *(seg - 1);
      }
      seg->start = start;
      seg->size = size;
  
      return 0;
  }
  
  VOID OsVmPhysSegAdd(VOID)
  {
      INT32 i, ret;
  
      LOS_ASSERT(g_vmPhysSegNum < VM_PHYS_SEG_MAX);
  
      for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {
          ret = OsVmPhysSegCreate(g_physArea[i].start, g_physArea[i].size);
          if (ret != 0) {
              VM_ERR("create phys seg failed");
          }
      }
  }
  
  VOID OsVmPhysAreaSizeAdjust(size_t size)
  {
      /*
       * The first physics memory segment is used for kernel image and kernel heap,
       * so just need to adjust the first one here.
       */
      g_physArea[0].start += size;
      g_physArea[0].size -= size;
  }
  
  UINT32 OsVmPhysPageNumGet(VOID)
  {
      UINT32 nPages = 0;
      INT32 i;
  
      for (i = 0; i < (sizeof(g_physArea) / sizeof(g_physArea[0])); i++) {
          nPages += g_physArea[i].size >> PAGE_SHIFT;
      }
  
      return nPages;
  }
  
  STATIC INLINE VOID OsVmPhysFreeListInit(struct VmPhysSeg *seg)
  {
      int i;
      UINT32 intSave;
      struct VmFreeList *list = NULL;
  
      LOS_SpinInit(&seg->freeListLock);
  
      LOS_SpinLockSave(&seg->freeListLock, &intSave);
      for (i = 0; i < VM_LIST_ORDER_MAX; i++) {
          list = &seg->freeList[i];
          LOS_ListInit(&list->node);
          list->listCnt = 0;
      }
      LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
  }
  
  VOID OsVmPhysInit(VOID)
  {
      struct VmPhysSeg *seg = NULL;
      UINT32 nPages = 0;
      int i;
  
      for (i = 0; i < g_vmPhysSegNum; i++) {
          seg = &g_vmPhysSeg[i];
          seg->pageBase = &g_vmPageArray[nPages];
          nPages += seg->size >> PAGE_SHIFT;
          OsVmPhysFreeListInit(seg);
          OsVmPhysLruInit(seg);
      }
  }
  
  STATIC VOID OsVmPhysFreeListAdd(LosVmPage *page, UINT8 order)
  {
      struct VmPhysSeg *seg = NULL;
      struct VmFreeList *list = NULL;
  
      if (page->segID >= VM_PHYS_SEG_MAX) {
          LOS_Panic("The page segment id(%d) is invalid\n", page->segID);
      }
  
      page->order = order;
      seg = &g_vmPhysSeg[page->segID];
  
      list = &seg->freeList[order];
      LOS_ListTailInsert(&list->node, &page->node);
      list->listCnt++;
  }
  
  STATIC VOID OsVmPhysFreeListAddUnsafe(LosVmPage *page, UINT8 order)
  {
      struct VmPhysSeg *seg = NULL;
      struct VmFreeList *list = NULL;
  
      if (page->segID >= VM_PHYS_SEG_MAX) {
          LOS_Panic("The page segment id(%d) is invalid\n", page->segID);
      }
  
      page->order = order;
      seg = &g_vmPhysSeg[page->segID];
  
      list = &seg->freeList[order];
      LOS_ListTailInsert(&list->node, &page->node);
      list->listCnt++;
  }
  
  STATIC VOID OsVmPhysFreeListDelUnsafe(LosVmPage *page)
  {
      struct VmPhysSeg *seg = NULL;
      struct VmFreeList *list = NULL;
  
      if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) {
          LOS_Panic("The page segment id(%u) or order(%u) is invalid\n", page->segID, page->order);
      }
  
      seg = &g_vmPhysSeg[page->segID];
      list = &seg->freeList[page->order];
      list->listCnt--;
      LOS_ListDelete(&page->node);
      page->order = VM_LIST_ORDER_MAX;
  }
  
  STATIC VOID OsVmPhysFreeListDel(LosVmPage *page)
  {
      struct VmPhysSeg *seg = NULL;
      struct VmFreeList *list = NULL;
  
      if ((page->segID >= VM_PHYS_SEG_MAX) || (page->order >= VM_LIST_ORDER_MAX)) {
          LOS_Panic("The page segment id(%u) or order(%u) is invalid\n", page->segID, page->order);
      }
  
      seg = &g_vmPhysSeg[page->segID];
      list = &seg->freeList[page->order];
      list->listCnt--;
      LOS_ListDelete(&page->node);
      page->order = VM_LIST_ORDER_MAX;
  }
  
  STATIC VOID OsVmPhysPagesSpiltUnsafe(LosVmPage *page, UINT8 oldOrder, UINT8 newOrder)
  {
      UINT32 order;
      LosVmPage *buddyPage = NULL;
  
      for (order = newOrder; order > oldOrder;) {
          order--;
          buddyPage = &page[VM_ORDER_TO_PAGES(order)];
          LOS_ASSERT(buddyPage->order == VM_LIST_ORDER_MAX);
          OsVmPhysFreeListAddUnsafe(buddyPage, order);
      }
  }
  
  LosVmPage *OsVmPhysToPage(paddr_t pa, UINT8 segID)
  {
      struct VmPhysSeg *seg = NULL;
      paddr_t offset;
  
      if (segID >= VM_PHYS_SEG_MAX) {
          LOS_Panic("The page segment id(%d) is invalid\n", segID);
      }
      seg = &g_vmPhysSeg[segID];
      if ((pa < seg->start) || (pa >= (seg->start + seg->size))) {
          return NULL;
      }
  
      offset = pa - seg->start;
      return (seg->pageBase + (offset >> PAGE_SHIFT));
  }
  
  VOID *OsVmPageToVaddr(LosVmPage *page)
  {
      VADDR_T vaddr;
      vaddr = KERNEL_ASPACE_BASE + page->physAddr - SYS_MEM_BASE;
  
      return (VOID *)(UINTPTR)vaddr;
  }
  
  LosVmPage *OsVmVaddrToPage(VOID *ptr)
  {
      struct VmPhysSeg *seg = NULL;
      PADDR_T pa = LOS_PaddrQuery(ptr);
      UINT32 segID;
  
      for (segID = 0; segID < g_vmPhysSegNum; segID++) {
          seg = &g_vmPhysSeg[segID];
          if ((pa >= seg->start) && (pa < (seg->start + seg->size))) {
              return seg->pageBase + ((pa - seg->start) >> PAGE_SHIFT);
          }
      }
  
      return NULL;
  }
  
  STATIC INLINE VOID OsVmRecycleExtraPages(LosVmPage *page, size_t startPage, size_t endPage)
  {
      if (startPage >= endPage) {
          return;
      }
  
      OsVmPhysPagesFreeContiguous(page, endPage - startPage);
  }
  
  STATIC LosVmPage *OsVmPhysLargeAlloc(struct VmPhysSeg *seg, size_t nPages)
  {
      struct VmFreeList *list = NULL;
      LosVmPage *page = NULL;
      LosVmPage *tmp = NULL;
      PADDR_T paStart;
      PADDR_T paEnd;
      size_t size = nPages << PAGE_SHIFT;
  
      list = &seg->freeList[VM_LIST_ORDER_MAX - 1];
      LOS_DL_LIST_FOR_EACH_ENTRY(page, &list->node, LosVmPage, node) {
          paStart = page->physAddr;
          paEnd = paStart + size;
          if (paEnd > (seg->start + seg->size)) {
              continue;
          }
  
          for (;;) {
              paStart += PAGE_SIZE << (VM_LIST_ORDER_MAX - 1);
              if ((paStart >= paEnd) || (paStart < seg->start) ||
                  (paStart >= (seg->start + seg->size))) {
                  break;
              }
              tmp = &seg->pageBase[(paStart - seg->start) >> PAGE_SHIFT];
              if (tmp->order != (VM_LIST_ORDER_MAX - 1)) {
                  break;
              }
          }
          if (paStart >= paEnd) {
              return page;
          }
      }
  
      return NULL;
  }
  
  STATIC LosVmPage *OsVmPhysPagesAlloc(struct VmPhysSeg *seg, size_t nPages)
  {
      struct VmFreeList *list = NULL;
      LosVmPage *page = NULL;
      LosVmPage *tmp = NULL;
      UINT32 order;
      UINT32 newOrder;
  
      order = OsVmPagesToOrder(nPages);
      if (order < VM_LIST_ORDER_MAX) {
          for (newOrder = order; newOrder < VM_LIST_ORDER_MAX; newOrder++) {
              list = &seg->freeList[newOrder];
              if (LOS_ListEmpty(&list->node)) {
                  continue;
              }
              page = LOS_DL_LIST_ENTRY(LOS_DL_LIST_FIRST(&list->node), LosVmPage, node);
              goto DONE;
          }
      } else {
          newOrder = VM_LIST_ORDER_MAX - 1;
          page = OsVmPhysLargeAlloc(seg, nPages);
          if (page != NULL) {
              goto DONE;
          }
      }
      return NULL;
  DONE:
  
      for (tmp = page; tmp < &page[nPages]; tmp = &tmp[1 << newOrder]) {
          OsVmPhysFreeListDelUnsafe(tmp);
      }
      OsVmPhysPagesSpiltUnsafe(page, order, newOrder);
      OsVmRecycleExtraPages(&page[nPages], nPages, ROUNDUP(nPages, (1 << min(order, newOrder))));
  
      return page;
  }
  
  VOID OsVmPhysPagesFree(LosVmPage *page, UINT8 order)
  {
      paddr_t pa;
      LosVmPage *buddyPage = NULL;
  
      if ((page == NULL) || (order >= VM_LIST_ORDER_MAX)) {
          return;
      }
  
      if (order < VM_LIST_ORDER_MAX - 1) {
          pa = VM_PAGE_TO_PHYS(page);
          do {
              pa ^= VM_ORDER_TO_PHYS(order);
              buddyPage = OsVmPhysToPage(pa, page->segID);
              if ((buddyPage == NULL) || (buddyPage->order != order)) {
                  break;
              }
              OsVmPhysFreeListDel(buddyPage);
              order++;
              pa &= ~(VM_ORDER_TO_PHYS(order) - 1);
              page = OsVmPhysToPage(pa, page->segID);
          } while (order < VM_LIST_ORDER_MAX - 1);
      }
  
      OsVmPhysFreeListAdd(page, order);
  }
  
  VOID OsVmPhysPagesFreeContiguous(LosVmPage *page, size_t nPages)
  {
      paddr_t pa;
      UINT32 order;
      size_t n;
  
      while (TRUE) {
          pa = VM_PAGE_TO_PHYS(page);
          order = VM_PHYS_TO_ORDER(pa);
          n = VM_ORDER_TO_PAGES(order);
          if (n > nPages) {
              break;
          }
          OsVmPhysPagesFree(page, order);
          nPages -= n;
          page += n;
      }
  
      while (nPages > 0) {
          order = LOS_HighBitGet(nPages);
          n = VM_ORDER_TO_PAGES(order);
          OsVmPhysPagesFree(page, order);
          nPages -= n;
          page += n;
      }
  }
  
  STATIC LosVmPage *OsVmPhysPagesGet(size_t nPages)
  {
      UINT32 intSave;
      struct VmPhysSeg *seg = NULL;
      LosVmPage *page = NULL;
      UINT32 segID;
  
      for (segID = 0; segID < g_vmPhysSegNum; segID++) {
          seg = &g_vmPhysSeg[segID];
          LOS_SpinLockSave(&seg->freeListLock, &intSave);
          page = OsVmPhysPagesAlloc(seg, nPages);
          if (page != NULL) {
              /* the first page of continuous physical addresses holds refCounts */
              LOS_AtomicSet(&page->refCounts, 0);
              page->nPages = nPages;
              LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
              return page;
          }
          LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
      }
      return NULL;
  }
  
  VOID *LOS_PhysPagesAllocContiguous(size_t nPages)
  {
      LosVmPage *page = NULL;
  
      if (nPages == 0) {
          return NULL;
      }
  
      page = OsVmPhysPagesGet(nPages);
      if (page == NULL) {
          return NULL;
      }
  
      return OsVmPageToVaddr(page);
  }
  
  VOID LOS_PhysPagesFreeContiguous(VOID *ptr, size_t nPages)
  {
      UINT32 intSave;
      struct VmPhysSeg *seg = NULL;
      LosVmPage *page = NULL;
  
      if (ptr == NULL) {
          return;
      }
  
      page = OsVmVaddrToPage(ptr);
      if (page == NULL) {
          VM_ERR("vm page of ptr(%#x) is null", ptr);
          return;
      }
      page->nPages = 0;
  
      seg = &g_vmPhysSeg[page->segID];
      LOS_SpinLockSave(&seg->freeListLock, &intSave);
  
      OsVmPhysPagesFreeContiguous(page, nPages);
  
      LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
  }
  
  VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr)
  {
      struct VmPhysSeg *seg = NULL;
      UINT32 segID;
  
      for (segID = 0; segID < g_vmPhysSegNum; segID++) {
          seg = &g_vmPhysSeg[segID];
          if ((paddr >= seg->start) && (paddr < (seg->start + seg->size))) {
              return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);
          }
      }
  
      return (VADDR_T *)(UINTPTR)(paddr - SYS_MEM_BASE + KERNEL_ASPACE_BASE);
  }
  
  VOID LOS_PhysPageFree(LosVmPage *page)
  {
      UINT32 intSave;
      struct VmPhysSeg *seg = NULL;
  
      if (page == NULL) {
          return;
      }
  
      if (LOS_AtomicDecRet(&page->refCounts) <= 0) {
          seg = &g_vmPhysSeg[page->segID];
          LOS_SpinLockSave(&seg->freeListLock, &intSave);
  
          OsVmPhysPagesFreeContiguous(page, ONE_PAGE);
          LOS_AtomicSet(&page->refCounts, 0);
  
          LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
      }
  }
  
  LosVmPage *LOS_PhysPageAlloc(VOID)
  {
      return OsVmPhysPagesGet(ONE_PAGE);
  }
  
  size_t LOS_PhysPagesAlloc(size_t nPages, LOS_DL_LIST *list)
  {
      LosVmPage *page = NULL;
      size_t count = 0;
  
      if ((list == NULL) || (nPages == 0)) {
          return 0;
      }
  
      while (nPages--) {
          page = OsVmPhysPagesGet(ONE_PAGE);
          if (page == NULL) {
              break;
          }
          LOS_ListTailInsert(list, &page->node);
          count++;
      }
  
      return count;
  }
  
  VOID OsPhysSharePageCopy(PADDR_T oldPaddr, PADDR_T *newPaddr, LosVmPage *newPage)
  {
      UINT32 intSave;
      LosVmPage *oldPage = NULL;
      VOID *newMem = NULL;
      VOID *oldMem = NULL;
      LosVmPhysSeg *seg = NULL;
  
      if ((newPage == NULL) || (newPaddr == NULL)) {
          VM_ERR("new Page invalid");
          return;
      }
  
      oldPage = LOS_VmPageGet(oldPaddr);
      if (oldPage == NULL) {
          VM_ERR("invalid paddr %p", oldPaddr);
          return;
      }
  
      seg = &g_vmPhysSeg[oldPage->segID];
      LOS_SpinLockSave(&seg->freeListLock, &intSave);
      if (LOS_AtomicRead(&oldPage->refCounts) == 1) {
          *newPaddr = oldPaddr;
      } else {
          newMem = LOS_PaddrToKVaddr(*newPaddr);
          oldMem = LOS_PaddrToKVaddr(oldPaddr);
          if ((newMem == NULL) || (oldMem == NULL)) {
              LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
              return;
          }
          if (memcpy_s(newMem, PAGE_SIZE, oldMem, PAGE_SIZE) != EOK) {
              VM_ERR("memcpy_s failed");
          }
  
          LOS_AtomicInc(&newPage->refCounts);
          LOS_AtomicDec(&oldPage->refCounts);
      }
      LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
      return;
  }
  
  struct VmPhysSeg *OsVmPhysSegGet(LosVmPage *page)
  {
      if ((page == NULL) || (page->segID >= VM_PHYS_SEG_MAX)) {
          return NULL;
      }
  
      return (OsGVmPhysSegGet() + page->segID);
  }
  
  UINT32 OsVmPagesToOrder(size_t nPages)
  {
      UINT32 order;
  
      for (order = 0; VM_ORDER_TO_PAGES(order) < nPages; order++);
  
      return order;
  }
  
  size_t LOS_PhysPagesFree(LOS_DL_LIST *list)
  {
      UINT32 intSave;
      LosVmPage *page = NULL;
      LosVmPage *nPage = NULL;
      LosVmPhysSeg *seg = NULL;
      size_t count = 0;
  
      if (list == NULL) {
          return 0;
      }
  
      LOS_DL_LIST_FOR_EACH_ENTRY_SAFE(page, nPage, list, LosVmPage, node) {
          LOS_ListDelete(&page->node);
          if (LOS_AtomicDecRet(&page->refCounts) <= 0) {
              seg = &g_vmPhysSeg[page->segID];
              LOS_SpinLockSave(&seg->freeListLock, &intSave);
              OsVmPhysPagesFreeContiguous(page, ONE_PAGE);
              LOS_AtomicSet(&page->refCounts, 0);
              LOS_SpinUnlockRestore(&seg->freeListLock, intSave);
          }
          count++;
      }
  
      return count;
  }
  #else
  VADDR_T *LOS_PaddrToKVaddr(PADDR_T paddr)
  {
      if ((paddr < DDR_MEM_ADDR) || (paddr >= ((PADDR_T)DDR_MEM_ADDR + DDR_MEM_SIZE))) {
          return NULL;
      }
  
      return (VADDR_T *)DMA_TO_VMM_ADDR(paddr);
  }
  #endif
  
  