/*
 * 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 tr_debug() to a printf() equivalent to emit trace */
#define tr_debug(...)


void StorageVolume::setup(uint64_t _addr, uint64_t _size, StorageVolumeManager *_volumeManager)
{
    volumeOffset  = _addr;
    volumeSize    = _size;
    volumeManager = _volumeManager;
    allocated     = true;
}

ARM_DRIVER_VERSION StorageVolume::GetVersion(void)
{
    ARM_DRIVER_VERSION bad_ver = {0, 0};

    if (!allocated) {
        return bad_ver;
    }
    return volumeManager->getStorage()->GetVersion();
}

ARM_STORAGE_CAPABILITIES StorageVolume::GetCapabilities(void)
{
    ARM_STORAGE_CAPABILITIES bad_cap;

    if (!allocated) {
        memset(&bad_cap, 0, sizeof(ARM_STORAGE_CAPABILITIES));
        return bad_cap;
    }
    return volumeManager->getStorage()->GetCapabilities();
}

int32_t StorageVolume::Initialize(ARM_Storage_Callback_t _callback)
{
    if (!allocated) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_VOLUME_NOT_ALLOCATED;
    }

    callback = _callback; /* nothing else to do since we've already initialized the storage */
    return 1; /* synchronous completion. */
}

int32_t StorageVolume::Uninitialize(void)
{
    if (!allocated) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_VOLUME_NOT_ALLOCATED;
    }

    return 1; /* synchronous completion. */
}

int32_t StorageVolume::PowerControl(ARM_POWER_STATE state)
{
    tr_debug("called powerControl(%u)", state);
    if (!allocated) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_VOLUME_NOT_ALLOCATED;
    }
    if (volumeManager->activeVolume != NULL) {
        return ARM_DRIVER_ERROR_BUSY;
    }

    volumeManager->activeVolume = this;
    int32_t rc = volumeManager->getStorage()->PowerControl(state);
    if (rc != ARM_DRIVER_OK) {
        volumeManager->activeVolume = NULL; /* we're certain that there is no more pending asynch. activity */
    }
    return rc;
}

int32_t StorageVolume::ReadData(uint64_t addr, void *data, uint32_t size)
{
    tr_debug("called ReadData(%" PRIu32 ", %" PRIu32 ")", (uint32_t)addr, size);
    if (!allocated) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_VOLUME_NOT_ALLOCATED;
    }
    if (volumeManager->activeVolume != NULL) {
        return ARM_DRIVER_ERROR_BUSY;
    }
    if ((size > volumeSize) || ((addr + size) > volumeSize)) {
        return ARM_DRIVER_ERROR_PARAMETER;
    }

    volumeManager->activeVolume = this;
    int32_t rc = volumeManager->getStorage()->ReadData(volumeOffset + addr, data, size);
    if (rc != ARM_DRIVER_OK) {
        volumeManager->activeVolume = NULL; /* we're certain that there is no more pending asynch. activity */
    }
    return rc;
}

int32_t StorageVolume::ProgramData(uint64_t addr, const void *data, uint32_t size)
{
    tr_debug("called ProgramData(%" PRIu32 ", %" PRIu32 ")", (uint32_t)addr, size);
    if (!allocated) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_VOLUME_NOT_ALLOCATED;
    }
    if (volumeManager->activeVolume != NULL) {
        return ARM_DRIVER_ERROR_BUSY;
    }
    if ((size > volumeSize) || ((addr + size) > volumeSize)) {
        return ARM_DRIVER_ERROR_PARAMETER;
    }

    volumeManager->activeVolume = this;
    int32_t rc = volumeManager->getStorage()->ProgramData(volumeOffset + addr, data, size);
    if (rc != ARM_DRIVER_OK) {
        volumeManager->activeVolume = NULL; /* we're certain that there is no more pending asynch. activity */
    }
    return rc;
}

