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

Module Name:    mtx_mem.h

Description:    This module defines an abstraction layer of the kernel services used by 
                the kernel module.

Comments:       This is simply used to allow the binary to be compatible with kernels 
                compiled with switches that modifies the ABI.  It allows the binary to
                be kernel-independant.

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

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

#ifndef INC_MEM_H
#define INC_MEM_H

/*-------------------------------------------------------------------------------------*\
                                 H E A D E R   F I L E S
\*-------------------------------------------------------------------------------------*/

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

#ifndef KMALLOC_MAXSIZE
#define KMALLOC_MAXSIZE 4000    /* Don't use full PAGE_SIZE here */
#endif

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

extern  size_t last_alloc_size;

/*-------------------------------------------------------------------------------------*\
                 I N T E R F A C E   F U N C T I O N   P R O T O T Y P E S
\*-------------------------------------------------------------------------------------*/

/* Memory management functions called from binary modules */
#ifdef MEMORY_STATS
INLINE void* mtx_mem_stats_alloc(size_t size, const char* filename, int line);
INLINE void  mtx_mem_stats_free(void* ptr);
#else
INLINE void* mtx_mem_alloc(size_t size);
INLINE void  mtx_mem_free(void* ptr, size_t size);
#endif
INLINE void* mtx_mem_io_remap(unsigned long offset, unsigned long size);
INLINE void  mtx_mem_io_unmap(void* ptr);

/* Memory management functions called from within open source code only */
void    mtx_mem_stats_dump(void);
void    mtx_mem_stats_dump_list(int64_t);
void*   mtx_mem_alloc_pci(size_t size);
void    mtx_mem_free_pci(void* ptr, size_t size);
UINT32  mtx_mem_alloc_agp(size_t size);
void    mtx_mem_free_agp(unsigned long ptr, size_t size);
void*   mtx_vmalloc(int size);
void    mtx_vfree(void* pAddr);
void    mtx_enable_dev_space(void *dev_id);

/*-------------------------------------------------------------------------------------*\
                             I N L I N E S   A N D   M A C R O S
\*-------------------------------------------------------------------------------------*/

#ifdef MEMORY_STATS
  extern  int64_t next_allocation_mark;
  #define MTXALLOC(size)      mtx_mem_stats_alloc(size, __FILE__, __LINE__)
  #define MTXFREE(ptr, size)  mtx_mem_stats_free(ptr)
#else
  #define MTXALLOC(size)      mtx_mem_alloc(size)
  #define MTXFREE(ptr, size)  mtx_mem_free(ptr, size)
#endif

#ifdef MEMORY_STATS

/***************************************************************************************
 * Function:       mtx_mem_stats_alloc
 *
 * Description:    Allocate memory in kernel address space and attach a mem_info
 *                 record to it so we can keep track of memory statistics.
 *
 * Parameters:     size             Size in bytes of memory space to allocate
 *                 filename         Name of file calling this allocation
 *                 line             Line number in file where allocation was called
 *
 * Return Value:   Ptr to allocated memory, NULL if failed
 *
 * Comments:        
 */
void* mtx_mem_stats_alloc(size_t size, const char* filename, int line)
{
    void *ptr = mtx_mem_alloc(size + sizeof(mtx_mem_info_t));
    
    if (!ptr) {
        
        MTX_ERROR("Failed to alloc chunk at %s:%d of size %u\n", filename, line, size);
        mtx_mem_stats_dump();
        
    } else {

        mtx_mem_info_t *mem_info = (mtx_mem_info_t*)ptr;

        if (mem_info_head.size == 0) {

            /* size of mem_info head is used as a flag for first allocation */
            mem_info_head.next = &mem_info_head;
            mem_info_head.prev = &mem_info_head;
            mem_info_head.size = ~0;
        }

        /* Fill mem_info record at the beginning of chunk */
        mem_info->size = size;
        mem_info->filename = filename;
        mem_info->line = line;
        mem_info->allocation_mark = next_allocation_mark++;

        /* Add record to list */
        mem_info->next = mem_info_head.next;
        mem_info->prev = &mem_info_head;
        mem_info_head.next->prev = mem_info;
        mem_info_head.next = mem_info;
    }

    return (void*)((char*)ptr + sizeof(mtx_mem_info_t));
}

