#include <linux/kernel.h>

#include <linux/spinlock.h>
//#include <asm/semaphore.h>  // for 2.6.18
#include <linux/semaphore.h>
#include <linux/completion.h>
#include <linux/sched.h>

#include <linux/time.h>
#include <linux/slab.h>     // kzalloc

#include <linux/pci.h>  // pci DMA

#include "DriverInfo.h"

#include "SPtype.h"
#include "funcresult.h"

#include "DrvDebug.h"
#include "SystemPorting.h"

VOID
SPEvent_Initialize(
    IN      PSPEvent    hSPEvent
    )
{
    init_completion(&hSPEvent->event);
    spin_lock_init(&hSPEvent->lock);
    hSPEvent->trigger = 0;
    hSPEvent->loop = 0;
}

VOID
SPEvent_Clear(
    IN      PSPEvent    hSPEvent
    )
{
    //while(spin_trylock_bh(&hSPEvent->lock) == 0)
    {
        //schedule();
    }
    
    hSPEvent->trigger = 0;
    
    //spin_unlock_bh(&hSPEvent->lock);
}

VOID
SPEvent_ClearAtDPC(
    IN      PSPEvent    hSPEvent
    )
{
    //spin_lock(&hSPEvent->lock);
    
    hSPEvent->trigger = 0;
    
    //spin_unlock(&hSPEvent->lock);
}   

VOID
SPEvent_Set(
    IN      PSPEvent    hSPEvent
    )
{
    while(spin_trylock_bh(&hSPEvent->lock) == 0)
    {
        hSPEvent->loop += 1;
        schedule();
        
        if (hSPEvent->loop > 10)
        {
            //DrvDbgPrint(0, "    hSPEvent:0x%p loop:%u\n", hSPEvent, hSPEvent->loop);
        }
    }
    hSPEvent->loop = 0;
    
    hSPEvent->trigger = 1;
    
    spin_unlock_bh(&hSPEvent->lock);
    
    complete(&hSPEvent->event);
}

VOID
SPEvent_SetAtDPC(
    IN      PSPEvent    hSPEvent
    )
{
    //UINT                        flags;
    
    spin_lock(&hSPEvent->lock);
    //spin_lock_irqsave(&hSPEvent->lock, flags);
    
    
    hSPEvent->trigger = 1;
    
    spin_unlock(&hSPEvent->lock);
    //spin_unlock_irqrestore(&hSPEvent->lock, flags);
    
    
    complete(&hSPEvent->event);
}


ULONG
SPEvent_Wait(
    IN      PSPEvent    hSPEvent,
    IN      ULONG       Timeoutms,
        OUT PULONG      pbTimeout
    )
{
    ULONG                       timeout;
    ULONG                       trigger;
    
    *pbTimeout = FALSE;
    
    while(spin_trylock_bh(&hSPEvent->lock) == 0)
    {
        hSPEvent->loop += 1;
        schedule();
        
        if (hSPEvent->loop > 10)
        {
            //DrvDbgPrint(0, "    hSPEvent:0x%p loop:%u\n", hSPEvent, hSPEvent->loop);
        }
    }
    hSPEvent->loop = 0;
    
    trigger = hSPEvent->trigger;
    
    spin_unlock_bh(&hSPEvent->lock);
    
    if (trigger == 0)
    {
        timeout = wait_for_completion_interruptible_timeout(&hSPEvent->event, msecs_to_jiffies(Timeoutms));
        if (timeout == 0)
        {
            *pbTimeout = TRUE;
        }
    }
    else
    {
        
    }
    
    return TRUE;
}

VOID
SPSemaphore_Initialize(
    IN      PSPSemaphore    hSPSemaphore,
    IN      ULONG           Limit
    )
{
    sema_init(&hSPSemaphore->lock, Limit);
    
}

VOID
SPSemaphore_Acquire(
    IN      PSPSemaphore    hSPSemaphore
    )
{
    /*while(down_trylock(&hSPSemaphore->lock) == 0)
    {
        schedule();
    }*/
    down(&hSPSemaphore->lock);
}
    
VOID
SPSemaphore_Release(
    IN      PSPSemaphore    hSPSemaphore
    )
{
    up(&hSPSemaphore->lock);

}

