/***********************************************************************\
*
* aicas GmbH, Karlsruhe
*
* Contents: Jamaica native thread functions
* Systems : QNX
*
\***********************************************************************/


/****************************** Includes *******************************/
/* do not move; needed here because of some macro definitions */
#include <sys/trace.h>

#include "jamaica_config.h"

#include <stdlib.h>
#include <stdio.h>
#include <stddef.h>
#include <sys/neutrino.h>

#include "jamaica_global.h"

#include "jamaica_native_memory.h"
#include "jamaica_native_thread.h"
#include "jamaica_resources_memory.h"

/****************** Conditional compilation switches *******************/

/***************************** Constants *******************************/

/**
 * This constant is the max number of CPUs that are supported by the
 * QNX data structure for representing CPU affinity masks. This data structure
 * is an array of 1024 characters, which is embedded in a structure of type
 * profs_threadctl, which is defined in the header sys/procfs.h as follows:
 *
 * typedef struct _procfs_threadctl {
 *       _Uint32t                                        tid;
 *       _Int32t                                         cmd;
 *       char                                            data[1024];
 * } procfs_threadctl;
 *
 * The layout of the data array is as follows:
 *
 * int size;
 * unsigned runMask[size];
 * unsigned inheritsMask[size];
 *
 * Each bit in the runMask represents one CPU.
 *
 * For the runMask, there is space for at most
 *     (1024 - sizeof(int))/(2 * sizeof(unsigned)) unsigneds.
 * So there is space for representing at most
 *     8 * sizeof(unsigned) * ((1024 - sizeof(int))/(2 * sizeof(unsigned))) CPUs.
 */
// NYI: Unify with constant JAMAICA_NATIVE_MISC_MAX_CPUS, as declared in
// jamaica_generic_misc.h.
// Currently, this other constant is defined to be 64 in base_unix. There is
// at least one reason why currently the VM can only deal with at most 64 CPUs
// (namely, the macro JAMAICA_NATIVE_MISC_GET_AFFINITY() returns a jamaica_uint64).
// Once the VM is able to deal with arbitrary constants JAMAICA_NATIVE_MISC_MAX_CPUS,
// the constant JAMAICA_NATIVE_THREAD_QNX_MAX_CPUS won't be needed anymore. Instead
// the generic constant JAMAICA_NATIVE_MISC_MAX_CPUS will be defined in the QNX
// layer to the same number that is currently the definition of
// JAMAICA_NATIVE_THREAD_QNX_MAX_CPUS.
#define JAMAICA_NATIVE_THREAD_QNX_MAX_CPUS (8 * sizeof(unsigned) * ((1024 - sizeof(int))/(2 * sizeof(unsigned))))

/***************************** Datatypes *******************************/

/***************************** Variables *******************************/

int jamaica_qnx_uintsPerCpuAffinityMask = 0;
int jamaica_qnx_bitsPerCpuAffinityMask = 0;

/****************************** Macros *********************************/

/***************************** Functions *******************************/

JAMAICA_LOCAL void jamaicaNativeQNXThread_initProcessDescriptor(
    char *processDescriptor)
{
  int result = snprintf(processDescriptor,
      JAMAICA_NATIVE_THREAD_QNX_PROC_PSEUDO_FILE_MAX_PATH,
      "/proc/%d/as", getpid());
  assert (result < JAMAICA_NATIVE_THREAD_QNX_PROC_PSEUDO_FILE_MAX_PATH);
  JAMAICA_VARIABLE_UNUSED(result);
}

/**
 * Initialize the system thread id for the current thread. This id is used for
 * assigning cpu affinities to threads.
 *
 * This function must be called once during initialization and never again
 * afterwards. After initialization the system thread id can be read without
 * synchronization.
 *
 * @param thread native thread control block
 */
