/***************************************************************************************\

Module Name:    mtx_os.c

Description:    Linux kernel services called by Matrox drivers.

References:     None.

    Copyright (c) 2004, Matrox Graphics Inc.
    All Rights Reserved.

\***************************************************************************************/


/* -------------------------------------------------------------------------------------- *\
                          I N C L U D E S   A N D   U S I N G S
\* -------------------------------------------------------------------------------------- */

#include "mtx_drv.h"

/* -------------------------------------------------------------------------------------- *\
                          C O N S T A N T S   A N D   T Y P E S
\* -------------------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------------------- *\
                 G L O B A L   V A R I A B L E S   D E C L A R A T I O N S
\* -------------------------------------------------------------------------------------- */

static spinlock_t g_oMtxInterlock = SPIN_LOCK_UNLOCKED;

/* -------------------------------------------------------------------------------------- *\
                   L O C A L   F U N C T I O N   D E C L A R A T I O N S
\* -------------------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------------------- *\
                                         M A C R O S
\* -------------------------------------------------------------------------------------- */

/* -------------------------------------------------------------------------------------- *\
                                           C O D E
\* -------------------------------------------------------------------------------------- */

/***************************************************************************************\

Function:       OsInterlocked*

Description:    Method that handle race-conditions with data shared between 
                user-level driver, kernel driver and interrupt handler.
                
Parameters:     Non applicable.

Return Value:   Non applicable.

Comments:       None.

\***************************************************************************************/
STACK_LINKAGE LONG OsInterlockedIncrement(IN OUT PLONG plValue)
{
    spin_lock(&g_oMtxInterlock);
    (*plValue)++;
    spin_unlock(&g_oMtxInterlock);

    return *plValue;
}

STACK_LINKAGE LONG OsInterlockedDecrement(IN OUT PLONG plValue)
{
    spin_lock(&g_oMtxInterlock);
    (*plValue)--;
    spin_unlock(&g_oMtxInterlock);
    
    return *plValue;
}

STACK_LINKAGE LONG OsInterlockedExchange(IN OUT PLONG plOld, IN LONG lNew)
{   
  #if (__i386__) 
    __asm__ __volatile__
    (
        "lock; xchgl %1,%0\n"
        : "=r" (lNew)
        : "m" (*(volatile unsigned int*)(plOld)),
          "0" (lNew)
        : "memory"
    );
  #else
    {
        LONG lTmp;

        spin_lock(&g_oMtxInterlock);
        lTmp = *plOld;
        *plOld = lNew;
        lNew = lTmp;
        spin_unlock(&g_oMtxInterlock);
    }
  #endif

    return lNew;
}

/***************************************************************************************\

Function:       OsAllocLockedPages

Description:    Allocate a number of locked pages in memory. Those pages may
                resides in normal system memory, or in AGP memory. Also, caller may
                has specified if a contiguous set of pages is requested (allowing 
                direct access from the hardware)
                
Parameters:     hDriver             Handle to OS driver
                ulNbPages           Number of pages to allocate
                bAgp                Allocate pages in AGP memory
                bContiguous         Multiple pages must be contiguous
                ppvVirtAddress      Returned pointer to the first byte of the first 
                                    page allocated (may be NULL)
                
Return Value:   DWORD               Physical address of the first byte of the first page 
                                    0 if failed.

Comments:       None.

\***************************************************************************************/
STACK_LINKAGE DWORD   OsAllocLockedPages(IN  HANDLE hDriver, 
                                         IN  ULONG  ulNbPages, 
                                         IN  BOOL   bAgp, 
                                         IN  BOOL   bContiguous,
                                         OUT VOID** ppvVirtAddress)
{
    mtx_device_t *dev = (mtx_device_t*)hDriver;
    mtx_region_t *region = NULL;
    mtx_memory_type eMemType;
    size_t szAlloc;    

    /* Convert number of pages to bytes */
    szAlloc = ulNbPages * PAGE_SIZE;

    /* Determine type of pages */
    eMemType = (bAgp ? MTX_MEMTYPE_AGP : (bContiguous ? MTX_MEMTYPE_PCI_LOCKED : MTX_MEMTYPE_SYS));

    /* Allocate region */
    region = MtxRegion_Alloc(dev, szAlloc, eMemType, MTX_MEMSCOPE_AUTH, NULL);
    if (!region)
    {
        MTX_ERROR("Parhelia OS: Failed to allocate locked pages (nb: %lu, type: %u)\n",
                  ulNbPages, eMemType);
        return 0;
    }
    
    region->pid = OsGetCurrentProcessId(hDriver);

    if (ppvVirtAddress != NULL)
    {
        /* Kernel wants direct accesses to those pages, map it to kernel space if not yet */
        if (eMemType == MTX_MEMTYPE_AGP)
        {
            region->kaddr = ClientIoRemap(region->base, region->size);
        }
        
        /* Return kernel virtual address */
        *ppvVirtAddress = region->kaddr;
    }

    return region->base;
}