VOID
SPSpinLock_Initialize(
    IN      PSPSpinLock     hSPSpinLock
    )
{
    spin_lock_init(&hSPSpinLock->lock);
    
    hSPSpinLock->loop = 0;
    
}

VOID
SPSpinLock_Acquire(
    IN      PSPSpinLock     hSPSpinLock
    )
{
    //spin_lock(&hSPSpinLock->lock);
    
    while(spin_trylock_bh(&hSPSpinLock->lock) == 0)
    {
        hSPSpinLock->loop += 1;
        schedule();
        
        if (hSPSpinLock->loop > 10)
        {
            //DrvDbgPrint(0, "    hSPSpinLock:0x%p loop:%u\n", hSPSpinLock, hSPSpinLock->loop);
        }
    }
    
    hSPSpinLock->loop = 0;
    
}

VOID
SPSpinLock_Release(
    IN      PSPSpinLock     hSPSpinLock
    )
{
    //spin_unlock(&hSPSpinLock->lock);
    spin_unlock_bh(&hSPSpinLock->lock);
}


VOID
SPSpinLock_AcquireAtDpcLevel(
    IN      PSPSpinLock     hSPSpinLock
    //IN      PUINT           pflags
    )
{
    // verified
    spin_lock(&hSPSpinLock->lock);
    //spin_lock_irqsave(&hSPSpinLock->lock, hSPSpinLock->flags);
    
}

VOID
SPSpinLock_ReleaseFromDpcLevel(
    IN      PSPSpinLock     hSPSpinLock
    //IN      PUINT           pflags
    )
{
    // verified
    spin_unlock(&hSPSpinLock->lock);
    //spin_unlock_irqrestore(&hSPSpinLock->lock, hSPSpinLock->flags);
    
}

VOID
SPInterruptLock_Initialize(
    IN      PSPInterruptLock    hSPInterruptLock
    )
{
    spin_lock_init(&hSPInterruptLock->lock);
    hSPInterruptLock->loop = 0;
}

VOID
SPInterruptLock_Acquire(
    IN      PSPInterruptLock     hSPInterruptLock
    )
{
    //if (hSPInterruptLock->Interrupt != NULL)
    {
        //spin_lock_irq(&hSPInterruptLock->lock);
        
        while(spin_trylock_irq(&hSPInterruptLock->lock) == 0)
        {
            hSPInterruptLock->loop += 1;
            schedule();
            if (hSPInterruptLock->loop > 10)
            {
                //DrvDbgPrint(0, "    hSPInterruptLock:0x%p loop:%u\n", hSPInterruptLock, hSPInterruptLock->loop);
            }
        }
        hSPInterruptLock->loop = 0;
        
        /*while(spin_trylock_irqsave(&hSPInterruptLock->lock, hSPInterruptLock->flags) == 0)
        {
            schedule();
        }*/
        
        
    }
}

VOID
SPInterruptLock_Release(
    IN      PSPInterruptLock     hSPInterruptLock
    )
{
    //if (hSPInterruptLock->Interrupt != NULL)
    {
        spin_unlock_irq(&hSPInterruptLock->lock);
        //spin_unlock_irqrestore(&hSPInterruptLock->lock, hSPInterruptLock->flags);
    }
}

VOID
SPInterruptLock_AcquireAtDpcLevel(
    IN      PSPInterruptLock    hSPInterruptLock
    )
{
    // verified
    spin_lock_irq(&hSPInterruptLock->lock);
    //spin_lock_irqsave(&hSPInterruptLock->lock, hSPInterruptLock->flags);
}

VOID
SPInterruptLock_ReleaseFromDpcLevel(
    IN      PSPInterruptLock    hSPInterruptLock
    )
{
    spin_unlock_irq(&hSPInterruptLock->lock);
    //spin_unlock_irqrestore(&hSPInterruptLock->lock, hSPInterruptLock->flags);
}

VOID
SPInterruptLock_AcquireAtISR(
    IN      PSPInterruptLock    hSPInterruptLock
    )
{
    // verified
    spin_lock(&hSPInterruptLock->lock);
}

VOID
SPInterruptLock_ReleaseFromISR(
    IN      PSPInterruptLock    hSPInterruptLock
    )
{
    spin_unlock(&hSPInterruptLock->lock);
}


