/*******************************************************************************
    Copyright (c) 2015 NVIDIA Corporation

    Permission is hereby granted, free of charge, to any person obtaining a copy
    of this software and associated documentation files (the "Software"), to
    deal in the Software without restriction, including without limitation the
    rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
    sell copies of the Software, and to permit persons to whom the Software is
    furnished to do so, subject to the following conditions:

        The above copyright notice and this permission notice shall be
        included in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
    THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
    FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
    DEALINGS IN THE SOFTWARE.

 *******************************************************************************/

#include "uvm_gpu_ops_tests.h"
#include "uvm_channel_mgmt.h"
#include "uvm_linux.h"
#include "uvm_common.h"
#include "nv_uvm_interface.h"
#include "uvm_common_test.h"

#define INIT_GPU_BUFFER1_VALUE (0xBA)
#define INIT_GPU_BUFFER2_VALUE (0xAD)
#define INIT_CPU_BUFFER1_VALUE (0xCA)
#define INIT_CPU_BUFFER2_VALUE (0xFE)
#define INIT_GARBAGE_VALUE     (0x43)

// Subtests

// Test if the dependency between two op in the same channel pool is correct.
// To do so we are doing two copies into the same region and one should wait
// the other before starting.
static
NV_STATUS channel_directed_singlepool(UvmChannelManager *channelManager,
                                      UvmtestMemblock *srcBuffer1,
                                      UvmtestMemblock *srcBuffer2,
                                      UvmtestMemblock *dstBuffer1,
                                      UvmTracker *tracker)
{
    UvmGpuPointer      dstBuffer2;
    NvU8               expectedValue = 0;
    NV_STATUS          status = NV_OK;

    // Right now source buffer 2 have to be located on CPU for the final check
    if (srcBuffer2->location != MEMBLOCK_CPU)
        return NV_ERR_INVALID_ARGUMENT;
    if (srcBuffer1->size > dstBuffer1->size)
        return NV_ERR_INVALID_ARGUMENT;

    // Initialize buffers
    // (for this test we only need the first and the last byte to be init)
    // TODO Implement a GPU memset support
    *(NvU8*)srcBuffer1->cpu = INIT_GPU_BUFFER1_VALUE;
    status = uvmtest_memcpy_virt(channelManager,
                                 dstBuffer1->gpu,
                                 srcBuffer1->gpu,
                                 1, NULL, NULL);
    if (NV_OK != status)
        return status;
    status = uvmtest_memcpy_virt(channelManager,
                                 dstBuffer1->gpu + dstBuffer1->size - 1,
                                 srcBuffer1->gpu,
                                 1, NULL, NULL);
    if (NV_OK != status)
        return status;
    *(NvU8*)srcBuffer1->cpu = INIT_CPU_BUFFER1_VALUE;
    *(NvU8*)srcBuffer2->cpu = INIT_CPU_BUFFER2_VALUE;
    *((NvU8*)srcBuffer1->cpu + srcBuffer1->size - 1) = INIT_CPU_BUFFER1_VALUE;
    *((NvU8*)srcBuffer2->cpu + srcBuffer2->size - 1) = INIT_CPU_BUFFER2_VALUE;

    expectedValue = *(NvU8*)srcBuffer2->cpu;

    // Create the second destination buffer in the first destination buffer
    // So the two buffers are overlapping
    dstBuffer2 = dstBuffer1->gpu + srcBuffer1->size - 1;

    status = uvmtest_memcpy_virt(channelManager,
                                 dstBuffer1->gpu, srcBuffer1->gpu,
                                 srcBuffer1->size, NULL, tracker);
    if (NV_OK != status)
        return status;
    status = uvmtest_memcpy_virt(channelManager, dstBuffer2, srcBuffer2->gpu,
                                 1, tracker, tracker);
    if (NV_OK != status)
        return status;

    // Wait until the operations are finished
    status = uvm_wait_for_tracker(tracker);
    if (NV_OK != status)
        return status;

    // Now check the value
    *(NvU8*)srcBuffer2->cpu = INIT_GARBAGE_VALUE;
    status = uvmtest_memcpy_virt(channelManager, srcBuffer2->gpu, dstBuffer2,
                                 1, NULL, NULL);
    if (NV_OK != status)
        return status;

    if (*(NvU8*)srcBuffer2->cpu != expectedValue)
    {
        UVM_ERR_PRINT("Invalid data Expected:%d Got:%d\n",
                      (int)expectedValue,
                      (int)*(NvU8*)srcBuffer2->cpu);
        return NV_ERR_INVALID_DATA;
    }
    return NV_OK;
}

// Tests
NV_STATUS uvmtest_channel_directed(UvmGpuUuid *gpuUuidStruct)
{
    const NvU64 blockSize = 16 * 1024 * 1024;

    NV_STATUS status = NV_OK;
    UvmChannelManager *channelManager = NULL;
    uvmGpuAddressSpaceHandle hVaSpace;

    UvmtestMemblock cpuBuffer1;
    UvmtestMemblock cpuBuffer2;
    UvmtestMemblock gpuBuffer1;
    UvmtestMemblock gpuBuffer2;

    UvmTracker tracker;

    // Setup
    UVM_DBG_PRINT_UUID("Entering", gpuUuidStruct);

    memset(&cpuBuffer1, 0, sizeof(cpuBuffer1));
    memset(&cpuBuffer2, 0, sizeof(cpuBuffer2));
    memset(&gpuBuffer1, 0, sizeof(gpuBuffer1));
    memset(&gpuBuffer2, 0, sizeof(gpuBuffer2));

    uvm_init_tracker(&tracker);

    // TODO: Here we create a new channel manager per major test. As
    // mentioned by Mark this will be really slow on the emulator. We should
    // get rid of this as soon as the API will allow us to do so.
    status = uvm_create_channel_manager(gpuUuidStruct, &channelManager);
    if (NV_OK != status)
    {
        UVM_ERR_PRINT_NV_STATUS("Could not allocate Uvm Channel Manager.",
                                status);
        goto cleanup;
    }
    hVaSpace = channelManager->channelPool.hVaSpace;

    status = uvmtest_alloc_virt_cpu(hVaSpace, &cpuBuffer1, blockSize, MAP_CPU);
    if (NV_OK != status)
        goto cleanup;
    status = uvmtest_alloc_virt_cpu(hVaSpace, &cpuBuffer2, blockSize, MAP_CPU);
    if (NV_OK != status)
        goto cleanup;
    status = uvmtest_alloc_virt_gpu(hVaSpace, &gpuBuffer1, blockSize, 0);
    if (NV_OK != status)
        goto cleanup;
    status = uvmtest_alloc_virt_gpu(hVaSpace, &gpuBuffer2, blockSize, 0);
    if (NV_OK != status)
        goto cleanup;

    // Test
    UVM_RUN_SUBTEST(status,
                    channel_directed_singlepool,
                    channelManager,
                    &cpuBuffer1, &cpuBuffer2, &gpuBuffer1,
                    &tracker);
    if (NV_OK != status)
        goto cleanup;

    // Teardown
cleanup:
    uvmtest_free_virt(&cpuBuffer1);
    uvmtest_free_virt(&cpuBuffer2);
    uvmtest_free_virt(&gpuBuffer1);
    uvmtest_free_virt(&gpuBuffer2);

    if (channelManager)
        uvm_destroy_channel_manager(channelManager);

    if (uvm_shrink_tracker(&tracker))
        UVM_ERR_PRINT("tracker state not as expected.");

    return status;
}
