/*
 * Copyright (C) 2005 Aapo Tahkola.
 *
 * All Rights Reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice (including the
 * next paragraph) shall be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE COPYRIGHT OWNER(S) AND/OR ITS SUPPLIERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

/**
 * \file
 *
 * \author Aapo Tahkola <aet@rasterburn.org>
 */

#include <unistd.h>

#include "r300_context.h"
#include "r300_cmdbuf.h"
#include "r300_ioctl.h"
#include "r300_mem.h"
#include "radeon_ioctl.h"

#ifdef USER_BUFFERS

static void resize_u_list(r300ContextPtr rmesa)
{
	void *temp;
	int nsize;

	temp = rmesa->rmm->u_list;
	nsize = rmesa->rmm->u_size * 2;

	rmesa->rmm->u_list = _mesa_malloc(nsize * sizeof(*rmesa->rmm->u_list));
	_mesa_memset(rmesa->rmm->u_list, 0,
		     nsize * sizeof(*rmesa->rmm->u_list));

	if (temp) {
		r300FlushCmdBuf(rmesa, __FUNCTION__);

		_mesa_memcpy(rmesa->rmm->u_list, temp,
			     rmesa->rmm->u_size * sizeof(*rmesa->rmm->u_list));
		_mesa_free(temp);
	}

	rmesa->rmm->u_size = nsize;
}

void r300_mem_init(r300ContextPtr rmesa)
{
	rmesa->rmm = malloc(sizeof(struct r300_memory_manager));
	memset(rmesa->rmm, 0, sizeof(struct r300_memory_manager));

	rmesa->rmm->u_size = 128;
	resize_u_list(rmesa);
}

void r300_mem_destroy(r300ContextPtr rmesa)
{
	_mesa_free(rmesa->rmm->u_list);
	rmesa->rmm->u_list = NULL;

	_mesa_free(rmesa->rmm);
	rmesa->rmm = NULL;
}

void *r300_mem_ptr(r300ContextPtr rmesa, int id)
{
	assert(id <= rmesa->rmm->u_last);
	return rmesa->rmm->u_list[id].ptr;
}

int r300_mem_find(r300ContextPtr rmesa, void *ptr)
{
	int i;

	for (i = 1; i < rmesa->rmm->u_size + 1; i++)
		if (rmesa->rmm->u_list[i].ptr &&
		    ptr >= rmesa->rmm->u_list[i].ptr &&
		    ptr <
		    rmesa->rmm->u_list[i].ptr + rmesa->rmm->u_list[i].size)
			break;

	if (i < rmesa->rmm->u_size + 1)
		return i;

	fprintf(stderr, "%p failed\n", ptr);
	return 0;
}

