/*++ @file
 POSIX Pthreads to emulate APs and implement threads

Copyright (c) 2011, Apple Inc. All rights reserved.
Copyright (c) 2011 - 2019, Intel Corporation. All rights reserved.<BR>

SPDX-License-Identifier: BSD-2-Clause-Patent


**/

#include "Host.h"
#include <pthread.h>

UINTN
EFIAPI
PthreadMutexLock (
  IN VOID  *Mutex
  )
{
  return (UINTN)pthread_mutex_lock ((pthread_mutex_t *)Mutex);
}

UINTN
EFIAPI
PthreadMutexUnLock (
  IN VOID  *Mutex
  )
{
  return (UINTN)pthread_mutex_unlock ((pthread_mutex_t *)Mutex);
}

UINTN
EFIAPI
PthreadMutexTryLock (
  IN VOID  *Mutex
  )
{
  return (UINTN)pthread_mutex_trylock ((pthread_mutex_t *)Mutex);
}

VOID *
PthreadMutexInit (
  IN VOID
  )
{
  pthread_mutex_t  *Mutex;
  int              err;

  Mutex = malloc (sizeof (pthread_mutex_t));
  err   = pthread_mutex_init (Mutex, NULL);
  if (err == 0) {
    return Mutex;
  }

  return NULL;
}

UINTN
PthreadMutexDestroy (
  IN VOID  *Mutex
  )
{
  if (Mutex != NULL) {
    return pthread_mutex_destroy ((pthread_mutex_t *)Mutex);
  }

  return -1;
}

// Can't store this data on PthreadCreate stack so we need a global
typedef struct {
  pthread_mutex_t              Mutex;
  THREAD_THUNK_THREAD_ENTRY    Start;
} THREAD_MANGLE;

THREAD_MANGLE  mThreadMangle = {
  PTHREAD_MUTEX_INITIALIZER,
  NULL
};

VOID *
SecFakePthreadStart (
  VOID  *Context
  )
{
  THREAD_THUNK_THREAD_ENTRY  Start;
  sigset_t                   SigMask;

  // Save global on the stack before we unlock
  Start = mThreadMangle.Start;
  pthread_mutex_unlock (&mThreadMangle.Mutex);

  // Mask all signals to the APs
  sigfillset (&SigMask);
  pthread_sigmask (SIG_BLOCK, &SigMask, NULL);

  //
  // We have to start the thread in SEC as we need to follow
  // OS X calling conventions. We can then call back into
  // to the callers Start.
  //
  // This is a great example of how all problems in computer
  // science can be solved by adding another level of indirection
  //
  return (VOID *)ReverseGasketUint64 ((UINTN)Start, (UINTN)Context);
}

UINTN
PthreadCreate (
  IN  VOID                       *Thread,
  IN  VOID                       *Attribute,
  IN  THREAD_THUNK_THREAD_ENTRY  Start,
  IN  VOID                       *Context
  )
{
  int      err;
  BOOLEAN  EnabledOnEntry;

  //
  // Threads inherit interrupt state so disable interrupts before we start thread
  //
  if (SecInterruptEanbled ()) {
    SecDisableInterrupt ();
    EnabledOnEntry = TRUE;
  } else {
    EnabledOnEntry = FALSE;
  }

  // Acquire lock for global, SecFakePthreadStart runs in a different thread.
  pthread_mutex_lock (&mThreadMangle.Mutex);
  mThreadMangle.Start = Start;

  err = pthread_create (Thread, Attribute, SecFakePthreadStart, Context);
  if (err != 0) {
    // Thread failed to launch so release the lock;
    pthread_mutex_unlock (&mThreadMangle.Mutex);
  }

  if (EnabledOnEntry) {
    // Restore interrupt state
    SecEnableInterrupt ();
  }

  return err;
}

VOID
PthreadExit (
  IN VOID  *ValuePtr
  )
{
  pthread_exit (ValuePtr);
  return;
}

UINTN
PthreadSelf (
  VOID
  )
{
  // POSIX currently allows pthread_t to be a structure or arithmetic type.
  // Check out sys/types.h to make sure this will work if you are porting.
  // On OS X (Darwin) pthread_t is a pointer to a structure so this code works.
  return (UINTN)pthread_self ();
}

EMU_THREAD_THUNK_PROTOCOL  gPthreadThunk = {
  GasketPthreadMutexLock,
  GasketPthreadMutexUnLock,
  GasketPthreadMutexTryLock,
  GasketPthreadMutexInit,
  GasketPthreadMutexDestroy,
  GasketPthreadCreate,
  GasketPthreadExit,
  GasketPthreadSelf
};

EFI_STATUS
PthreadOpen (
  IN  EMU_IO_THUNK_PROTOCOL  *This
  )
{
  if (This->Instance != 0) {
    // Only single instance is supported
    return EFI_NOT_FOUND;
  }

  if (This->ConfigString[0] == L'0') {
    // If AP count is zero no need for threads
    return EFI_NOT_FOUND;
  }

  This->Interface = &gPthreadThunk;

  return EFI_SUCCESS;
}

EFI_STATUS
PthreadClose (
  IN  EMU_IO_THUNK_PROTOCOL  *This
  )
{
  return EFI_SUCCESS;
}

EMU_IO_THUNK_PROTOCOL  gPthreadThunkIo = {
  &gEmuThreadThunkProtocolGuid,
  NULL,
  NULL,
  0,
  GasketPthreadOpen,
  GasketPthreadClose,
  NULL
};
