/*
 * Copyright (c) 2000, 2001, 2002, 2003, 2004, 2005, 2008, 2009
 *	The President and Fellows of Harvard College.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Synchronization test code.
 */

#include <types.h>
#include <lib.h>
#include <clock.h>
#include <thread.h>
#include <synch.h>
#include <test.h>

#define NSEMLOOPS 63
#define NLOCKLOOPS 120
#define NCVLOOPS 5
#define NTHREADS 32

static volatile unsigned long testval1;
static volatile unsigned long testval2;
static volatile unsigned long testval3;
static struct semaphore *testsem;
static struct lock *testlock;
static struct cv *testcv;
static struct semaphore *donesem;

static void inititems(void) {
  if (testsem == NULL) {
    testsem = sem_create("testsem", 2);
    if (testsem == NULL) {
      panic("synchtest: sem_create failed\n");
    }
  }
  if (testlock == NULL) {
    testlock = lock_create("testlock");
    if (testlock == NULL) {
      panic("synchtest: lock_create failed\n");
    }
  }
  if (testcv == NULL) {
    testcv = cv_create("testlock");
    if (testcv == NULL) {
      panic("synchtest: cv_create failed\n");
    }
  }
  if (donesem == NULL) {
    donesem = sem_create("donesem", 0);
    if (donesem == NULL) {
      panic("synchtest: sem_create failed\n");
    }
  }
}

static void semtestthread(void *junk, unsigned long num) {
  int i;
  (void)junk;

  /*
   * Only one of these should print at a time.
   */
  P(testsem);
  kprintf("Thread %2lu: ", num);
  for (i = 0; i < NSEMLOOPS; i++) {
    kprintf("%c", (int)num + 64);
  }
  kprintf("\n");
  V(donesem);
}

int semtest(int nargs, char **args) {
  int i, result;

  (void)nargs;
  (void)args;

  inititems();
  kprintf("Starting semaphore test...\n");
  kprintf("If this hangs, it's broken: ");
  P(testsem);
  P(testsem);
  kprintf("ok\n");

  for (i = 0; i < NTHREADS; i++) {
    result = thread_fork("semtest", NULL, semtestthread, NULL, i);
    if (result) {
      panic("semtest: thread_fork failed: %s\n", strerror(result));
    }
  }

  for (i = 0; i < NTHREADS; i++) {
    V(testsem);
    P(donesem);
  }

  /* so we can run it again */
  V(testsem);
  V(testsem);

  kprintf("Semaphore test done.\n");
  return 0;
}

static void fail(unsigned long num, const char *msg) {
  kprintf("thread %lu: Mismatch on %s\n", num, msg);
  kprintf("Test failed\n");

  lock_release(testlock);

  V(donesem);
  thread_exit();
}

static void locktestthread(void *junk, unsigned long num) {
  int i;
  (void)junk;

  for (i = 0; i < NLOCKLOOPS; i++) {
    lock_acquire(testlock);
    testval1 = num;
    testval2 = num * num;
    testval3 = num % 3;

    if (testval2 != testval1 * testval1) {
      fail(num, "testval2/testval1");
    }

    if (testval2 % 3 != (testval3 * testval3) % 3) {
      fail(num, "testval2/testval3");
    }

    if (testval3 != testval1 % 3) {
      fail(num, "testval3/testval1");
    }

    if (testval1 != num) {
      fail(num, "testval1/num");
    }

    if (testval2 != num * num) {
      fail(num, "testval2/num");
    }

    if (testval3 != num % 3) {
      fail(num, "testval3/num");
    }

    lock_release(testlock);
  }
  V(donesem);
}

int locktest(int nargs, char **args) {
  int i, result;

  (void)nargs;
  (void)args;

  inititems();
  kprintf("Starting lock test...\n");

  for (i = 0; i < NTHREADS; i++) {
    result = thread_fork("synchtest", NULL, locktestthread, NULL, i);
    if (result) {
      panic("locktest: thread_fork failed: %s\n", strerror(result));
    }
  }
  for (i = 0; i < NTHREADS; i++) {
    P(donesem);
  }

  kprintf("Lock test done.\n");

  return 0;
}

