/******************************************************************************/
/*                              SCRAMNet GT                                   */
/******************************************************************************/
/*                                                                            */
/* Copyright (c) 2021 Curtiss-Wright Controls Electronic System, Inc.         */
/*     dtn_support@curtisswright.com 800-252-5601 (U.S. only) 937-252-5601    */
/*                                                                            */
/* This program is free software; you can redistribute it and/or              */
/* modify it under the terms of the GNU General Public License                */
/* as published by the Free Software Foundation; either version 2             */
/* of the License, or (at your option) any later version.                     */
/*                                                                            */
/* See the GNU General Public License for more details.                       */
/*                                                                            */
/******************************************************************************/
/**
@file ksys.c
@brief windows specific k functions used by core
**/

#include "ksys.h"
#include "scgtDebug.h"
WARNING_PUSH
WARNING_DISABLE(4514)  /*'foo' : unreferenced inline function has been removed */
#include <math.h>
WARNING_POP

/** ksys.cpp file revision 8/28/2019 */
char *FILE_REV_KSYS_C = "4";    

/********************* prototypes ***********************/

PMDL ksysCreateMDL(void *virtPtr, ULONG len, KPROCESSOR_MODE procMode);
void ksysDestroyMDL(PMDL mdl);

/***********************************************************/
/*********** Mem copies to and from user space *************/
/***********************************************************/


/**************************************************************************//**
 * @fn void ksysCopyToUser( _Out_writes_bytes_all_(numBytes) void *userPtrDest,
 *                          _In_reads_bytes_(numBytes) void *src, 
 *                           uint32 numBytes)
 * 
 *  @brief  copy to user. 
 *
 * @param [in,out]  userPtrDest If non-null, the user pointer destination. 
 * @param [in,out]  src         If non-null, source for the. 
 * @param  numBytes             Number of bytes. 
 *****************************************************************************/
#ifdef __cplusplus
void ksysCopyToUser( _Inout_ __drv_aliasesMem void *userPtrDest,
    _In_reads_bytes_(numBytes) void *src, uint32 numBytes)
#else
void ksysCopyToUser(void *userPtrDest, void *src, uint32 numBytes)
#endif
{
    PMDL pMdl = NULLPTR;
    //uint8 *dest;
    PVOID dest = NULLPTR;
    if (src == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ksysCopyToUser() src pointer == NULLPTR\n");
        return;
    }
    if (userPtrDest == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ksysCopyToUser() userPtrDest pointer == NULLPTR\n");
        return;
    }
    pMdl = IoAllocateMdl(userPtrDest, numBytes, FALSE, FALSE, NULLPTR);
    if (pMdl == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ksysCopyToUser(): mdl allocation failed\n");
        return;
    }
    WARNING_SUPPRESS(26477);
    _IRQL_limited_to_(DISPATCH_LEVEL);
    __try
    {
        MmProbeAndLockPages(pMdl, UserMode, IoModifyAccess);
    }
    #pragma warning(suppress:6320)
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        ULONG const ecode = GetExceptionCode();
        //KdPrint(("ksysCopyToUser(): probe and lock failed \n"));
        ScgtDbgPrint(SCGT_DBG_ERROR,"ksysCopyToUser(): probe and lock failed. ecode:%u Kirql %u\n",
                     ecode, KeGetCurrentIrql() );
        ScgtDbgPrint(SCGT_DBG_ERROR,"ksysCopyToUser inputs.  userPtrDest:%p  src:%p numBytes:%u ",
                     userPtrDest,src, numBytes);
        IoFreeMdl(pMdl);
        return;
    }
    
    /*DLC added MdlMappingNoExecute flag
    MdlMappingNoExecute indicates that the mapped physical pages are to be configured as
        no - execute memory.Starting with Windows 8, this flag bit can be bitwise - ORed with the
        MM_PAGE_PRIORITY value to specify memory in which instruction execution is disabled.
        As a best practice, drivers written for Windows 8 and later versions of Windows should
        always specify no - execute memory unless executable memory is explicitly required.
    */
