/*! @file
 * @brief Implementation of auxiliary functions of bdbuf tests.
 *
 * Copyright (C) 2010 OKTET Labs, St.-Petersburg, Russia
 * Author: Oleg Kravtsov <Oleg.Kravtsov@oktetlabs.ru>
 *
 * The license and distribution terms for this file may be
 * found in the file LICENSE in this distribution or at
 * http://www.rtems.org/license/LICENSE.
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#include <rtems.h>
#include <rtems/io.h>
#include <rtems/bdbuf.h>
#include <rtems/inttypes.h>
#include "bdbuf_tests.h"

#include <tmacros.h>

struct bdbuf_test_descr {
    void (* main)(void);
} bdbuf_tests[] = {
    { bdbuf_test1_1_main },
    { bdbuf_test1_2_main },
    { bdbuf_test1_3_main },
    { bdbuf_test1_4_main },
    { bdbuf_test1_5_main },

    { bdbuf_test2_1_main },
    { bdbuf_test2_2_main },

    { bdbuf_test3_1_main },
    { bdbuf_test3_2_main },
    { bdbuf_test3_3_main },

    { bdbuf_test4_1_main },
    { bdbuf_test4_2_main },
    { bdbuf_test4_3_main },
};

#define TEST_SEM_ATTRIBS RTEMS_DEFAULT_ATTRIBUTES

/** Device ID used for testing */
rtems_disk_device *test_dd = NULL;

/** Test result variable */
bool       good_test_result = true;

test_ctx g_test_ctx;

const char *test_name = "NO TEST";

bdbuf_test_msg test_drv_msg;

/** Count of messages in RX message queue used on disk driver side. */
#define TEST_DRV_RX_MQUEUE_COUNT 10
/** Name of disk driver RX message queue. */
#define TEST_DRV_RX_MQUEUE_NAME  (rtems_build_name( 'M', 'Q', 'D', ' ' ))

/** Count of messages in Test task RX message queue */
#define TEST_TASK_RX_MQUEUE_COUNT 10
/** Name of Test task RX message queue */
#define TEST_TASK_RX_MQUEUE_NAME  (rtems_build_name( 'M', 'Q', 'T', ' ' ))

rtems_status_code
bdbuf_test_start_aux_task(rtems_name name,
                          rtems_task_entry entry_point,
                          rtems_task_argument arg,
                          Objects_Id *id)
{
    rtems_status_code rc;
    Objects_Id        task_id;

    rc = rtems_task_create(name, BDBUF_TEST_THREAD_PRIO_NORMAL, 1024 * 2,
                           RTEMS_PREEMPT | RTEMS_NO_TIMESLICE | RTEMS_NO_ASR,
                           RTEMS_LOCAL | RTEMS_NO_FLOATING_POINT,
                           &task_id);
    if (rc != RTEMS_SUCCESSFUL)
    {
        printf("Failed to create task\n");
        return rc;
    }

    rc = rtems_task_start(task_id, entry_point, arg);
    if (rc != RTEMS_SUCCESSFUL)
    {
        printf("Failed to start task\n");
    }
    else
    {
        if (id != NULL)
            *id = task_id;
    }
    return rc;
}