/***************************************************************************************
 * Function:       mtx_mem_stats_free
 *
 * Description:    Free a memory chunk and the mem_info record attached to it.
 *
 * Parameters:     ptr          Pointer to memory chunk
 *
 * Return Value:   Ptr to allocated memory, NULL if failed
 *
 * Comments:        
 */
void mtx_mem_stats_free(void* ptr)
{
    if (ptr != NULL) {
        
        mtx_mem_info_t *mem_info = (mtx_mem_info_t*)((char*)ptr - sizeof(mtx_mem_info_t));

        /* Remove record from list */
        mem_info->next->prev = mem_info->prev;
        mem_info->prev->next = mem_info->next;
        
        /* Free chunk of memory */
        mtx_mem_free((void*)mem_info, mem_info->size + sizeof(mtx_mem_info_t));
    }
}

#endif  /* #ifdef MEMORY_STATS */

/***************************************************************************************
 * Function:       mtx_mem_alloc
 *
 * Description:    Allocate memory in kernel address space. The memory chunk is
 *                 insure to be contiguous in virtual address space, but not
 *                 physically. It then cannot be used for DMA by the device.
 *
 * Parameters:     size             Size in bytes of memory space to allocate
 *
 * Return Value:   Ptr to allocated memory, NULL if failed
 *
 * Comments:       XXX This method is not safe under interrupt handlers
 */
void* mtx_mem_alloc(size_t size)
{
    void *ptr;
    u32 align = sizeof(void*);

    /* align size depending on architecture */
    size = (size + align) & ~align;

    if (size <= KMALLOC_MAXSIZE) {
        
        /* Allocate memory using kmalloc */
        ptr = kmalloc(size, GFP_KERNEL);
        
    } else {

        /* Use vmalloc to allocate the memory chunk */
        ptr = vmalloc(size);
    }

    if (!ptr) {
        MTX_ERROR("Failed to allocate memory\n");
        return NULL;
    }
    
    /* Reset memory to zeros */
    memset(ptr, 0, size);

  #if MEMORY_STATS
    ++alloc_count;
    alloc_mem += size;
  #endif

    last_alloc_size = size;

    return ptr;
}

/***************************************************************************************
 * Function:       mtx_mem_free
 *
 * Description:    Free a memory chunk previously allocated by mtx_mem_alloc.
 *
 * Parameters:     ptr      Pointer to memory to free.
 *                 size     Size in bytes of memory to free.
 *
 * Return Value:   None.
 *
 * Comments:
 *
 */
void mtx_mem_free(void* ptr, size_t size)
{
    u32 align = sizeof(void*);

    /* align size depending on architecture */
    size = (size + align) & ~align;

    if (!ptr || !size) {
        MTX_ERROR("Attempt to free NULL pointer\n");
        return;
    }
    
    if (size <= KMALLOC_MAXSIZE) {

        /* This memory chunk has been allocated by kmalloc */
        kfree(ptr);
        
    } else {

        /* This memory chunk has been alloc by vmalloc */
        vfree(ptr);
    }
    
  #if MEMORY_STATS
    --alloc_count;
    alloc_mem -= size;
  #endif
}


/***************************************************************************************
 * Function:       mtx_mem_io_remap
 *
 * Description:    Map any memory to kernel memory space.
 *
 * Parameters:     offset           Start address of the mapping
 *                 size             Size of the mapping
 *
 * Return Value:   Ptr to mapped memory, NULL if failed
 *
 * Comments:
 *
 */
void* mtx_mem_io_remap(unsigned long offset, unsigned long size)
{
    void *ptr = ioremap(offset, size);

    if (!ptr) return NULL;
    
  #if MEMORY_STATS
    ++ioremap_count;
  #endif

    return ptr;
}

/***************************************************************************************
 * Function:       mtx_mem_io_unmap
 *
 * Description:    Free kernel memory map.
 *
 * Parameters:     ptr              Pointer to mapped memory
 *
 * Return Value:   None
 *
 * Comments:
 *
 */
void mtx_mem_io_unmap(void* ptr)
{
    if (!ptr) return;

    iounmap(ptr);
    
  #if MEMORY_STATS
    --ioremap_count;
  #endif
}

#endif  /* #ifndef INC_MEM_H */
