/**
 * @file tees_rpmb.c
 * @brief Extention API for accessing RPMB in QSEE.
 * @author Andrey Neyvanov (a.neyvanov@samsung.com)
 * @date Created May 23
 * @par In Samsung Ukraine R&D Center (SURC) under a contract between
 * @par LLC "Samsung Electronics Ukraine Company" (Kiev, Ukraine) and
 * @par "Samsung Elecrtronics Co", Ltd (Seoul, Republic of Korea)
 * @par Copyright: (c) Samsung Electronics Co, Ltd 2017. All rights reserved.
 *
 * This software is proprietary of Samsung Electronics.
 * No part of this software, either material or conceptual may be copied
 * or distributed, transmitted, transcribed, stored in a retrieval system
 * or translated into any human or computer language in any form by any means,
 * electronic, mechanical, manual or otherwise, or disclosed to third parties
 * without the express written permission of Samsung Electronics.
 *
 */

#include <qsee_heap.h>
#include <qsee_stor.h>
#include <qsee_fs.h>
#include <qsee_core.h>
#include <qsee_ecc.h>
#include <tees_log.h>  /* This is Multibuild's logging API header */
#include <string.h>

#include <tees_rpmb.h>

#define D_HANDLE_INITIALIZED  0xA7A6A5A4
#define P_HANDLE_INITIALIZED  0xA4A5A6A7

typedef struct __TEES_RpmbDeviceHandle {
  qsee_stor_device_handle_t device_handle;
  uint32_t initialized;
} TEES_RpmbDeviceHandle_st;

typedef struct __TEES_RpmbPartitionHandle {
  qsee_stor_client_handle_t client_handle;
  TEES_RpmbDeviceHandle deviceHandle;
  TEES_RpmbPartitionInfo partitionInfo;
  uint32_t initialized;
} TEES_RpmbPartitionHandle_st;

TEE_Result TEES_RpmbDeviceInit(TEES_RpmbDeviceType deviceType,
                               TEES_RpmbDeviceHandle *device) {
  int ret;

  if (device == NULL) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (deviceType != TEES_RPMB_DEVICE_DEFAULT) {
    MB_LOGE("deviceType is not supported: deviceType = %d\n", deviceType);
    return TEE_ERROR_NOT_SUPPORTED;
  }

  *device = (struct __TEES_RpmbDeviceHandle *)
            TEE_Malloc(sizeof(struct __TEES_RpmbDeviceHandle), 0);
  if (NULL == *device) {
    MB_LOGE("Can not allocate memory.\n");
    return TEE_ERROR_GENERIC;
  }

  TEES_RpmbDeviceHandle_st *s = *device;

  ret = qsee_stor_device_init(QSEE_STOR_EMMC_RPMB, 0, &s->device_handle);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGE("Device init failure: ret=%d\n", ret);

    TEE_MemFill(device, 0, sizeof(struct __TEES_RpmbDeviceHandle));
    TEE_Free(device);

    return TEE_ERROR_GENERIC;
  }

  s->initialized = D_HANDLE_INITIALIZED;

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbOpenPartition(TEES_RpmbDeviceHandle device,
                                  uint32_t partitionId,
                                  TEES_RpmbPartitionHandle *partition) {
  int ret;

  if (device == NULL || device->initialized != D_HANDLE_INITIALIZED ||
      partition == NULL) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  *partition = (struct __TEES_RpmbPartitionHandle *)
               TEE_Malloc(sizeof(struct __TEES_RpmbPartitionHandle),
                          HINT_FILL_WITH_ZEROS);
  if (NULL == *partition) {
    MB_LOGE("Can not allocate memory.\n");
    return TEE_ERROR_GENERIC;
  }

  TEES_RpmbPartitionHandle_st *s = *partition;

  ret = qsee_stor_open_partition(&device->device_handle, partitionId,
                                 &s->client_handle);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGI("%s\n", __func__);
    MB_LOGE("qsee_stor_open_partition failure: ret=%d\n", ret);

    TEE_MemFill(*partition, 0, sizeof(struct __TEES_RpmbPartitionHandle));
    TEE_Free(*partition);

    if (ret == QSEE_STOR_PARTI_NOT_FOUND_ERROR) {
      return TEE_ERROR_ITEM_NOT_FOUND;
    } else {
      return TEE_ERROR_GENERIC;
    }
  }

  qsee_stor_client_info_t client_info;
  ret = qsee_stor_client_get_info(&s->client_handle, &client_info);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGE("qsee_stor_client_get_info failure: ret=%d\n", ret);
    TEE_MemFill(partition, 0, sizeof(struct __TEES_RpmbPartitionHandle));
    TEE_Free(partition);

    return TEE_ERROR_GENERIC;
  }

  s->deviceHandle = device;
  s->partitionInfo.partitionId = partitionId;
  s->partitionInfo.totalSectors = client_info.total_sectors;
  s->partitionInfo.bytesPerSector = client_info.bytes_per_sector;
  s->initialized = P_HANDLE_INITIALIZED;

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbCreatePartition(TEES_RpmbDeviceHandle device,
                                    uint32_t partitionId,
                                    uint32_t partitionSize,
                                    uint32_t flags) {
  int ret;
  uint16_t num_sectors;
  qsee_stor_device_info_t deviceInfo;

  if (flags != 0 || device == NULL ||
      device->initialized != D_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  ret = qsee_stor_device_get_info(&device->device_handle, &deviceInfo);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGE("Get device info failure: ret=%d\n", ret);
    return TEE_ERROR_GENERIC;
  }

  num_sectors = partitionSize / deviceInfo.bytes_per_sector;

  if (partitionSize % deviceInfo.bytes_per_sector != 0) {
    ++num_sectors;
  }

  if (num_sectors > deviceInfo.available_sectors) {
    return TEE_ERROR_STORAGE_NO_SPACE;
  }

  ret = qsee_stor_add_partition(&device->device_handle, partitionId,
                                num_sectors);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGE("%s: qsee_stor_add_partition failure: ret=%d\n", __func__, ret);
    return TEE_ERROR_GENERIC;
  }

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbRemovePartition(TEES_RpmbDeviceHandle device,
                                    uint32_t partitionId,
                                    uint32_t flags) {
  int ret;

  qsee_stor_client_handle_t client_handle;

  if (flags != 0 || device == NULL ||
      device->initialized != D_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  ret = qsee_stor_open_partition(&device->device_handle, partitionId,
                                 &client_handle);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGI("%s\n", __func__);
    MB_LOGE("qsee_stor_open_partition failure: ret=%d\n", ret);

    if (ret == QSEE_STOR_PARTI_NOT_FOUND_ERROR) {
      return TEE_ERROR_ITEM_NOT_FOUND;
    } else {
      return TEE_ERROR_GENERIC;
    }
  }

#ifdef HAS_RPMB_PARTITION_REMOVE
  ret = qsee_stor_remove_client(&client_handle);

  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGI("%s\n", __func__);
    MB_LOGE("qsee_stor_remove_client failure: ret=%d\n", ret);

    return TEE_ERROR_GENERIC;
  }
  return TEE_SUCCESS;