#ifdef Win7       
    dest = MmGetSystemAddressForMdlSafe(pMdl, NormalPagePriority);
#else
    dest = MmGetSystemAddressForMdlSafe(pMdl, NormalPagePriority | MdlMappingNoExecute );
#endif
    if( dest != NULLPTR )
    {
        /* do the copy */
        RtlCopyMemory(dest, src, numBytes);
        //memcpy(dest, src, numBytes);
    }
    else
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ksysCopyToUser() dest pointer == NULLPTR\n");
    }

    MmUnlockPages(pMdl);
    IoFreeMdl(pMdl);
    //ScgtDbgPrint(SCGT_DBG_INFO, "ksysCopyToUser() copied %d bytes from src:%p to dest: %p\n",numBytes, src, dest);
}

/**************************************************************************//**
 @fn void ksysCopyFromUser(_Out_writes_bytes_all_(numBytes) void *dst,
                      _In_reads_bytes_(numBytes) void *userPtrSrc, uint32 numBytes)
 *
 * @brief  Ksys copy from user. 
 *
 * @param [in,out]  dst         If non-null, destination for the. 
 * @param [in,out]  userPtrSrc  If non-null, the user pointer source. 
 * @param  numBytes             Number of bytes. 
 *****************************************************************************/
#ifdef __cplusplus
void ksysCopyFromUser(_Out_writes_bytes_all_(numBytes) void *dst, 
                      _In_reads_bytes_(numBytes) void *userPtrSrc, uint32 numBytes)
#else
void ksysCopyFromUser(void *dst, void *userPtrSrc, uint32 numBytes)
#endif
{
    MDL *mdl = NULLPTR;
    PVOID src = NULLPTR;
    mdl = IoAllocateMdl(userPtrSrc, numBytes, FALSE, FALSE, NULLPTR);
    if (mdl == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_ERROR, "ksysCopyToUser(): mdl allocation failed\n");
        //KdPrint(("ksysCopyToUser(): mdl allocation failed\n"));
        return;
    }
    
    __try
    {
        MmProbeAndLockPages(mdl, UserMode, IoReadAccess);
    }
    #pragma warning(suppress:6320)
    __except (EXCEPTION_EXECUTE_HANDLER)
    {
        KdPrint(("ksysCopyToUser(): probe and lock failed \n"));
        IoFreeMdl(mdl);
        return;
    }
    
    /*
    DLC added MdlMappingNoExecute flag
    MdlMappingNoExecute indicates that the mapped physical pages are to be configured as
        no - execute memory.Starting with Windows 8, this flag bit can be bitwise - ORed with the
        MM_PAGE_PRIORITY value to specify memory in which instruction execution is disabled.
        As a best practice, drivers written for Windows 8 and later versions of Windows should
        always specify no - execute memory unless executable memory is explicitly required.
    */
#ifdef Win7    
    if ((src = MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority)) != NULLPTR)
#else
    if ((src =  MmGetSystemAddressForMdlSafe(mdl, NormalPagePriority | MdlMappingNoExecute)) != NULLPTR)
#endif    
    {
        /* do the copy */
        memcpy(dst, src, numBytes);
    }

    MmUnlockPages(mdl);
    IoFreeMdl(mdl);
}


/***********************************************************/
/************ Virtual to bus addr translation ***************/
/***********************************************************/

/**************************************************************************//**
 * @fn  uint32 ksysMapVirtToBus(void *dmaHandle, void *ptr, uint32 numBytes)
 *
 * @brief  Ksys map virt to bus.  mapData must point to a DMA_ADAPTER
 *
 * @param [in,out]  dmaHandle   If non-null, handle of the DMA . 
 * @param [in,out]  ptr         If non-null, the pointer to allocated memory. 
 * @param  numBytes             Number of bytes. 
 *
 * @return . 
 *****************************************************************************/