/***************************************************************************************\

Function:       OsFreeLockedPages

Description:    Free a number of locked memory pages previously allocated. OS should
                keep track itself of the allocation type (AGP, contiguous).

Parameters:     hDriver             Handle to OS driver
                dwPhysAddress       Physical address to the first byte of the first page
                pvVirtAddress       Virtual address to the first byte of the first page
                ulNbPages           Number of pages to free
                
Return Value:   BOOL                TRUE is succeed, 
                                    FALSE otherwise.
                                    
Comments:       The dwPhysAddress and ulNbPages fields correspond to the same used when
                allocating the pages with OsAllocLockedPages

\***************************************************************************************/
STACK_LINKAGE BOOL   OsFreeLockedPages(IN HANDLE hDriver, 
                                       IN DWORD dwPhysAddress,
                                       IN VOID* pvVirtAddress,
                                       IN ULONG ulNbPages)
{
    mtx_device_t *dev = (mtx_device_t*)hDriver;
    mtx_region_t *region = NULL;
    
    /* Retrieve region from physical address */
    region = MtxRegion_FindFromBase(dev, dwPhysAddress);
    if (!region)
    {
        MTX_ERROR("Parhelia OS: Failed to free locked pages, wrong address (0x%08x)\n", 
                  dwPhysAddress);
        return FALSE;
    }

    /* Sanity checks */
    if (region->size != (ulNbPages * PAGE_SIZE))
    {
        MTX_ERROR("Parhelia OS: Failed to free locked pages, size doesn't match (sz: %lu, req: %lu)\n", 
                  region->size, ulNbPages * PAGE_SIZE);
        return FALSE;
    }

    /* Free memory */
    MtxRegion_Free(dev, region);

    return TRUE;
}

/***************************************************************************************\

Function:       OsMapPhysicalMemory

Description:    Map to kernel space a block of memory starting from a physical address.
                The block may resides anywhere in the system (IO, AGP, PCI, ...)
                
Parameters:     hDriver             Handle to OS driver
                dwPhysicalAddress   Start physical address of the memory block.
                ulSize              Size in bytes of mapping
                
Return Value:   MEMHANDLE           The kernel virtual memhandle pointing to this 
                                    physical block. NULL if failed.
Comments:       None.

\***************************************************************************************/
STACK_LINKAGE MEMHANDLE   OsMapPhysicalMemory(IN HANDLE hDriver, 
                                              IN DWORD dwPhysicalAddress,
                                              IN ULONG ulSize)
{
    mtx_device_t* dev = (mtx_device_t*)hDriver;
    mtx_region_t* region = NULL;
    MEMHANDLE pvVirtualAddress = (MEMHANDLE)NULL;

    /* Retrieve device region at this physical address */
    region = MtxRegion_FindFromBase(dev, dwPhysicalAddress);
    if (!region)
    {
        /* Only mapping of a registered device region is allowed */
        MTX_ERROR("Parhelia OS: Mapping an invalid memory region (0x%08x, size:%lu)\n",
                  dwPhysicalAddress, ulSize);

        return (MEMHANDLE)NULL;
    }
    
    if (!region->kaddr)
    {
        /* Map region */
        pvVirtualAddress = memMap(dwPhysicalAddress, ulSize);
        if (!pvVirtualAddress)
        {
            MTX_ERROR("Parhelia OS: Fail to map memory region (0x%08x, size:%lu\n",
                      dwPhysicalAddress, ulSize);
            
            return (MEMHANDLE)NULL;
        }

        region->kaddr = (VOID*)pvVirtualAddress;
    }
    else
    {
        /* Region has already been mapped */
        pvVirtualAddress = (MEMHANDLE)region->kaddr;
    }

    return pvVirtualAddress;
}

/***************************************************************************************\

Function:       OsUnmapMemory

Description:    Unmap a block of kernel virtual address.
                
Parameters:     hDriver             Handle to OS driver
                pvVirtualAddress    Kernel virtual mapping
                ulSize              Size in bytes of mapping
                
Return Value:   BOOL                TRUE if succeed
                                    FALSE otherwise
Comments:       None.

\***************************************************************************************/
STACK_LINKAGE BOOL   OsUnmapMemory(IN HANDLE hDriver, 
                                   IN MEMHANDLE pvVirtualAddress,
                                   IN ULONG ulSize)
{
    mtx_device_t* dev = (mtx_device_t*)hDriver;
    mtx_region_t* region;
    
    /* Retrieve device region at this physical address */
    region = MtxRegion_FindFromVirtual(dev, (VOID*)pvVirtualAddress);
    if (!region || (ulSize != region->size))
    {
        MTX_ERROR("Parhelia OS: Unmapping an invalid memory region (0x%08p, size:%lu)\n",
                  (VOID*)pvVirtualAddress, ulSize);

        return FALSE;
    }
    
    /* Unmap region: we'll let the driver cleanup do that for now */
    /* memUnmap(region->kaddr);
    region->kaddr = NULL; */

    return TRUE;
}

/***************************************************************************************\

Function:       OsGetCurrentProcessId

Description:    Return the PID of the current process.
                
Parameters:     hDriver             Handle to OS driver
                
Return Value:   DWORD               PID of current process, 0 if none or failed.

Comments:      

\***************************************************************************************/
STACK_LINKAGE DWORD   OsGetCurrentProcessId(IN  HANDLE hDriver)
{
    return current->pid;
}

/***************************************************************************************\

Function:       OsSchedule

Description:    Call the OS schedule to yield process
                
Parameters:     NONE
                
Return Value:   NONE

Comments:      

\***************************************************************************************/
STACK_LINKAGE void OsSchedule( void )
{
    ClientSchedule();
}
