/*
 * Copyright (c) 2006-2016, ARM Limited, All Rights Reserved
 * SPDX-License-Identifier: Apache-2.0
 *
 * 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.
 */

// This file is deprecated so deprecation warnings when building it are silenced
#if   defined ( __CC_ARM )
#pragma diag_suppress 1361  // Deprecated declaration
#elif defined ( __GNUC__ )
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#endif

#include "storage-volume-manager/storage_volume_manager.h"
#include <string.h>
#include <inttypes.h>

/* redefine these macros to a printf() equivalent to emit trace */
#define tr_debug(...)
#define tr_error(...)

/*
 * The following variable records the volume-manager instance which last setup
 * StorageVolumeManager::storageCallback() as a callback for a storage driver.
 * We use this value in the callback handler to de-multiplex the callback into a
 * volume-manager.
 */
static StorageVolumeManager *activeVolumeManager;

InitializeCallback_t initializeCallback;

#define STORAGE_API_DEFINITIONS_FOR_VOLUME(N)                                          \
extern "C" ARM_DRIVER_VERSION GetVersion_ ## N(void) {                                 \
    return activeVolumeManager->volumes[(N)].GetVersion();                             \
}                                                                                      \
ARM_STORAGE_CAPABILITIES GetCapabilities_ ## N(void) {                                 \
    return activeVolumeManager->volumes[(N)].GetCapabilities();                        \
}                                                                                      \
int32_t Initialize_ ## N(ARM_Storage_Callback_t callback) {                            \
    return activeVolumeManager->volumes[(N)].Initialize(callback);                     \
}                                                                                      \
int32_t Uninitialize_ ## N(void) {                                                     \
    return activeVolumeManager->volumes[(N)].Uninitialize();                           \
}                                                                                      \
int32_t PowerControl_ ## N(ARM_POWER_STATE state) {                                    \
    return activeVolumeManager->volumes[(N)].PowerControl(state);                      \
}                                                                                      \
int32_t ReadData_ ## N(uint64_t addr, void *data, uint32_t size) {                     \
    return activeVolumeManager->volumes[(N)].ReadData(addr, data, size);               \
}                                                                                      \
int32_t ProgramData_ ## N(uint64_t addr, const void *data, uint32_t size) {            \
    return activeVolumeManager->volumes[(N)].ProgramData(addr, data, size);            \
}                                                                                      \
int32_t Erase_ ## N(uint64_t addr, uint32_t size) {                                    \
    return activeVolumeManager->volumes[(N)].Erase(addr, size);                        \
}                                                                                      \
int32_t EraseAll_ ## N(void) {                                                         \
    return activeVolumeManager->volumes[(N)].EraseAll();                               \
}                                                                                      \
ARM_STORAGE_STATUS GetStatus_ ## N(void) {                                             \
    return activeVolumeManager->volumes[(N)].GetStatus();                              \
}                                                                                      \
int32_t GetInfo_ ## N(ARM_STORAGE_INFO *infoP) {                                       \
    return activeVolumeManager->volumes[(N)].GetInfo(infoP);                           \
}                                                                                      \
uint32_t ResolveAddress_ ## N(uint64_t addr) {                                         \
    return activeVolumeManager->volumes[(N)].ResolveAddress(addr);                     \
}                                                                                      \
int32_t GetNextBlock_ ## N(const ARM_STORAGE_BLOCK* prevP, ARM_STORAGE_BLOCK *nextP) { \
    return activeVolumeManager->volumes[(N)].GetNextBlock(prevP, nextP);               \
}                                                                                      \
int32_t GetBlock_ ## N(uint64_t addr, ARM_STORAGE_BLOCK *blockP) {                     \
    return activeVolumeManager->volumes[(N)].GetBlock(addr, blockP);                   \
}                                                                                      \
ARM_DRIVER_STORAGE VIRTUAL_MTD_ ## N = {                                               \
    .GetVersion      = GetVersion_ ## N,                                               \
    .GetCapabilities = GetCapabilities_ ## N,                                          \
    .Initialize      = Initialize_ ## N,                                               \
    .Uninitialize    = Uninitialize_ ## N,                                             \
    .PowerControl    = PowerControl_ ## N,                                             \
    .ReadData        = ReadData_ ## N,                                                 \
    .ProgramData     = ProgramData_ ## N,                                              \
    .Erase           = Erase_ ## N,                                                    \
    .EraseAll        = EraseAll_ ## N,                                                 \
    .GetStatus       = GetStatus_ ## N,                                                \
    .GetInfo         = GetInfo_ ## N,                                                  \
    .ResolveAddress  = ResolveAddress_ ## N,                                           \
    .GetNextBlock    = GetNextBlock_ ## N,                                             \
    .GetBlock        = GetBlock_ ## N,                                                 \
};