uint32 ksysMapVirtToBus(void *dmaHandle, void *ptr, uint32 numBytes)
{
    MDL *trMdl= NULLPTR;
    DMA_ADAPTER *dmaAdapter = NULLPTR;
    PHYSICAL_ADDRESS physAddr;
    physAddr.QuadPart = 0;
    ULONG buffLength = 0;
    PVOID MapRegisterBase=NULLPTR;
    PVOID CurrentVa=NULLPTR;
    //UNREFERENCED_PARAMETER(numBytes);

    if (dmaHandle == NULLPTR)
    {
        KdPrint(("ksysMapVirtToBus(): mapData == NULL!!\n"));
        return 0;
    }

    // DLC?? buffLength = 1;
    buffLength = numBytes;
 
    ScgtDbgPrint(SCGT_DBG_DMA, "ksysMapVirtToBus() call ksysCreateMDL()\n");
    trMdl = ksysCreateMDL(ptr, buffLength, KernelMode);
    if (trMdl == NULLPTR)
    {
        ScgtDbgPrint(SCGT_DBG_DMA, "ksysMapVirtToBus()  ksysCreateMDL() returned NULL\n");
        return 0;
    }

    dmaAdapter = (DMA_ADAPTER *) dmaHandle;
    if (dmaAdapter == NULLPTR) return 0;
    // warning C26489 : Don't dereference a pointer that may be invalid:
    // '(PCHAR)(trMdl->StartVa) + (__int64)(trMdl->ByteOffset)'. 'trMdl._MDL::StartVa' may have been invalidated at line 168 (lifetime.1).
    #pragma warning (suppress : 26489 )
    CurrentVa = MmGetMdlVirtualAddress(trMdl);

    ScgtDbgPrint(SCGT_DBG_DMA, "ksysMapVirtToBus()  CurrentVa %p\n",CurrentVa);
    ScgtDbgPrint(SCGT_DBG_DMA, "ksysMapVirtToBus() call KeFlushIoBuffers()\n");
    // If ReadOperation is TRUE, the driver is reading information from the 
    // device to system memory, so valid data still might be in the processor
    // instruction and data caches.KeFlushIoBuffers flushes data from all 
    // processors' caches to system memory, including the processor on which
    // the caller is running.
    // If ReadOperation is FALSE, the driver is writing data from system memory
    // to a device, so valid data might be in the processor's data cache but 
    // not yet transferred to the device. 

    KeFlushIoBuffers(trMdl,
                    FALSE, // ReadOperation
                    TRUE); // DmaOperation  - Specifies TRUE for a DMA transfer, FALSE for PIO.
    
    ScgtDbgPrint(SCGT_DBG_DMA, "ksysMapVirtToBus() call MapTransfer()\n");
   #pragma warning ( suppress:6387)
    physAddr = dmaAdapter->DmaOperations->MapTransfer(dmaAdapter,
                                                      trMdl,
                                                      MapRegisterBase,
                                                      CurrentVa,
                                                      &buffLength,
                                                      FALSE);
    ScgtDbgPrint(SCGT_DBG_DMA, "ksysMapVirtToBus() call FlushAdapterBuffers()\n");
    #pragma warning(suppress:6387)
    dmaAdapter->DmaOperations->FlushAdapterBuffers(dmaAdapter,
                                                      trMdl,
                                                      MapRegisterBase,
                                                      CurrentVa,
                                                      buffLength,
                                                      FALSE);
    WARNING_SUPPRESS(4711); // function 'ksysDestroyMDL' selected for automatic inline expansion
    ksysDestroyMDL(trMdl);

    return physAddr.LowPart;
}

/**************************************************************************//**
 * @fn  PMDL ksysCreateMDL(void *virtPtr, ULONG len, KPROCESSOR_MODE procMode)
 * 
 * @brief  Ksys create mdl. 
 *
 * @param [in,out]  virtPtr  buffer to lock
 * @param  len               The buffer length.
 * @param  procMode          The processor mode. KernelMode or UserMode
 *
 * @return mdl pointer
 *****************************************************************************/