#else
  return TEE_ERROR_NOT_SUPPORTED;
#endif /* HAS_RPMB_PARTITION_REMOVE */
}

void TEES_RpmbClosePartition(TEES_RpmbPartitionHandle partition) {
  TEE_MemFill(partition, 0, sizeof(struct __TEES_RpmbPartitionHandle));
  TEE_Free(partition);
}

void TEES_RpmbDeviceClose(TEES_RpmbDeviceHandle device) {
  TEE_MemFill(device, 0, sizeof(struct __TEES_RpmbDeviceHandle));
  TEE_Free(device);
}

TEE_Result TEES_RpmbGetDeviceInfo(TEES_RpmbDeviceHandle device,
                                  TEES_RpmbDeviceInfo *deviceInfo) {
  int ret;
  qsee_stor_device_info_t device_info;

  if (device == NULL || deviceInfo == NULL ||
      device->initialized != D_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  ret = qsee_stor_device_get_info(&device->device_handle, &device_info);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGE("Get device info failure: ret=%d\n", ret);
    return TEE_ERROR_GENERIC;
  }

  deviceInfo->deviceId = (uint32_t)device_info.dev_id;
  deviceInfo->totalSectors = device_info.total_sectors;
  deviceInfo->bytesPerSector = device_info.bytes_per_sector;
  deviceInfo->freeSectors = device_info.available_sectors;

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbGetPartitionInfo(TEES_RpmbPartitionHandle partition,
                                     TEES_RpmbPartitionInfo *partitionInfo) {
  if (partition == NULL || partitionInfo == NULL ||
      partition->initialized != P_HANDLE_INITIALIZED) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  TEE_MemMove(partitionInfo, &partition->partitionInfo,
              sizeof(TEES_RpmbPartitionInfo));

  return TEE_SUCCESS;
}



TEE_Result TEES_RpmbWriteData(TEES_RpmbPartitionHandle partition,
                              uint8_t *dataBuffer,
                              uint32_t bufferLen,
                              uint32_t startSector,
                              uint32_t *sectors) {
  int ret;

  if (dataBuffer == NULL || bufferLen == 0 ||
      partition == NULL ||
      partition->initialized != P_HANDLE_INITIALIZED ||
      startSector >= partition->partitionInfo.totalSectors || sectors ==
      NULL) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (bufferLen > (partition->partitionInfo.totalSectors - startSector) *
      partition->partitionInfo.bytesPerSector) {
    return TEE_ERROR_STORAGE_NO_SPACE;
  }

  *sectors = (bufferLen / partition->partitionInfo.bytesPerSector);
  if (bufferLen % partition->partitionInfo.bytesPerSector != 0) {
    *sectors += 1;
  }

  ret = qsee_stor_write_sectors(&partition->client_handle, startSector,
                                *sectors, dataBuffer);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGE("qsee_stor_write_sectors failure: ret=%d\n", ret);
    *sectors = 0;
    return TEE_ERROR_GENERIC;
  }

  return TEE_SUCCESS;
}

TEE_Result TEES_RpmbReadData(TEES_RpmbPartitionHandle partition,
                             uint8_t *dataBuffer,
                             uint32_t *bufferLen,
                             uint32_t startSector,
                             uint32_t sectors) {
  int ret;

  if (dataBuffer == NULL || bufferLen == NULL ||
      partition == NULL ||
      partition->initialized != P_HANDLE_INITIALIZED ||
      startSector >= partition->partitionInfo.totalSectors ||
      sectors > partition->partitionInfo.totalSectors) {
    return TEE_ERROR_BAD_PARAMETERS;
  }

  if (*bufferLen < sectors * partition->partitionInfo.bytesPerSector) {
    return TEE_ERROR_SHORT_BUFFER;
  }

  *bufferLen = sectors * partition->partitionInfo.bytesPerSector;

  ret = qsee_stor_read_sectors(&partition->client_handle, startSector,
                               sectors, dataBuffer);
  if (ret != QSEE_STOR_SUCCESS) {
    MB_LOGE("qsee_stor_read_sectors failure: ret=%d\n", ret);
    *bufferLen = 0;
    return TEE_ERROR_GENERIC;
  }

  return TEE_SUCCESS;
}
