/*++ @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.
SPDX-License-Identifier: BSD-2-Clause-Patent
**/
#include "Host.h"
#include 
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
};