PMDL ksysCreateMDL(void *virtPtr, ULONG len, KPROCESSOR_MODE procMode)
{
    PMDL mdl = NULLPTR;
    
    mdl = IoAllocateMdl(virtPtr, len, FALSE, FALSE, NULLPTR);
    if (mdl != NULLPTR)
    {
        __try
        {
            MmProbeAndLockPages(mdl, procMode, IoModifyAccess);
        }
        #pragma warning(suppress:6320)
        __except (EXCEPTION_EXECUTE_HANDLER)
        {
            ScgtDbgPrint(SCGT_DBG_ERROR,"MmProbeAndLockPages call in ksysCreateMDL() failed\n ");
            return NULLPTR;
        }
    }
    return mdl;
}

/**************************************************************************//**
 * @fn  void ksysDestroyMDL(PMDL mdl)
 *
 * @brief  destroy mdl. 
 *
 * @param  mdl   The mdl. 
 *****************************************************************************/
inline void ksysDestroyMDL(PMDL mdl)
{
    MmUnlockPages(mdl);
    ExFreePool(mdl);
}

/**************************************************************************//**
 * @fn  void ksysUnmapVirtToBus(void *dmaHandle, void *ptr)
 *
 * @brief  Ksys unmap virtual to bus. 
 *
 * @param [in,out]  dmaHandle   If non-null, handle of the dma. 
 * @param [in,out]  ptr         If non-null, the pointer. 
 *****************************************************************************/
void ksysUnmapVirtToBus(void *dmaHandle, void *ptr)
{
    UNREFERENCED_PARAMETER(ptr);
    UNREFERENCED_PARAMETER(dmaHandle);
}

/*************************************************************/
/****************** register write/read **********************/
/*************************************************************/

/**************************************************************************//**
 * @fn  void ksysWriteReg(void *pRegs, uint32 offset, uint32 val)
 *
 * @brief  Write to register. 
 * @sa scgtWriteCReg
 *
 * @param [in,out]  pRegs If non-null, the regs. 
 * @param  offset         The offset. 
 * @param  val            The value. 
 *****************************************************************************/
void ksysWriteReg(void *pRegs, uint32 offset, uint32 val)
{
    /* Bus swapping done by our cards.. otherwise you need to do this: */
    /* WRITE_REGISTER_ULONG((ULONG *)(((char *)pRegs) + offset), BUS_SWAP(val)); */
    WRITE_REGISTER_ULONG((ULONG *)(((char *)pRegs) + offset), val);
}

/**************************************************************************//**
 * @fn  uint32 ksysReadReg(void *pRegs, uint32 offset)
 *
 * @brief  Read from register 
 * @sa scgtReadCReg
 *
 * @param [in,out]  pRegs If non-null, the regs. 
 * @param  offset         The offset. 
 *
 * @return . 
 ***************************************************************************/
uint32 ksysReadReg(void *pRegs, uint32 offset)
{
    /* Bus swapping done by our cards.. otherwise you need to do this: */
    /* return BUS_SWAP(READ_REGISTER_ULONG((ULONG *)(((char *)pRegs) + offset))); */
    return READ_REGISTER_ULONG((ULONG *)(((char *)pRegs) + offset));
}

/********************************************************/
/****************** memory allocation *******************/
/********************************************************/

/**************************************************************************//**
 * @fn  void *ksysMalloc(uint32 nbytes)
 * 
 * @brief  Allocate non paged kernel memory.
 *
 * @param  nbytes   The nbytes. 
 *
 * @return null if it fails, else. 
 *****************************************************************************/
void *ksysMalloc(uint32 nbytes)
{
    //DLC  Starting with windows 8 new flag NonPagedPoolNx  
    // In contrast to the nonpaged pool designated by NonPagedPool, which 
    // allocates executable memory, the NX nonpaged pool allocates memory 
    // in which instruction execution is disabled.
#ifdef Win7
    return ExAllocatePoolWithTag(NonPagedPool, nbytes, 'ksys');

    //return ExAllocatePoolWithTag(NonPagedPoolNx, nbytes, 'ksys');
#else
    // Starting with window 10 build 2004 
    //UnsafeAllocatePool rule added in WDH 20236 and above.
    // new funtion zero initialize the allocat4d buffer.  Kernel-mode driver
    // should not opt-out of zeroizing for allocations that will be copied to an 
    // untrusted location (user-mode, over the network,etc. to avoid disclosing
    // sensative information
    return ExAllocatePoolZero(NonPagedPoolNx, nbytes, 'ksys');
#endif
}

