/*
 * Copyright (C) 2017 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 "gralloc_vsoc_priv.h"

#include <unistd.h>
#include <string.h>
#include <sys/mman.h>

#include <cutils/hashmap.h>
#include <hardware/gralloc.h>
#include <hardware/hardware.h>
#include <log/log.h>

namespace {

const size_t g_page_size = sysconf(_SC_PAGESIZE);

struct HmLockGuard {
  HmLockGuard(Hashmap* map) : map_(map) {
    hashmapLock(map_);
  }
  ~HmLockGuard() {
    hashmapUnlock(map_);
  }
 private:
  Hashmap* map_;
};

int offset_hash(void* key) {
  return *reinterpret_cast<int*>(key);
}

bool offset_equals(void* key1, void* key2) {
  return *reinterpret_cast<int*>(key1) ==
         *reinterpret_cast<int*>(key2);
}

// Keeps track of how many times a buffer is locked in the current process.
struct GrallocBuffer {
  void* vaddr;
  int ref_count;
  GrallocBuffer() : vaddr(NULL), ref_count(0) {}

  static Hashmap* mapped_buffers() {
    static Hashmap* mapped_buffers =
        hashmapCreate(19, offset_hash, offset_equals);
    return mapped_buffers;
  }
};

}

void* reference_buffer(const vsoc_buffer_handle_t* hnd) {
  Hashmap* map = GrallocBuffer::mapped_buffers();
  HmLockGuard lock_guard(map);
  GrallocBuffer* buffer = reinterpret_cast<GrallocBuffer*>(
      hashmapGet(map, const_cast<int*>(&hnd->offset)));
  if (!buffer) {
    buffer = new GrallocBuffer();
    hashmapPut(map, const_cast<int*>(&hnd->offset), buffer);
  }

  if (!buffer->vaddr) {
    void* mapped =
      mmap(NULL, hnd->size, PROT_READ | PROT_WRITE, MAP_SHARED, hnd->fd, 0);
    if (mapped == MAP_FAILED) {
      ALOGE("Unable to map buffer (offset: %d, size: %d): %s",
            hnd->offset,
            hnd->size,
            strerror(errno));
      return NULL;
    }
    // Set up the guard pages. The last page is always a guard
    uintptr_t base = uintptr_t(mapped);
    uintptr_t addr = base + hnd->size - g_page_size;
    if (mprotect((void*)addr, g_page_size, PROT_NONE) == -1) {
      ALOGW("Unable to protect last page of buffer (offset: %d, size: %d): %s",
            hnd->offset,
            hnd->size,
            strerror(errno));
    }
    buffer->vaddr = mapped;
  }
  buffer->ref_count++;
  return buffer->vaddr;
}

int unreference_buffer(const vsoc_buffer_handle_t* hnd) {
  int result = 0;
  Hashmap* map = GrallocBuffer::mapped_buffers();
  HmLockGuard lock_guard(map);
  GrallocBuffer* buffer = reinterpret_cast<GrallocBuffer*>(
      hashmapGet(map, const_cast<int*>(&hnd->offset)));
  if (!buffer) {
    ALOGE("Unreferencing an unknown buffer (offset: %d, size: %d)",
          hnd->offset,
          hnd->size);
    return -EINVAL;
  }
  if (buffer->ref_count == 0) {
    ALOGE("Unbalanced reference/unreference on buffer (offset: %d, size: %d)",
          hnd->offset,
          hnd->size);
    return -EINVAL;
  }
  buffer->ref_count--;
  if (buffer->ref_count == 0) {
    result = munmap(buffer->vaddr, hnd->size);
    if (result) {
      ALOGE("Unable to unmap buffer (offset: %d, size: %d): %s",
            hnd->offset,
            hnd->size,
            strerror(errno));
    }
    buffer->vaddr = NULL;
  }
  return result;
}