#define STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_1 STORAGE_API_DEFINITIONS_FOR_VOLUME(0)
#define STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_2 STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_1 STORAGE_API_DEFINITIONS_FOR_VOLUME(1)
#define STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_3 STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_2 STORAGE_API_DEFINITIONS_FOR_VOLUME(2)
#define STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_4 STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_3 STORAGE_API_DEFINITIONS_FOR_VOLUME(3)
/* ... add more of the above if ever needed */

#define STORAGE_API_DEFINITIONS_FOR_VOLUMES(N) EXPAND(CONCATENATE(STORAGE_API_DEFINITIONS_LIST_FOR_VOLUME_, N))

STORAGE_API_DEFINITIONS_FOR_VOLUMES(MAX_VOLUMES);

int32_t StorageVolumeManager::initialize(ARM_DRIVER_STORAGE *mtd, InitializeCallback_t callback)
{
    activeVolume        = NULL;
    initializeCallback  = callback;

    storage             = mtd;
    storageCapabilities = mtd->GetCapabilities();

    int32_t rc = mtd->GetInfo(&storageInfo);
    if (rc != ARM_DRIVER_OK) {
        tr_error("StorageVolumeManager::initialize: call to GetInfo() failed with %" PRId32, rc);
        return ARM_DRIVER_ERROR;
    }

    rc = mtd->Initialize(storageCallback);
    if (rc < ARM_DRIVER_OK) {
        tr_error("Initialize() failed with error %" PRId32, rc);
        return ARM_DRIVER_ERROR;
    }

    activeVolumeManager = this;
    if (rc == ARM_DRIVER_OK) {
        /* there is pending asynchronous activity which will result in a callback later */
        return ARM_DRIVER_OK;
    }

    /* Clear previously allocated volumes */
    for (size_t i = 0; i < MAX_VOLUMES; i++) {
        if (volumes[i].isAllocated()) {
            volumes[i].deallocate();
        }
    }

    /* synchronous completion */
    initialized = true;
    return 1;
}

int32_t StorageVolumeManager::addVolume(uint64_t addr, uint64_t size, StorageVolume **volumePP)
{
    tr_debug("StorageVolumeManager_addVolume: addr = %" PRIu32 ", size = %" PRIu32, (uint32_t)addr, (uint32_t)size);

    *volumePP = NULL;

    /*
     * sanity checks for arguments
     */
    ARM_STORAGE_INFO info;
    int32_t rc;
    rc = storage->GetInfo(&info);
    if (rc != ARM_DRIVER_OK) {
        tr_error("StorageVolumeManager_addVolume: storage->GetInfo() failed with %" PRId32, rc);
        return ARM_DRIVER_ERROR;
    }
    if (size > info.total_storage) {
        tr_error("StorageVolumeManager_addVolume: 'size' parameter too large: %" PRIu32, (uint32_t)size);
        return ARM_DRIVER_ERROR;
    }
    ARM_STORAGE_BLOCK firstBlock;
    rc = storage->GetNextBlock(NULL, &firstBlock);
    if (rc != ARM_DRIVER_OK) {
        tr_error("StorageVolumeManager_addVolume: storage->GetNextBlock() failed with %" PRId32, rc);
        return ARM_DRIVER_ERROR;
    }
    if ((addr < firstBlock.addr) || ((addr + size) > (firstBlock.addr + info.total_storage))) {
        tr_error("StorageVolumeManager_addVolume: given range [%" PRIu32 ", %" PRIu32 ") isn't entirely contained within available storage range [%" PRIu32 ", %" PRIu32 ")",
            (uint32_t)addr, (uint32_t)(addr + size), (uint32_t)firstBlock.addr, (uint32_t)(firstBlock.addr + info.total_storage));
        return ARM_DRIVER_ERROR;
    }

    /* Find an unused volume. */
    uint32_t index = findIndexOfUnusedVolume();
    if (index == MAX_VOLUMES) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_EXHASTED_VOLUMES;
    }

    /* setup volume */
    StorageVolume *volumeP = &volumes[index];
    volumeP->setup(addr, size, this);
    *volumePP = volumeP;
    tr_debug("StorageVolumeManager_addVolume: setup volume at index %" PRIu32, index);

    return ARM_DRIVER_OK;
}