//#define MM_DEBUG
int r300_mem_alloc(r300ContextPtr rmesa, int alignment, int size)
{
	drm_radeon_mem_alloc_t alloc;
	int offset = 0, ret;
	int i, free = -1;
	int done_age;
	drm_radeon_mem_free_t memfree;
	int tries = 0;
	static int bytes_wasted = 0, allocated = 0;

	if (size < 4096)
		bytes_wasted += 4096 - size;

	allocated += size;

#if 0
	static int t = 0;
	if (t != time(NULL)) {
		t = time(NULL);
		fprintf(stderr, "slots used %d, wasted %d kb, allocated %d\n",
			rmesa->rmm->u_last, bytes_wasted / 1024,
			allocated / 1024);
	}
#endif

	memfree.region = RADEON_MEM_REGION_GART;

      again:

	done_age = radeonGetAge((radeonContextPtr) rmesa);

	if (rmesa->rmm->u_last + 1 >= rmesa->rmm->u_size)
		resize_u_list(rmesa);

	for (i = rmesa->rmm->u_last + 1; i > 0; i--) {
		if (rmesa->rmm->u_list[i].ptr == NULL) {
			free = i;
			continue;
		}

		if (rmesa->rmm->u_list[i].h_pending == 0 &&
		    rmesa->rmm->u_list[i].pending
		    && rmesa->rmm->u_list[i].age <= done_age) {
			memfree.region_offset =
			    (char *)rmesa->rmm->u_list[i].ptr -
			    (char *)rmesa->radeon.radeonScreen->gartTextures.
			    map;

			ret =
			    drmCommandWrite(rmesa->radeon.radeonScreen->
					    driScreen->fd, DRM_RADEON_FREE,
					    &memfree, sizeof(memfree));

			if (ret) {
				fprintf(stderr, "Failed to free at %p\n",
					rmesa->rmm->u_list[i].ptr);
				fprintf(stderr, "ret = %s\n", strerror(-ret));
				exit(1);
			} else {
#ifdef MM_DEBUG
				fprintf(stderr, "really freed %d at age %x\n",
					i,
					radeonGetAge((radeonContextPtr) rmesa));
#endif
				if (i == rmesa->rmm->u_last)
					rmesa->rmm->u_last--;

				if (rmesa->rmm->u_list[i].size < 4096)
					bytes_wasted -=
					    4096 - rmesa->rmm->u_list[i].size;

				allocated -= rmesa->rmm->u_list[i].size;
				rmesa->rmm->u_list[i].pending = 0;
				rmesa->rmm->u_list[i].ptr = NULL;
				free = i;
			}
		}
	}
	rmesa->rmm->u_head = i;

	if (free == -1) {
		WARN_ONCE("Ran out of slots!\n");
		//usleep(100);
		r300FlushCmdBuf(rmesa, __FUNCTION__);
		tries++;
		if (tries > 100) {
			WARN_ONCE("Ran out of slots!\n");
			exit(1);
		}
		goto again;
	}

	alloc.region = RADEON_MEM_REGION_GART;
	alloc.alignment = alignment;
	alloc.size = size;
	alloc.region_offset = &offset;

	ret =
	    drmCommandWriteRead(rmesa->radeon.dri.fd, DRM_RADEON_ALLOC, &alloc,
				sizeof(alloc));
	if (ret) {
#if 0
		WARN_ONCE("Ran out of mem!\n");
		r300FlushCmdBuf(rmesa, __FUNCTION__);
		//usleep(100);
		tries2++;
		tries = 0;
		if (tries2 > 100) {
			WARN_ONCE("Ran out of GART memory!\n");
			exit(1);
		}
		goto again;
#else
		WARN_ONCE
		    ("Ran out of GART memory (for %d)!\nPlease consider adjusting GARTSize option.\n",
		     size);
		return 0;
#endif
	}

	i = free;

	if (i > rmesa->rmm->u_last)
		rmesa->rmm->u_last = i;

	rmesa->rmm->u_list[i].ptr =
	    ((GLubyte *) rmesa->radeon.radeonScreen->gartTextures.map) + offset;
	rmesa->rmm->u_list[i].size = size;
	rmesa->rmm->u_list[i].age = 0;
	//fprintf(stderr, "alloc %p at id %d\n", rmesa->rmm->u_list[i].ptr, i);

#ifdef MM_DEBUG
	fprintf(stderr, "allocated %d at age %x\n", i,
		radeonGetAge((radeonContextPtr) rmesa));
#endif

	return i;
}