PVOID
SPAllocatePoolWithTag(
    IN      ULONG           bPagedPool,
    IN      ULONG           NumberOfBytes,
    IN      ULONG           Tag
    )
{
    PVOID       hBuffer;
    
    if (bPagedPool == TRUE)
    {
        // Paged pool can only be allocated and accessed at IRQL < DISPATCH_LEVEL. can sleep.
        hBuffer = kzalloc(
            NumberOfBytes,
            GFP_KERNEL
            );
    }
    else
    {
        // non Paged pool can only be allocated IRQL >= DISPATCH_LEVEL. can not sleep.
        hBuffer = kzalloc(
            NumberOfBytes,
            GFP_ATOMIC
            );
    }
    
    return hBuffer;
}

VOID
SPFreePool(
    IN      PVOID           hBuffer
    )
{
    if (hBuffer != 0)
    {
        kfree(hBuffer);
    }
}

VOID
SPZeroMemory(
    IN      VOID            *Destination,
    IN      ULONG           Length
    )
{
    if (Destination == NULL)
    {
        DrvDbgPrint(0, "    SPZeroMemory NULL\n");
    }
    else
    {
        memset(Destination, 0, Length);
    }
}

VOID 
SPCopyMemory(
    IN      VOID            *Destination,
    IN      VOID            *Source,
    IN      ULONG           Length
    )
{
    if (Destination == NULL || Source == NULL)
    {
        DrvDbgPrint(0, "    SPCopyMemory NULL\n");
    }
    else
    {
        memcpy(Destination, Source, Length);
    }
}


PVOID
SPAllocateCommonBuffer32(
    IN      PSPCommonBuffer     pSPCommonBuffer,
    IN      ULONG               NumberOfBytes
    )
{
    dma_addr_t  dma_handle;
    
    pSPCommonBuffer->Length = NumberOfBytes;
    pSPCommonBuffer->BoundaryAddress = 0xFFFFFFFF;
    pSPCommonBuffer->VirtualAddress = NULL;
    pSPCommonBuffer->PhysicalAddress.QuadPart = 0;
    
    pSPCommonBuffer->VirtualAddress = pci_alloc_consistent(
        pSPCommonBuffer->pcidev,
        NumberOfBytes,
        &dma_handle
        );
    if (pSPCommonBuffer->VirtualAddress == NULL)
    {
        //DrvDbgPrint(DMT_Error, "MmAllocateContiguousMemorySpecifyCache NULL \n");
    }
    else
    {
        pSPCommonBuffer->PhysicalAddress.QuadPart = dma_handle;
    }
    
    return pSPCommonBuffer->VirtualAddress;
}


VOID
SPFreeCommonBuffer32(
    IN      PSPCommonBuffer     pSPCommonBuffer
    )
{
    dma_addr_t  dma_handle;
    
    if (pSPCommonBuffer->VirtualAddress != NULL)
    {
        dma_handle = pSPCommonBuffer->PhysicalAddress.QuadPart;
        pci_free_consistent(
            pSPCommonBuffer->pcidev,
            pSPCommonBuffer->Length,
            pSPCommonBuffer->VirtualAddress,
            dma_handle
            );
        pSPCommonBuffer->VirtualAddress = NULL;
    }
}

VOID
SPTime_StartTime(
    IN      PSPTime         hSPTime
    )
{
    do_gettimeofday(&hSPTime->start);
}

VOID
SPTime_EndTime(
    IN      PSPTime         hSPTime
    )
{
    do_gettimeofday(&hSPTime->end);
}

VOID
SPTime_EscapeTime(
    IN      PSPTime         hSPTime,
        OUT PULONGLONG      pms
    )
{
    __kernel_time_t             sec;
    __kernel_time_t             usec;
    
    sec = hSPTime->end.tv_sec - hSPTime->start.tv_sec;
    usec = hSPTime->end.tv_usec - hSPTime->start.tv_usec;
    
    if (sec < 0)
    {
        *pms = 0;
        return; 
    }
    else if (sec == 0)
    {
        if (usec < 0)
        {
           *pms = 0;
            return; 
        }
    }
    
    if (usec > 0)
    {
    }
    else
    {
        sec -= 1;
        usec += 1000000;
    }
    
    *pms = sec*1000 + usec/1000;
}