static void cvtestthread(void *junk, unsigned long num) {
  int i;
  volatile int j;
  struct timespec ts1, ts2;

  (void)junk;

  for (i = 0; i < NCVLOOPS; i++) {
    lock_acquire(testlock);
    while (testval1 != num) {
      gettime(&ts1);
      cv_wait(testcv, testlock);
      gettime(&ts2);

      /* ts2 -= ts1 */
      timespec_sub(&ts2, &ts1, &ts2);

      /* Require at least 2000 cpu cycles (we're 25mhz) */
      if (ts2.tv_sec == 0 && ts2.tv_nsec < 40 * 2000) {
        kprintf("cv_wait took only %u ns\n", ts2.tv_nsec);
        kprintf("That's too fast... you must be "
                "busy-looping\n");
        V(donesem);
        thread_exit();
      }
    }
    kprintf("Thread %lu\n", num);
    testval1 = (testval1 + NTHREADS - 1) % NTHREADS;

    /*
     * loop a little while to make sure we can measure the
     * time waiting on the cv.
     */
    for (j = 0; j < 3000; j++)
      ;

    cv_broadcast(testcv, testlock);
    lock_release(testlock);
  }
  V(donesem);
}

int cvtest(int nargs, char **args) {

  int i, result;

  (void)nargs;
  (void)args;

  inititems();
  kprintf("Starting CV test...\n");
  kprintf("Threads should print out in reverse order.\n");

  testval1 = NTHREADS - 1;

  for (i = 0; i < NTHREADS; i++) {
    result = thread_fork("synchtest", NULL, cvtestthread, NULL, i);
    if (result) {
      panic("cvtest: thread_fork failed: %s\n", strerror(result));
    }
  }
  for (i = 0; i < NTHREADS; i++) {
    P(donesem);
  }

  kprintf("CV test done\n");

  return 0;
}

////////////////////////////////////////////////////////////

/*
 * Try to find out if going to sleep is really atomic.
 *
 * What we'll do is rotate through NCVS lock/CV pairs, with one thread
 * sleeping and the other waking it up. If we miss a wakeup, the sleep
 * thread won't go around enough times.
 */

#define NCVS 250
#define NLOOPS 40
static struct cv *testcvs[NCVS];
static struct lock *testlocks[NCVS];
static struct semaphore *gatesem;
static struct semaphore *exitsem;

static void sleepthread(void *junk1, unsigned long junk2) {
  unsigned i, j;

  (void)junk1;
  (void)junk2;

  for (j = 0; j < NLOOPS; j++) {
    for (i = 0; i < NCVS; i++) {
      lock_acquire(testlocks[i]);
      V(gatesem);
      cv_wait(testcvs[i], testlocks[i]);
      lock_release(testlocks[i]);
    }
    kprintf("sleepthread: %u\n", j);
  }
  V(exitsem);
}

static void wakethread(void *junk1, unsigned long junk2) {
  unsigned i, j;

  (void)junk1;
  (void)junk2;

  for (j = 0; j < NLOOPS; j++) {
    for (i = 0; i < NCVS; i++) {
      P(gatesem);
      lock_acquire(testlocks[i]);
      cv_signal(testcvs[i], testlocks[i]);
      lock_release(testlocks[i]);
    }
    kprintf("wakethread: %u\n", j);
  }
  V(exitsem);
}

int cvtest2(int nargs, char **args) {
  unsigned i;
  int result;

  (void)nargs;
  (void)args;

  for (i = 0; i < NCVS; i++) {
    testlocks[i] = lock_create("cvtest2 lock");
    testcvs[i] = cv_create("cvtest2 cv");
  }
  gatesem = sem_create("gatesem", 0);
  exitsem = sem_create("exitsem", 0);

  kprintf("cvtest2...\n");

  result = thread_fork("cvtest2", NULL, sleepthread, NULL, 0);
  if (result) {
    panic("cvtest2: thread_fork failed\n");
  }
  result = thread_fork("cvtest2", NULL, wakethread, NULL, 0);
  if (result) {
    panic("cvtest2: thread_fork failed\n");
  }

  P(exitsem);
  P(exitsem);

  sem_destroy(exitsem);
  sem_destroy(gatesem);
  exitsem = gatesem = NULL;
  for (i = 0; i < NCVS; i++) {
    lock_destroy(testlocks[i]);
    cv_destroy(testcvs[i]);
    testlocks[i] = NULL;
    testcvs[i] = NULL;
  }

  kprintf("cvtest2 done\n");
  return 0;
}
