/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*!
 * \file virtual_memory.cc
 * \brief Thread-safe virtal memory manager
 */

#include "virtual_memory.h"

#include <dmlc/logging.h>
#include <vta/driver.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <list>
#include <utility>
#include <iterator>
#include <unordered_map>
#include <map>
#include <mutex>

namespace vta {
namespace vmem {

/*!
 * \brief Get virtual address given physical address.
 * \param phy_addr The simulator phyiscal address.
 * \return The true virtual address;
 */
void* VirtualMemoryManager::GetAddr(uint64_t phy_addr) {
  CHECK_NE(phy_addr, 0)
      << "trying to get address that is nullptr";
  std::lock_guard<std::mutex> lock(mutex_);
  uint64_t loc = (phy_addr >> kPageBits) - 1;
  CHECK_LT(loc, ptable_.size())
      << "phy_addr=" << phy_addr;
  Page* p = ptable_[loc];
  CHECK(p != nullptr);
  size_t offset = (loc - p->ptable_begin) << kPageBits;
  offset += phy_addr & (kPageSize - 1);
  return reinterpret_cast<char*>(p->data) + offset;
}

/*!
 * \brief Get physical address
 * \param buf The virtual address.
 * \return The true physical address;
 */
vta_phy_addr_t VirtualMemoryManager::GetPhyAddr(void* buf) {
  std::lock_guard<std::mutex> lock(mutex_);
  auto it = pmap_.find(buf);
  CHECK(it != pmap_.end());
  Page* p = it->second.get();
  return (p->ptable_begin + 1) << kPageBits;
}

/*!
 * \brief Allocate memory from manager
 * \param size The size of memory
 * \return The virtual address
 */
void* VirtualMemoryManager::Alloc(size_t size) {
  std::lock_guard<std::mutex> lock(mutex_);
  size_t npage = (size + kPageSize - 1) / kPageSize;
  auto it = free_map_.lower_bound(npage);
  if (it != free_map_.end()) {
    Page* p = it->second;
    free_map_.erase(it);
    return p->data;
  }
  size_t start = ptable_.size();
  std::unique_ptr<Page> p(new Page(start, npage));
  // insert page entry
  ptable_.resize(start + npage, p.get());
  void* data = p->data;
  pmap_[data] = std::move(p);
  return data;
}

/*!
 * \brief Free the memory.
 * \param size The size of memory
 * \return The virtual address
 */
void VirtualMemoryManager::Free(void* data) {
  std::lock_guard<std::mutex> lock(mutex_);
  if (pmap_.size() == 0) return;
  auto it = pmap_.find(data);
  CHECK(it != pmap_.end());
  Page* p = it->second.get();
  free_map_.insert(std::make_pair(p->num_pages, p));
}

/*!
 * \brief Copy from the host memory to device memory (virtual).
 * \param dst The device memory address (virtual)
 * \param src The host memory address
 * \param size The size of memory
 */
void VirtualMemoryManager::MemCopyFromHost(void* dst, const void * src, size_t size) {
  void * addr = this->GetAddr(reinterpret_cast<uint64_t>(dst));
  memcpy(addr, src, size);
}

/*!
 * \brief Copy from the device memory (virtual) to host memory.
 * \param dst The host memory address
 * \param src The device memory address (virtual)
 * \param size The size of memory
 */
void VirtualMemoryManager::MemCopyToHost(void* dst, const void * src, size_t size) {
  void * addr = this->GetAddr(reinterpret_cast<uint64_t>(src));
  memcpy(dst, addr, size);
}

VirtualMemoryManager* VirtualMemoryManager::Global() {
  static VirtualMemoryManager inst;
  return &inst;
}

}  // namespace vmem
}  // namespace vta
