/*
 * Copyright (C) 2018 The Android Open Source Project
 *
 * Licensed 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.
 */

#include "mem_map.h"
#include "logging.h"
#include "mman.h"

#include <zircon/process.h>
#include <zircon/syscalls.h>

namespace art {

static zx_handle_t fuchsia_lowmem_vmar = ZX_HANDLE_INVALID;
static zx_vaddr_t fuchsia_lowmem_base = 0;
static size_t fuchsia_lowmem_size = 0;

static const char map_name[] = "mmap-android";
static constexpr uintptr_t FUCHSIA_LOWER_MEM_START = 0x80000000;
static constexpr uintptr_t FUCHSIA_LOWER_MEM_SIZE  = 0x60000000;

void MemMap::TargetMMapInit() {
  if (fuchsia_lowmem_vmar != ZX_HANDLE_INVALID) {
    return;
  }

  zx_info_vmar_t vmarinfo;
  CHECK_EQ(zx_object_get_info(zx_vmar_root_self(),
                              ZX_INFO_VMAR,
                              &vmarinfo,
                              sizeof(vmarinfo),
                              nullptr,
                              nullptr), ZX_OK) << "could not find info from root vmar";

  uintptr_t lower_mem_start = FUCHSIA_LOWER_MEM_START - vmarinfo.base;
  fuchsia_lowmem_size = FUCHSIA_LOWER_MEM_SIZE;
  uint32_t allocflags = ZX_VM_FLAG_CAN_MAP_READ |
                        ZX_VM_FLAG_CAN_MAP_WRITE |
                        ZX_VM_FLAG_CAN_MAP_EXECUTE |
                        ZX_VM_FLAG_SPECIFIC;
  CHECK_EQ(zx_vmar_allocate(zx_vmar_root_self(),
                            lower_mem_start,
                            fuchsia_lowmem_size,
                            allocflags,
                            &fuchsia_lowmem_vmar,
                            &fuchsia_lowmem_base), ZX_OK) << "could not allocate lowmem vmar";
}

void* MemMap::TargetMMap(void* start, size_t len, int prot, int flags, int fd, off_t fd_off) {
  zx_status_t status;
  uintptr_t mem = 0;

  bool mmap_lower = (flags & MAP_32BIT) != 0;

  // for file-based mapping use system library
  if ((flags & MAP_ANONYMOUS) == 0) {
    if (start != nullptr) {
      flags |= MAP_FIXED;
    }
    CHECK(!mmap_lower) << "cannot map files into low memory for Fuchsia";
    return mmap(start, len, prot, flags, fd, fd_off);
  }

  uint32_t vmarflags = 0;
  if ((prot & PROT_READ) != 0) {
    vmarflags |= ZX_VM_FLAG_PERM_READ;
  }
  if ((prot & PROT_WRITE) != 0) {
    vmarflags |= ZX_VM_FLAG_PERM_WRITE;
  }
  if ((prot & PROT_EXEC) != 0) {
    vmarflags |= ZX_VM_FLAG_PERM_EXECUTE;
  }

  if (len == 0) {
    errno = EINVAL;
    return MAP_FAILED;
  }

  zx_info_vmar_t vmarinfo;
  size_t vmaroffset = 0;
  if (start != nullptr) {
    vmarflags |= ZX_VM_FLAG_SPECIFIC;
    status = zx_object_get_info((mmap_lower ? fuchsia_lowmem_vmar : zx_vmar_root_self()),
                                ZX_INFO_VMAR,
                                &vmarinfo,
                                sizeof(vmarinfo),
                                nullptr,
                                nullptr);
    if (status < 0 || reinterpret_cast<uintptr_t>(start) < vmarinfo.base) {
      errno = EINVAL;
      return MAP_FAILED;
    }
    vmaroffset = reinterpret_cast<uintptr_t>(start) - vmarinfo.base;
  }

  zx_handle_t vmo;
  if (zx_vmo_create(len, 0, &vmo) < 0) {
    errno = ENOMEM;
    return MAP_FAILED;
  }
  zx_vmo_get_size(vmo, &len);
  zx_object_set_property(vmo, ZX_PROP_NAME, map_name, strlen(map_name));

  if (mmap_lower) {
    status = zx_vmar_map(fuchsia_lowmem_vmar, vmaroffset, vmo, fd_off, len, vmarflags, &mem);
  } else {
    status = zx_vmar_map(zx_vmar_root_self(), vmaroffset, vmo, fd_off, len, vmarflags, &mem);
  }
  zx_handle_close(vmo);
  if (status != ZX_OK) {
    return MAP_FAILED;
  }

  return reinterpret_cast<void *>(mem);
}

int MemMap::TargetMUnmap(void* start, size_t len) {
  uintptr_t addr = reinterpret_cast<uintptr_t>(start);
  zx_handle_t alloc_vmar = zx_vmar_root_self();
  if (addr >= fuchsia_lowmem_base && addr < fuchsia_lowmem_base + fuchsia_lowmem_size) {
    alloc_vmar = fuchsia_lowmem_vmar;
  }
  zx_status_t status = zx_vmar_unmap(alloc_vmar, addr, len);
  if (status < 0) {
    errno = EINVAL;
    return -1;
  }
  return 0;
}

}  // namespace art
