#include "driver_log.h"

#include <sys/mman.h>
#include <driver/mem/phys.h>
#include <driver.h>

#include "dr_bf.h"
#include "kaslr.h"
#include "memory.h"

/**
 * Seems like that BF does not have needed functionality now
 * @TODO(i.vorobiov): Need to push BF team to provide needed functionality
 */
PaTzResult PlatformVirtToPhys64(const void *virt, PhysicalAddress *phys) {
  return PA_TZ_NON_SUPPORTED;
}

uint32_t GetMappingFlags(MemoryAccessType type) {
  if (type == kMemoryAccessRead) {
    return (PROT_READ);
  } else if (type == kMemoryAccessWrite) {
    return (PROT_READ | PROT_WRITE);
  }

  return 0;
}

PaTzResult PlatformSysMap(PhysicalAddress phys, size_t size,
                          MemoryAccessType type, void **virt) {
  PaTzResult result = PlatformCheckNonSecureRegion(phys, size);
  if (result != PA_TZ_SUCCESS) {
    LOG_E("Failed CheckNonSecureRegion.\n");
    LOG_D("Received result: 0x%x\n", result);
  }

  uint64_t aligned = AlignToPageDown(phys);
  size_t full_size = size + phys - aligned;

  // Prepared mapping flags
  uint32_t mask_operation = GetMappingFlags(type);

  void *mapped = mmap(NULL, full_size, mask_operation,
                 MAP_SHARED | MAP_PHYS_NON_SECURE,
                 GetFileDescriptorMmap(), aligned);

  if (mapped == MAP_FAILED) {
    LOG_E("Failed mapping memory.\n");
    LOGADDR_D("phys: ", phys);
    LOG_D("Size: %d\n", size);
    return PA_TZ_GENERAL_ERROR;
  }

  *virt = (void *)((uintptr_t)mapped + (uintptr_t)PageOffset(phys));

  return PA_TZ_SUCCESS;
}

PaTzResult PlatformSysUnmap(void *virt, size_t size) {
  uint64_t aligned = AlignToPageDown((uintptr_t)virt);
  size_t full_size = size + (uintptr_t)virt - aligned;

  if (munmap((void *)(uintptr_t)aligned, full_size) != 0) {
    LOG_E("Failed unmapping memory.\n");
    LOG_D("Virt: %p, aligned: %llx\n", virt, aligned);
    LOG_D("Size: %x, full size: %x\n", size, full_size);
    return PA_TZ_GENERAL_ERROR;
  }

  return PA_TZ_SUCCESS;
}

PaTzResult PlatformSysMapTrustlet(const void *virt_trustlet, size_t size,
                                  MemoryAccessType type, void **virt_driver) {
  if (virt_driver == NULL) {
    LOG_E("Pointer to the *virt_driver is null!\n");
    return PA_TZ_GENERAL_ERROR;
  }
  *virt_driver = TEES_AcquireUserBuffer(GetCurrentCaller(), (uint64_t)(uintptr_t)virt_trustlet,
      size, GetMappingFlags(type));

  return (*virt_driver != MAP_FAILED ? PA_TZ_SUCCESS : PA_TZ_GENERAL_ERROR);
}

PaTzResult PlatformSysUnmapTrustlet(const void *virt_driver, size_t size) {
  TEE_Result result = TEES_ReleaseUserBuffer(virt_driver, size);

  return (result == TEE_SUCCESS ? PA_TZ_SUCCESS : PA_TZ_GENERAL_ERROR);
}

PaTzResult PlatformReadOemBuffer(uint32_t offset, size_t size, void *out) {
  return PA_TZ_NON_SUPPORTED;
}

PaTzResult KernelGetKaslrOffset(uint32_t *kaslr_offset) {
  if (!kaslr_offset) {
    LOG_E("Invalid arguments.\n");
    return PA_TZ_GENERAL_ERROR;
  }

  Kaslr kaslr = {0};

  PaTzResult result = PhysicalGetBytes(KernelGetKaslrStructAddress(), sizeof(kaslr), &kaslr);
  if (result != PA_TZ_SUCCESS) {
    LOG_E("Can not read KASLR.\n");
    return result;
  }

  if (kaslr.magic != kKaslrMagicCode) {
    LOG_I("KASLR Magic code is NOT found.\n");
    *kaslr_offset = 0;
  } else {
    LOG_I("KASLR Magic code is found.\n");
    LOG_D("KASLR Offset is 0x%llx.\n", kaslr.offset);
    *kaslr_offset = kaslr.offset;
  }

  return PA_TZ_SUCCESS;
}

/**
 * On TEEgris platform we map memory with flag MAP_PHYS_NON_SECURE, so
 * can not map secure memory to driver via this API.
 */
PaTzResult PlatformCheckNonSecureRegion(PhysicalAddress phys_addr, uint32_t region_size) {
  return PA_TZ_SUCCESS;
}