int32_t StorageVolumeManager::addVolume_C(uint64_t addr, uint64_t size, _ARM_DRIVER_STORAGE *mtd)
{
    int32_t rc;
    StorageVolume *volumeP;
    if ((rc = addVolume(addr, size, &volumeP)) < ARM_DRIVER_OK) {
        return rc;
    }

    /* locate index of the allocated volume */
    size_t index;
    for (index = 0; index < MAX_VOLUMES; index++) {
        if (volumes[index].isAllocated() && (&volumes[index] == volumeP)) {
            break;
        }
    }
    if (index == MAX_VOLUMES) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_EXHASTED_VOLUMES;
    }

    if (index == 0) {
        *mtd = VIRTUAL_MTD_0;
    } else if (index == 1) {
        *mtd = VIRTUAL_MTD_1;
    } else if (index == 2) {
        *mtd = VIRTUAL_MTD_2;
    } else if (index == 3) {
        *mtd = VIRTUAL_MTD_3;
    } else {
        return ARM_DRIVER_ERROR;
    }

    return ARM_DRIVER_OK;
}

int32_t StorageVolumeManager::lookupVolume(uint64_t addr, StorageVolume **volumePP)
{
    /*
     * Traverse the volumes in reverse order of creation; this allows newly created volumes to supersede the older ones.
     */
    for (size_t index = MAX_VOLUMES - 1; index > 0; --index) {
        StorageVolume *volume = &volumes[index];
        if ((addr >= volume->getVolumeOffset()) && (addr < (volume->getVolumeOffset() + volume->getVolumeSize()))) {
            *volumePP = volume;
            return ARM_DRIVER_OK;
        }
    }

    return ARM_DRIVER_ERROR;
}

void StorageVolumeManager::storageCallback(int32_t status, ARM_STORAGE_OPERATION operation)
{
    tr_debug("StorageVolumeManager_callback: operation = %u", operation);
    StorageVolumeManager *volumeManager = activeVolumeManager;

    switch (operation) {
        case ARM_STORAGE_OPERATION_INITIALIZE:
            volumeManager->initialized = true;
            if (initializeCallback != NULL) {
                initializeCallback(status);
            }
            break;

        case ARM_STORAGE_OPERATION_POWER_CONTROL:
        case ARM_STORAGE_OPERATION_READ_DATA:
        case ARM_STORAGE_OPERATION_PROGRAM_DATA:
        case ARM_STORAGE_OPERATION_ERASE:
        case ARM_STORAGE_OPERATION_ERASE_ALL:
            if (volumeManager->activeVolume != NULL) {
                /* Reset activeVolume and invoke callback. We reset activeVolume before the
                 * callback because the callback may attempt to launch another asynchronous
                 * operation, which requires 'activeVolume' to be NULL. */
                StorageVolume *callbackVolume = volumeManager->activeVolume; /* remember the volume which will receive the callback. */
                volumeManager->activeVolume   = NULL;

                if (callbackVolume->isAllocated() && callbackVolume->getCallback()) {
                    (callbackVolume->getCallback())(status, operation);
                }
            }
            break;

        default:
            tr_error("StorageVolumeManager_callback: unknown operation %u", operation);
            break;
    }
}

size_t StorageVolumeManager::findIndexOfUnusedVolume(void) const {
    size_t index;
    for (index = 0; index < MAX_VOLUMES; index++) {
        if (!volumes[index].isAllocated()) {
            break;
        }
    }
    return index;
}