void jamaicaNativeQNXThread_initSystemThreadId(jamaica_native_thread *thread)
{
  jamaica_native_thread_target_specific *s = &thread->targetSpecific;
  jamaicaNativeQNXThread_initProcessDescriptor(s->processDescriptor);
  s->qnxTid = __tls()->__tid;
  s->size = 0;  // to be initialized later, when a cpuAffinityMask is in scope
}

/** init cpu affinity mask
 *
 * This function must be called after a variable for a cpuAffinityMask has been
 * allocated and before it is first used.
 *
 * @param thread thread control block of current thread, or NULL if the current
 *    thread does not have a thread control block or it is not known
 * @param cpuAffinityMask (out!) cpu affinity mask
 * @param result (out!) JAMAICA_NATIVE_OK if initialization succeeded,
 *    JAMAICA_NATIVE_ERROR otherwise
 */
int jamaicaNativeQNXThread_cpuAffinityMaskInit(
    jamaica_native_thread *thread,
    jamaica_native_cpuAffinityMask *cpuAffinityMask)
{
  static int runMaskSize; // just the runMask
  static int fullMaskSize;  // size field + runMask + inheritsMask
  char *dataArray = cpuAffinityMask->data;
  int *runMaskSizePointer = (int *) dataArray;
  unsigned numCpus = _syspage_ptr->num_cpu;

  // Initialize global and static variables, if not done yet.
  if (jamaica_qnx_uintsPerCpuAffinityMask == 0)
    {
      if (numCpus > JAMAICA_NATIVE_THREAD_QNX_MAX_CPUS)
        {
          fprintf(stderr, "Too many cpus! At most %d CPUs are supported.\n",
              JAMAICA_NATIVE_THREAD_QNX_MAX_CPUS);
          return JAMAICA_NATIVE_ERROR;
        }

      jamaica_qnx_uintsPerCpuAffinityMask = RMSK_SIZE(numCpus);
      runMaskSize = jamaica_qnx_uintsPerCpuAffinityMask * sizeof(unsigned);
      jamaica_qnx_bitsPerCpuAffinityMask = runMaskSize * 8;
      fullMaskSize = sizeof(int) + 2 * runMaskSize;
    }

  // Initialize target-specific size field for thread, if not done yet.
  if (thread != NULL)
    {
      jamaica_native_thread_target_specific *targetSpecific =
          &thread->targetSpecific;
      if (targetSpecific->size == 0)
        {
          targetSpecific->size =
              offsetof(jamaica_native_cpuAffinityMask, data) + fullMaskSize;
        }
    }

  // Initialize data array of cpuAffinityMask.
  memset(dataArray, 0, fullMaskSize);
  *runMaskSizePointer = jamaica_qnx_uintsPerCpuAffinityMask;

  return JAMAICA_NATIVE_OK;
}

/*
 * Precondition:    (thread==NULL)
 *               or (thread is the current thread)
 *               or (the current thread holds the scheduler semaphore)
 */