/**************************************************************************//**
 * @fn  void ksysFree(void *p, uint32 len)
*
 * @brief  Free allocated non-paged memory
 *
 * @param [in,out]  p  If non-null, the. 
 * @param  len         The length. 
 *****************************************************************************/
void ksysFree(void *p, uint32 len)
{
    UNREFERENCED_PARAMETER(len);
    // ExFreePool(p);
    ExFreePoolWithTag(p, 'ksys');
}

/************************************************************/
/*********** time outing semaphore implementation ************/
/************************************************************/

/**************************************************************************//**
 * @fn  uint32 ksysSemBTakeWithTimeout(ksysSemB *p_sem, long timeOut)
 *
 * @brief  Perform a timed wait on a semaphore 
 *
 * @param [in,out]  p_sem If non-null, the sem. 
 * @param  timeOut  The time out in milliseconds (1/1000 seconds)
 *
 * @return returns 0 on success, 1 on timeout.
 *****************************************************************************/
uint32 ksysSemBTakeWithTimeout(ksysSemB *p_sem, long timeOut)
{
    LARGE_INTEGER to = { 0,0 };

    /* convert timeout from 100ns to 1/1000 second ticks */
    to.QuadPart = -(labs(timeOut));
    to.QuadPart *= 100000;
    
    p_sem->waiting++;   /* used for stats only */
    
    //if ((KeWaitForSingleObject((PHANDLE) &p_sem->sem, Executive, KernelMode, FALSE, &to)) == STATUS_TIMEOUT)
    if ((KeWaitForSingleObject((PVOID) &p_sem->sem, Executive, KernelMode, FALSE, &to)) == STATUS_TIMEOUT)
        return 1;

    p_sem->waiting--;
    return 0;
}

/**************************************************************************//**
 * @fn  uint32 ksysSemBTake(ksysSemB *p_sem)
 *
 * @brief  Take semaphore B
 *
 * @param [in,out]  p_sem If non-null, the semaphore. 
 *
 * @return . 
 *****************************************************************************/
uint32 ksysSemBTake(ksysSemB *p_sem)
{
   //return KeWaitForSingleObject((PHANDLE) &p_sem->sem, Executive, KernelMode, FALSE, NULLPTR);
   return (uint32) KeWaitForSingleObject((PVOID) &p_sem->sem, Executive, KernelMode, FALSE, NULLPTR);
}
 
/**************************************************************************//**
 * @fn  uint32 ksysSemBGive(ksysSemB *p_sem)
 *
 * @brief  Give semaphore b 
 *
 * @param [in,out]  p_sem If non-null, the semaphore. 
 *
 * @return  returns 0 on success, 1 if already given
 *****************************************************************************/
uint32 ksysSemBGive(ksysSemB *p_sem)
{   
    if ((KeReadStateSemaphore(&p_sem->sem)) == 0) 
    {
        KeReleaseSemaphore(&p_sem->sem, 0, 1, FALSE);
        return 0;
    }
    
    return 1;
}


/*****************************************************************/
/************************** sleep ********************************/
/*****************************************************************/

/**************************************************************************//**
 * @fn  void ksysUSleep(unsigned long usec)
 *
 * @brief  sleep for number of microseconds specified. 
 *
 * @param  usec  The usec. 
 *****************************************************************************/
void ksysUSleep(unsigned long usec)
{
#if 0
    LARGE_INTEGER to;
    KSEMAPHORE sem;

    KeInitializeSemaphore(&sem, 0, 3);
    to.QuadPart = -labs(usec * 10);

    /* Take & timeout on the semaphore */
    KeWaitForSingleObject((PHANDLE)&sem, Executive, KernelMode, FALSE, &to);
#endif
    KeStallExecutionProcessor(usec);
}