void r300_mem_use(r300ContextPtr rmesa, int id)
{
	uint64_t ull;
#ifdef MM_DEBUG
	fprintf(stderr, "%s: %d at age %x\n", __FUNCTION__, id,
		radeonGetAge((radeonContextPtr) rmesa));
#endif
	drm_r300_cmd_header_t *cmd;

	assert(id <= rmesa->rmm->u_last);

	if (id == 0)
		return;

	cmd =
	    (drm_r300_cmd_header_t *) r300AllocCmdBuf(rmesa,
						      2 + sizeof(ull) / 4,
						      __FUNCTION__);
	cmd[0].scratch.cmd_type = R300_CMD_SCRATCH;
	cmd[0].scratch.reg = R300_MEM_SCRATCH;
	cmd[0].scratch.n_bufs = 1;
	cmd[0].scratch.flags = 0;
	cmd++;

	ull = (uint64_t) (intptr_t) & rmesa->rmm->u_list[id].age;
	_mesa_memcpy(cmd, &ull, sizeof(ull));
	cmd += sizeof(ull) / 4;

	cmd[0].u = /*id */ 0;

	LOCK_HARDWARE(&rmesa->radeon);	/* Protect from DRM. */
	rmesa->rmm->u_list[id].h_pending++;
	UNLOCK_HARDWARE(&rmesa->radeon);
}

unsigned long r300_mem_offset(r300ContextPtr rmesa, int id)
{
	unsigned long offset;

	assert(id <= rmesa->rmm->u_last);

	offset = (char *)rmesa->rmm->u_list[id].ptr -
	    (char *)rmesa->radeon.radeonScreen->gartTextures.map;
	offset += rmesa->radeon.radeonScreen->gart_texture_offset;

	return offset;
}

void *r300_mem_map(r300ContextPtr rmesa, int id, int access)
{
#ifdef MM_DEBUG
	fprintf(stderr, "%s: %d at age %x\n", __FUNCTION__, id,
		radeonGetAge((radeonContextPtr) rmesa));
#endif
	void *ptr;
	int tries = 0;

	assert(id <= rmesa->rmm->u_last);

	if (access == R300_MEM_R) {

		if (rmesa->rmm->u_list[id].mapped == 1)
			WARN_ONCE("buffer %d already mapped\n", id);

		rmesa->rmm->u_list[id].mapped = 1;
		ptr = r300_mem_ptr(rmesa, id);

		return ptr;
	}

	if (rmesa->rmm->u_list[id].h_pending)
		r300FlushCmdBuf(rmesa, __FUNCTION__);

	if (rmesa->rmm->u_list[id].h_pending) {
		return NULL;
	}

	while (rmesa->rmm->u_list[id].age >
	       radeonGetAge((radeonContextPtr) rmesa) && tries++ < 1000)
		usleep(10);

	if (tries >= 1000) {
		fprintf(stderr, "Idling failed (%x vs %x)\n",
			rmesa->rmm->u_list[id].age,
			radeonGetAge((radeonContextPtr) rmesa));
		return NULL;
	}

	if (rmesa->rmm->u_list[id].mapped == 1)
		WARN_ONCE("buffer %d already mapped\n", id);

	rmesa->rmm->u_list[id].mapped = 1;
	ptr = r300_mem_ptr(rmesa, id);

	return ptr;
}

void r300_mem_unmap(r300ContextPtr rmesa, int id)
{
#ifdef MM_DEBUG
	fprintf(stderr, "%s: %d at age %x\n", __FUNCTION__, id,
		radeonGetAge((radeonContextPtr) rmesa));
#endif

	assert(id <= rmesa->rmm->u_last);

	if (rmesa->rmm->u_list[id].mapped == 0)
		WARN_ONCE("buffer %d not mapped\n", id);

	rmesa->rmm->u_list[id].mapped = 0;
}

void r300_mem_free(r300ContextPtr rmesa, int id)
{
#ifdef MM_DEBUG
	fprintf(stderr, "%s: %d at age %x\n", __FUNCTION__, id,
		radeonGetAge((radeonContextPtr) rmesa));
#endif

	assert(id <= rmesa->rmm->u_last);

	if (id == 0)
		return;

	if (rmesa->rmm->u_list[id].ptr == NULL) {
		WARN_ONCE("Not allocated!\n");
		return;
	}

	if (rmesa->rmm->u_list[id].pending) {
		WARN_ONCE("%p already pended!\n", rmesa->rmm->u_list[id].ptr);
		return;
	}

	rmesa->rmm->u_list[id].pending = 1;
}
#endif