JAMAICA_LOCAL int jamaicaNativeQNXThread_runmask_get_and_set(
    jamaica_native_thread *thread,
    jamaica_native_cpuAffinityMask *cpuAffinityMask)
{
  int result = JAMAICA_NATIVE_OK;

  if (   (thread != NULL)
      && ((&thread->targetSpecific)->qnxTid != __tls()->__tid) )
    {
      // In this case, we are accessing the runmask of a thread different from
      // the current thread. To this end, we must use the low-level function _devctl.

      // A structure that contains constant parameters for the _devctl function
      // (i.e., parameters that are the same in all calls to _devctl)
      jamaica_native_thread_target_specific *args = &thread->targetSpecific;

      // Whenever the VM gets here, it must hold the scheduler semaphore. This
      // avoids concurrent O_RDWR accesses of the pseudo file. Concurrent
      // O_RDWR accesses result in an error, because only one thread at a time
      // may open a pseudo file in O_RDWR mode.
      int fd = open(args->processDescriptor, O_RDWR);
      if (fd == -1)
        {
          fprintf(stderr, "Can't open pseudo file %s (errno: %d)!\n",
                  args->processDescriptor, errno);
          result = JAMAICA_NATIVE_ERROR;
        }
      else
        {
          int status;
          cpuAffinityMask->tid = args->qnxTid;
          cpuAffinityMask->cmd = _NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT;
          status = _devctl(fd, DCMD_PROC_THREADCTL, cpuAffinityMask,
                           args->size, _DEVCTL_FLAG_NORETVAL);
          if (status == -1)
            {
              fprintf(stderr, "Can't set or get cpu affinity for thread %d: %s!\n",
                      cpuAffinityMask->tid, strerror(errno));
              close(fd);
              result = JAMAICA_NATIVE_ERROR;
            }
        }
      if (result == JAMAICA_NATIVE_OK)
        {
          close(fd);
        }
    }
  else
    {
      // In this case, we are accessing the runmask of the current thread.
      // To this end, we can use the high-level function ThreadCtl.

      if (ThreadCtl(
            _NTO_TCTL_RUNMASK_GET_AND_SET_INHERIT,
            cpuAffinityMask->data)
          == -1)
        {
          fprintf(stderr, "Can't set or get cpu affinity for current thread: %s\n",
              strerror(errno));
          result = JAMAICA_NATIVE_ERROR;
        }
    }
  return result;
}

/** get cpu affinity mask of thread
 *
 * @param thread thread control block. If NULL, get affinity of current thread
 * @param cpuAffinityMask (out!) cpu affinity mask (bit n is cpu n to assign
 *    thread if possible)
 * @return JAMAICA_NATIVE_OK if affinity mask set,
 *    JAMAICA_NATIVE_ERROR otherwise
 *
 * Precondition:    (thread==NULL)
 *               or (thread is the current thread)
 *               or (the current thread holds the scheduler semaphore)
 */
int jamaicaNativeQNXThread_getCPUAffinity(
    jamaica_native_thread *thread,
    jamaica_native_cpuAffinityMask *cpuAffinityMask)
{
  JAMAICA_NATIVE_THREAD_CPU_AFFINITY_MASK_CLEAR(*cpuAffinityMask);
  return jamaicaNativeQNXThread_runmask_get_and_set(thread,cpuAffinityMask);
}

/** set cpu affinity mask of thread
 *
 * @param thread thread control block. If NULL set affinity of current thread.
 *    (type: jamaica_native_thread*)
 * @param cpuAffinityMask cpu affinity mask (bit n is cpu n to assign thread if
 *    possible)  (type: jamaica_native_cpuAffinityMask)
 * @return JAMAICA_NATIVE_OK if affinity mask set,
 *    JAMAICA_NATIVE_ERROR otherwise
 *
 * Precondition:    (thread==NULL)
 *               or (thread is the current thread)
 *               or (the current thread holds the scheduler semaphore)
 */
int jamaicaNativeQNXThread_setCPUAffinity(
    jamaica_native_thread *thread,
    jamaica_native_cpuAffinityMask *cpuAffinityMask)
{
  return jamaicaNativeQNXThread_runmask_get_and_set(thread,cpuAffinityMask);
}


int jamaicaNativeQNXThread_traceEvent(jamaica_int32 code, char* msg)
{
  if (code < _NTO_TRACE_USERFIRST ||  code > _NTO_TRACE_USERLAST)
    {
      fprintf(stderr, "jamaicaNativeQNX_Thread(code,msg), "
                      "illegal argument: code = %ld. "
                      "Must be in range [%d,%d].\n",
                      code, _NTO_TRACE_USERFIRST, _NTO_TRACE_USERLAST);
      return JAMAICA_NATIVE_ERROR;
    }

  if (trace_nlogf(code, 128, msg) == -1)
    {
      fprintf(stderr, "Error from trace_nlogf, %s\n", strerror(errno));
      return JAMAICA_NATIVE_ERROR;
    }

  return JAMAICA_NATIVE_OK;
}

/* end of file */
