/*
 * Copyright (c) 2015 embedded brains GmbH.  All rights reserved.
 *
 *  embedded brains GmbH
 *  Dornierstr. 4
 *  82178 Puchheim
 *  Germany
 *  <rtems@embedded-brains.de>
 *
 * 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/lock.h>

#define _KERNEL

#include <sys/time.h>
#include <sys/timetc.h>

#include <stdlib.h>
#include <stdio.h>
#include <inttypes.h>
#include <unistd.h>

#include <rtems.h>
#include <rtems/counter.h>
#include <rtems/test.h>

#include <rtems/score/timecounterimpl.h>
#include <rtems/timecounter.h>
#include <rtems/bsd.h>

#include <test_support.h>

#include "tmacros.h"

const char rtems_test_name[] = "SPTIMECOUNTER 2";

#define CPU_COUNT 32

#define DURATION_IN_SECONDS 1

typedef struct {
  rtems_test_parallel_context base;
  struct timecounter tc_null;
  uint32_t binuptime_per_job[CPU_COUNT];
  sbintime_t duration_per_job[CPU_COUNT];
  uint32_t rtemsuptime_per_job[CPU_COUNT];
} timecounter_context;

static timecounter_context test_instance;

static rtems_interval test_duration(void)
{
  return DURATION_IN_SECONDS * rtems_clock_get_ticks_per_second();
}

static uint32_t test_get_timecount_null(struct timecounter *tc)
{
  return 0;
}

static void install_tc_null(timecounter_context *ctx)
{
  struct timecounter *tc_cpu = &ctx->tc_null;

  tc_cpu->tc_get_timecount = test_get_timecount_null;
  tc_cpu->tc_counter_mask = 0xffffffff;
  tc_cpu->tc_frequency = rtems_counter_nanoseconds_to_ticks(1000000000);
  tc_cpu->tc_quality = 2000;
  rtems_timecounter_install(tc_cpu);
}

static rtems_interval test_bintime_init(
  rtems_test_parallel_context *base,
  void *arg,
  size_t active_workers
)
{
  rtems_test_spin_until_next_tick();

  return test_duration();
}

static void test_bintime_body(
  rtems_test_parallel_context *base,
  void *arg,
  size_t active_workers,
  size_t worker_index
)
{
  timecounter_context *ctx = (timecounter_context *) base;
  uint32_t counter = 1;
  struct bintime start;
  struct bintime end;

  rtems_bsd_binuptime(&start);

  do {
    ++counter;
    rtems_bsd_binuptime(&end);
  } while (!rtems_test_parallel_stop_job(&ctx->base));

  ctx->binuptime_per_job[worker_index] = counter;
  ctx->duration_per_job[worker_index] = bttosbt(end) - bttosbt(start);
}

static void test_bintime_fini(
  rtems_test_parallel_context *base,
  void *arg,
  size_t active_workers
)
{
  timecounter_context *ctx = (timecounter_context *) base;
  size_t i;

  printf("  <BinuptimeTest activeWorker=\"%zu\">\n", active_workers);

  for (i = 0; i < active_workers; ++i) {
    sbintime_t error;

    printf(
      "    <Counter worker=\"%zu\">%" PRIu32 "</Counter>\n"
      "    <Duration worker=\"%zu\" unit=\"sbintime\">%" PRId64 "</Duration>\n",
      i + 1,
      ctx->binuptime_per_job[i],
      i + 1,
      ctx->duration_per_job[i]
    );

    error = DURATION_IN_SECONDS * SBT_1S - ctx->duration_per_job[i];
    rtems_test_assert(error * error < SBT_1MS * SBT_1MS);
  }

  printf("  </BinuptimeTest>\n");
}

static rtems_interval test_bintime_null_init(
  rtems_test_parallel_context *base,
  void *arg,
  size_t active_workers
)
{
  timecounter_context *ctx = &test_instance;

  install_tc_null(ctx);

  return test_duration();
}

static void test_bintime_null_body(
  rtems_test_parallel_context *base,
  void *arg,
  size_t active_workers,
  size_t worker_index
)
{
  timecounter_context *ctx = (timecounter_context *) base;
  struct bintime bt;
  uint32_t counter = 0;

  while (!rtems_test_parallel_stop_job(&ctx->base)) {
    ++counter;
    rtems_bsd_binuptime(&bt);
  }

  ctx->binuptime_per_job[worker_index] = counter;
}

static void test_bintime_null_fini(
  rtems_test_parallel_context *base,
  void *arg,
  size_t active_workers
)
{
  timecounter_context *ctx = (timecounter_context *) base;
  size_t i;

  printf("  <BinuptimeNullTest activeWorker=\"%zu\">\n", active_workers);

  for (i = 0; i < active_workers; ++i) {
    printf(
      "    <Counter worker=\"%zu\">%" PRIu32 "</Counter>\n",
      i + 1,
      ctx->binuptime_per_job[i]
    );
  }

  printf("  </BinuptimeNullTest>\n");
}

static const rtems_test_parallel_job timecounter_jobs[] = {
  {
    .init = test_bintime_init,
    .body = test_bintime_body,
    .fini = test_bintime_fini,
    .cascade = true
  },{
    .init = test_bintime_null_init,
    .body = test_bintime_null_body,
    .fini = test_bintime_null_fini,
    .cascade = true
  }
};

static void Init(rtems_task_argument arg)
{
  timecounter_context *ctx = &test_instance;
  struct bintime bt;
  struct timespec ts;
  struct timeval tv;

  TEST_BEGIN();

  printf("<SPTimecounter01>\n");

  rtems_test_parallel(
    &ctx->base,
    NULL,
    &timecounter_jobs[0],
    RTEMS_ARRAY_SIZE(timecounter_jobs)
  );

  /* Check for all functions available in the bsd.h user space */

  rtems_bsd_bintime(&bt);
  rtems_bsd_microtime(&tv);
  rtems_bsd_nanotime(&ts);
  rtems_bsd_binuptime(&bt);
  rtems_bsd_microuptime(&tv);
  rtems_bsd_nanouptime(&ts);
  rtems_bsd_getbintime(&bt);
  rtems_bsd_getmicrotime(&tv);
  rtems_bsd_getnanotime(&ts);
  rtems_bsd_getbinuptime(&bt);
  rtems_bsd_getmicrouptime(&tv);
  rtems_bsd_getnanouptime(&ts);

  printf("</SPTimecounter01>\n");

  TEST_END();
  rtems_test_exit(0);
}

#define CONFIGURE_MICROSECONDS_PER_TICK 1000

#define CONFIGURE_APPLICATION_NEEDS_CLOCK_DRIVER
#define CONFIGURE_APPLICATION_NEEDS_SIMPLE_CONSOLE_DRIVER

#define CONFIGURE_MAXIMUM_TASKS (2 + CPU_COUNT - 1)
#define CONFIGURE_MAXIMUM_TIMERS 2
#define CONFIGURE_MAXIMUM_PERIODS 1

#define CONFIGURE_MAXIMUM_PROCESSORS CPU_COUNT

#define CONFIGURE_RTEMS_INIT_TASKS_TABLE

#define CONFIGURE_INITIAL_EXTENSIONS RTEMS_TEST_INITIAL_EXTENSION

#define CONFIGURE_INIT

#include <rtems/confdefs.h>
