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

File Name:      mtx_ctx.c

Description:    MTX context manipulation. A context is a representation of each
                reference to a device. 

References:     None.

Author:         Karl Lessard    <klessard@matrox.com>

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

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

#define __NO_VERSION__
#include "mtx_drv.h"


/***************************************************************************************
 * Function:       mtx_ctx_register
 *
 * Description:    Reserve a unique ID for a new context 
 *
 * Parameters:     dev           MTX device to init
 *                 ctx           MTX context to this device
 *  
 * Return Value:   Unique ID, -1 if failed.
 *
 * Comments:       None.
 */
static int mtx_ctx_register(mtx_device_t *dev)
{
    int bit;

    /* Look for a free bit in our context bitmap */
    down(&dev->semaphore);
    bit = find_first_zero_bit(dev->context_bitmap, MAX_CONTEXTS_PER_DEVICE);
    
    if (bit >= MAX_CONTEXTS_PER_DEVICE) {
        up(&dev->semaphore);
        return -1;
    }

    /* Set this bit as used */
    set_bit(bit, dev->context_bitmap);
    up(&dev->semaphore);

    return bit;
}

/***************************************************************************************
 * Function:       mtx_ctx_unregister
 *
 * Description:    Free a old context unique ID
 * 
 * Parameters:     dev           MTX device to init
 *                 unique_id     Unique ID to release
 *  
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       None.
 */
static int mtx_ctx_unregister(mtx_device_t *dev, int unique_id)
{
    if (unique_id >= MAX_CONTEXTS_PER_DEVICE) return -EINVAL;
    
    /* Free bit in our context bitmap */
    down(&dev->semaphore);
    clear_bit(unique_id, dev->context_bitmap);
    up(&dev->semaphore);

    return 0;
}

/***************************************************************************************
 * Function:       mtx_ctx_init
 *
 * Description:    Initialize a new context, and register it to the device. 
 *
 * Parameters:     dev           MTX device
 *                 ctx           MTX context to init
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       None.
 */
int mtx_ctx_init(mtx_device_t *dev, mtx_context_t *ctx)
{
    int ret;

    if (!dev || !ctx) return -EINVAL;;

  #if MEMORY_STATS
    /* remember allocation mark before the context has allocated anything */ 
    ctx->first_allocation_mark = next_allocation_mark;
  #endif

    /* Register context to device */
    if ((ret = mtx_ctx_register(dev)) == -1) {
        ctx->unique_id = -1;    
        return -EBUSY;
    }

    ctx->unique_id = ret;

    /* init context for core driver */
    ctx->core_data = dev->core->open(dev->core_data, (MTXHANDLE)ctx);

    if (ctx->core_data == (MTXHANDLE)NULL) {

        MTX_ERROR("Fail to open new context from core driver\n");
        mtx_ctx_unregister(dev, ctx->unique_id);
        return -EINVAL;
    }

    /* dump memory stats */
    mtx_mem_stats_dump();
    
    return 0;
}

/***************************************************************************************
 * Function:       mtx_ctx_cleanup
 *
 * Description:    Cleanup a context, and unregister it from the device. 
 *
 * Parameters:     dev           MTX device to init
 *                 ctx           Context to delete
 *
 * Return Value:   0 if succeed, errno otherwise.
 *
 * Comments:       None.
 */
int mtx_ctx_cleanup(mtx_device_t *dev, mtx_context_t *ctx)
{
    struct list_head *pos, *tmp;
    mtx_region_t *region = NULL;
    int ret;

    if (!dev || !ctx || (ctx->unique_id == -1)) return -EINVAL;

    /* remove context from core driver */
    dev->core->close(dev->core_data, ctx->core_data);

    /* Free ressources private to this context */
    list_for_each_safe(pos, tmp, &dev->memory_regions) {

        region = list_entry(pos, mtx_region_t, list_node);

        if (region && (region->pid == current->pid)) {
            MtxRegion_Free(dev, region);
        }
    }
    
    /* Unregister context from device */
    ret = mtx_ctx_unregister(dev, ctx->unique_id);
    if (ret < 0) {
        return ret;
    }

  #if MEMORY_STATS
    /* dump memory stats and list remaining blocks allocated since this 
     * context creation */
    mtx_mem_stats_dump_list(ctx->first_allocation_mark);
  #endif

    return 0;
}