int32_t StorageVolume::Erase(uint64_t addr, uint32_t size)
{
    tr_debug("called erase(%" PRIu32 ", %" PRIu32 ")", (uint32_t)addr, size);
    if (!allocated) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_VOLUME_NOT_ALLOCATED;
    }
    if (volumeManager->activeVolume != NULL) {
        return ARM_DRIVER_ERROR_BUSY;
    }
    if ((size > volumeSize) || ((addr + size) > volumeSize)) {
        return ARM_DRIVER_ERROR_PARAMETER;
    }

    volumeManager->activeVolume = this;
    int32_t rc = volumeManager->getStorage()->Erase(volumeOffset + addr, size);
    if (rc != ARM_DRIVER_OK) {
        volumeManager->activeVolume = NULL; /* we're certain that there is no more pending asynch. activity */
    }
    return rc;
}

int32_t StorageVolume::EraseAll(void)
{
    tr_debug("called eraseAll");
    if (!allocated) {
        return STORAGE_VOLUME_MANAGER_STATUS_ERROR_VOLUME_NOT_ALLOCATED;
    }
    if (volumeManager->activeVolume != NULL) {
        return ARM_DRIVER_ERROR_BUSY;
    }

    int32_t rc;

    /* Allow EraseAll() only if the volume spans the entire storage. */
    {
        ARM_STORAGE_INFO info;
        rc = volumeManager->getStorage()->GetInfo(&info);
        if (rc != ARM_DRIVER_OK) {
            return ARM_DRIVER_ERROR;
        }

        if ((volumeOffset != 0) || (volumeSize != info.total_storage)) {
            return ARM_DRIVER_ERROR_UNSUPPORTED;
        }
    }

    volumeManager->activeVolume = this;
    rc = volumeManager->getStorage()->EraseAll();
    if (rc != ARM_DRIVER_OK) {
        volumeManager->activeVolume = NULL; /* we're certain that there is no more pending asynch. activity */
    }
    return rc;
}

ARM_STORAGE_STATUS StorageVolume::GetStatus(void)
{
    const uint32_t busy = ((volumeManager->activeVolume != NULL) ? (uint32_t)1 : (uint32_t)0);
    ARM_STORAGE_STATUS status = {0, 0};
    status.busy = busy;
    return status;
}

int32_t StorageVolume::GetInfo(ARM_STORAGE_INFO *infoP)
{
    int32_t rc;
    rc = volumeManager->getStorage()->GetInfo(infoP);
    if (rc != ARM_DRIVER_OK) {
        return ARM_DRIVER_ERROR;
    }

    infoP->total_storage = volumeSize;
    return ARM_DRIVER_OK;
}

uint32_t StorageVolume::ResolveAddress(uint64_t addr) {
    return (uint32_t)(volumeOffset + addr);
}

int32_t StorageVolume::GetNextBlock(const ARM_STORAGE_BLOCK* prevP, ARM_STORAGE_BLOCK *nextP)
{
    int32_t rc;
    ARM_STORAGE_BLOCK tmpBlock;
    do {
        /* iterate forward */
        rc = volumeManager->getStorage()->GetNextBlock(prevP, &tmpBlock);
        if (rc != ARM_DRIVER_OK) {
            return rc;
        }

        /* Stop iteration if we have progressed past the boundary of this volume. */
        if (tmpBlock.addr >= (volumeOffset + volumeSize)) {
            return ARM_DRIVER_ERROR;
        }
    } while (!this->overlapsWithBlock(&tmpBlock));

    if (nextP) {
        memcpy(nextP, &tmpBlock, sizeof(ARM_STORAGE_BLOCK));
        transformBlockToVolume(nextP);
    }
    return ARM_DRIVER_OK;
}

int32_t StorageVolume::GetBlock(uint64_t addr, ARM_STORAGE_BLOCK *blockP)
{
    ARM_STORAGE_BLOCK tmpBlock;
    int32_t rc = volumeManager->getStorage()->GetBlock(ResolveAddress(addr), &tmpBlock);
    if (rc != ARM_DRIVER_OK) {
        return rc;
    }
    if (!this->overlapsWithBlock(&tmpBlock)) {
        return ARM_DRIVER_ERROR;
    }

    if (blockP) {
        memcpy(blockP, &tmpBlock, sizeof(ARM_STORAGE_BLOCK));
        transformBlockToVolume(blockP);
    }
    return ARM_DRIVER_OK;
}
