#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include "tee_client_api.h"

enum cmd {
    CMD_SPI_CONFIG = 0,
    CMD_SPI_WRITE,
    CMD_SPI_READ,
    CMD_SPI_WRITE_READ,
    CMD_SPI_WRITE_READ_NOVERIFY,
    CMD_FULL_TEST = 5,
    CMD_FULL_TEST_NOVERIFY = 6
};

/**
 * Test   : %test_id%
 * Responsible : SRR
 * HW depended : Y
 * Dep reason : SPI
 * Level 1 : Secure Kernel
 * Level 2 : Drivers
 * Level 3 : SPI
 * Client : client
 * TA     : ta (7aa02708-cb09-11e4-97c5-14dae9ebf7fb)
 */

/**
 * TestCase     : %testcase_id%
 * Name         : Validate correctness of SPI driver implementation.
 * Description  : Use SPI device to transfer and receive some data.
 *                Test requires two arguments:
 *                [port] - SPI port number
 *                [cmd] - CMD to be send to driver
 *                CMD_MAX_FULL_TEST used in scope of this test case \
 *                to send a sequence of commands in order to execute \
 *                basic SPI operations and check for result correctness.
 *                Usage example: tee_test_spi_common 2 5
 * Precondition : Enable CONFIG_SPI in secure kernel.
 *                MISO and MOSI pins should be physically shorten
 *                on a device for a chosen port.
 * Scenario     : 1. CA sends request to TA to initalize SPI clock.
 *                2. CA sends request to TA to write some data to TX- register.
 *                3. CA sends request to TA to read some data from RX- register.
 *                4. CA sends request to TA to write and read some data \
 *                from SPI RX/TX- registers and verify corectness. As \
 *                MISO and MOSI pins are shorten, we should receive \
 *                the same buffer as we passed in.
 * Pass Cond    : Pass if every request were performed correctly and \
 *                correct buffer was received from SPI.
 */

/**
 * TestCase     : %testcase_id%
 * Name         : Validate correctness of SPI driver implementation.
 * Description  : Use SPI device to transfer and receive some data.
 *                Test requires two arguments:
 *                [port] - SPI port number
 *                [cmd] - CMD to be send to driver
 *                CMD_MAX_FULL_TEST used in scope of this test case \
 *                to send a sequence of commands in order to execute \
 *                basic SPI operations.
 *                Usage example: tee_test_spi_common 2 6
 * Precondition : Enable CONFIG_SPI in secure kernel.
 * Scenario     : 1. CA sends request to TA to initalize SPI clock.
 *                2. CA sends request to TA to write some data to TX- register.
 *                3. CA sends request to TA to read some data from RX- register.
 *                4. CA sends request to TA to write and read some data \
 *                from SPI RX/TX- registers without verifying of correctness.
 * Pass Cond    : Pass if every request were performed correctly.
 */


static const TEEC_UUID uuid = {
    .timeLow = 0x7aa02708,
    .timeMid = 0xcb09,
    .timeHiAndVersion = 0x11e4,
    .clockSeqAndNode = {0x97, 0xc5, 0x14, 0xda, 0xe9, 0xeb, 0xf7, 0xfb},
};

static TEEC_Result do_execute_cmd(TEEC_Context *context, unsigned int port, unsigned int cmd)
{
    TEEC_Result res;
    TEEC_Operation operation;
    TEEC_Session session;
    uint32_t returnOrigin;

    res = TEEC_OpenSession(context, &session, &uuid, 0, 0, NULL, &returnOrigin);
    if (res != TEEC_SUCCESS)
    {
        printf("TEEC_OpenSession returned %x from %x\n", res, returnOrigin);
        return res;
    }

    operation.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INPUT, TEEC_NONE, TEEC_NONE,
                                            TEEC_NONE);
    operation.params[0].value.a = port;

    res = TEEC_InvokeCommand(&session, cmd, &operation, &returnOrigin);
    if (res != TEEC_SUCCESS)
    {
        printf("TEEC_InvokeCommand returned %x from %x\n", res, returnOrigin);
    }

    TEEC_CloseSession(&session);
    return res;
}

int main(int argc, char *argv[])
{
    TEEC_Context context;
    TEEC_Result res = TEEC_ERROR_GENERIC;
    unsigned int cmd, port;

    if (argc != 3) {
        printf("Incorrect input, usage: tee_test_spi_common [port] [cmd]\n");
        goto out;
    } else {
        port = atoi(argv[1]);
        cmd = atoi(argv[2]);
    }

    printf("port = %u, cmd = %u\n", port, cmd);

    res = TEEC_InitializeContext(NULL, &context);
    if (res != TEEC_SUCCESS) {
        printf("InitializeContext failed\n");
        goto out;
    }

    switch (cmd) {
        case CMD_FULL_TEST:
            res = do_execute_cmd(&context, port, CMD_SPI_CONFIG);
            res |= do_execute_cmd(&context, port, CMD_SPI_WRITE);
            res |= do_execute_cmd(&context, port, CMD_SPI_READ);
            res |= do_execute_cmd(&context, port, CMD_SPI_WRITE_READ);
        break;
        case CMD_FULL_TEST_NOVERIFY:
            res = do_execute_cmd(&context, port, CMD_SPI_CONFIG);
            res |= do_execute_cmd(&context, port, CMD_SPI_WRITE);
            res |= do_execute_cmd(&context, port, CMD_SPI_READ);
            res |= do_execute_cmd(&context, port, CMD_SPI_WRITE_READ_NOVERIFY);
        break;
        default:
            res = do_execute_cmd(&context, port, cmd);
    }

    TEEC_FinalizeContext(&context);
out:
    puts(res == TEEC_SUCCESS ? "Test passed" : "Test failed");
    return (res != TEEC_SUCCESS);
}