void
run_bdbuf_tests()
{
    rtems_status_code sc;
    unsigned int      i;
    int               fd;
    int               rv;

    /* Create a message queue to get events from disk driver. */
    sc = rtems_message_queue_create(TEST_TASK_RX_MQUEUE_NAME,
                                    TEST_TASK_RX_MQUEUE_COUNT,
                                    sizeof(bdbuf_test_msg),
                                    RTEMS_DEFAULT_ATTRIBUTES,
                                    &g_test_ctx.test_qid);

    if (sc != RTEMS_SUCCESSFUL)
    {
        printf("Failed to create message queue for test task: %u\n", sc);
        return;
    }

    sc = test_disk_initialize();
    rtems_test_assert(sc == RTEMS_SUCCESSFUL);

    fd = open(TEST_DISK_NAME, O_RDWR);
    rtems_test_assert(fd >= 0);

    rv = rtems_disk_fd_get_disk_device(fd, &test_dd);
    rtems_test_assert(rv == 0);

    rv = close(fd);
    rtems_test_assert(rv == 0);

    /*
     * On initialization test disk device driver registers
     * its RX message queue, so we just need to locate it.
     */
    sc = rtems_message_queue_ident(TEST_DRV_RX_MQUEUE_NAME,
                                   RTEMS_SEARCH_ALL_NODES,
                                   &g_test_ctx.test_drv_qid);
    if (sc != RTEMS_SUCCESSFUL)
    {
        printf("Failed to find Test Driver Queue: %u\n", sc);
        return;
    }

    for (i = 0; i < ARRAY_NUM(g_test_ctx.test_sync_main); i++)
    {
        sc = rtems_semaphore_create(rtems_build_name('T', 'S', 'M', '0' + i),
                                    0, TEST_SEM_ATTRIBS, 0,
                                    &g_test_ctx.test_sync_main[i]);
        if (sc != RTEMS_SUCCESSFUL)
        {
            printf("Failed to create sync sem for test task: %u\n", sc);
            return;
        }
    }

    for (i = 0; i < ARRAY_NUM(g_test_ctx.test_sync); i++)
    {
        sc = rtems_semaphore_create(rtems_build_name('T', 'S', 'T', '0' + i),
                                    0, TEST_SEM_ATTRIBS, 0,
                                    &g_test_ctx.test_sync[i]);
        if (sc != RTEMS_SUCCESSFUL)
        {
            printf("Failed to create sync sem for test task #%d: %u\n", i + 1, sc);
            return;
        }
    }

    sc = rtems_semaphore_create(rtems_build_name('T', 'S', 'M', 'E'),
                                0, TEST_SEM_ATTRIBS, 0,
                                &g_test_ctx.test_end_main);
    if (sc != RTEMS_SUCCESSFUL)
    {
        printf("Failed to create end sync sem for test task: %u\n", sc);
        return;
    }

    for (i = 0; i < ARRAY_NUM(g_test_ctx.test_task); i++)
        g_test_ctx.test_task[i] = OBJECTS_ID_NONE;

    for (i = 0; i < sizeof(bdbuf_tests) / sizeof(bdbuf_tests[0]); i++)
    {
        bdbuf_tests[i].main();
    }
}


rtems_status_code
bdbuf_test_create_drv_rx_queue(Objects_Id *id)
{
    return rtems_message_queue_create(TEST_DRV_RX_MQUEUE_NAME,
                                      TEST_DRV_RX_MQUEUE_COUNT,
                                      sizeof(bdbuf_test_msg),
                                      RTEMS_DEFAULT_ATTRIBUTES,
                                      id);
}

rtems_status_code
bdbuf_test_create_drv_tx_queue(Objects_Id *id)
{
    return  rtems_message_queue_ident(TEST_TASK_RX_MQUEUE_NAME,
                                      RTEMS_SEARCH_ALL_NODES,
                                      id);
}

rtems_status_code
bdbuf_test_start_thread(unsigned int idx, rtems_task_entry func)
{
    rtems_status_code sc;

    if (g_test_ctx.test_task[idx] != OBJECTS_ID_NONE)
    {
        sc = rtems_task_delete(g_test_ctx.test_task[idx]);
        if (sc != RTEMS_SUCCESSFUL)
        {
            printf("Failed to delete test thread %u in test %s\n",
                   idx + 1, g_test_ctx.test_name);
            return sc;
        }
    }
    sc = bdbuf_test_start_aux_task(
            rtems_build_name('T', 'S', '.', '0' + idx),
            func, (rtems_task_argument)(NULL),
            &g_test_ctx.test_task[idx]);
    return sc;
}

rtems_status_code
bdbuf_test_end()
{
    rtems_status_code sc;
    unsigned int      i;

    for (i = 0; i < ARRAY_NUM(g_test_ctx.test_task); i++)
    {
        if (g_test_ctx.test_task[i] != OBJECTS_ID_NONE)
        {
            sc = rtems_semaphore_obtain(g_test_ctx.test_end_main,
                                        RTEMS_WAIT, RTEMS_NO_TIMEOUT);
            if (sc != RTEMS_SUCCESSFUL)
            {
                printf("Failed to get a thread stopped\n");
            }
            g_test_ctx.test_task[i] = OBJECTS_ID_NONE;
        }
    }
    return RTEMS_SUCCESSFUL;
}
