/*
 * \brief   kmalloc() implementation
 * \author  Christian Helmuth <ch12@os.inf.tu-dresden.de>
 * \date    2007-01-24
 *
 * In Linux 2.6 this resides in mm/slab.c.
 *
 * This implementation of kmalloc() stays with Linux's and uses kmem_caches for
 * some power of two bytes. For larger allocations ddedkit_large_malloc() is
 * used. This way, we optimize for speed and potentially waste memory
 * resources.
 */

/* Linux */
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/bootmem.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/mm.h>
#include <asm/io.h>

#include "local.h"

#include <dde26.h>

/* dummy */
int forbid_dac;

/* This stuff is needed by some drivers, e.g. for ethtool.
 * XXX: This is a fake, implement it if you really need ethtool stuff.
 */
struct page* mem_map = NULL;
static bootmem_data_t contig_bootmem_data;
struct pglist_data contig_page_data = { .bdata = &contig_bootmem_data };

int remap_pfn_range(struct vm_area_struct *vma, unsigned long addr,
		    unsigned long pfn, unsigned long size, pgprot_t prot)
{
	return 0;
}
EXPORT_SYMBOL(remap_pfn_range);

/*******************
 ** Configuration **
 *******************/

#define DEBUG_MALLOC 0

/********************
 ** Implementation **
 ********************/

/*
 * These are the default caches for kmalloc. Custom caches can have other sizes.
 */
static struct cache_sizes malloc_sizes[] = {
#define CACHE(x) { .cs_size = (x) },
#include <linux/kmalloc_sizes.h>
	CACHE(ULONG_MAX)
#undef CACHE
};


/*
 * kmalloc() cache names
 */
static const char *malloc_names[] = {
#define CACHE(x) "size-" #x,
#include <linux/kmalloc_sizes.h>
	NULL
#undef CACHE
};


/**
 * Find kmalloc() cache for size
 */
static struct kmem_cache *find_cache(size_t size)
{
	struct cache_sizes *sizes;

	for (sizes = malloc_sizes; size > sizes->cs_size; ++sizes) ;

	return sizes->cs_cachep;
}


/**
 * Free previously allocated memory
 * @objp: pointer returned by kmalloc.
 *
 * If @objp is NULL, no operation is performed.
 *
 * Don't free memory not originally allocated by kmalloc()
 * or you will run into trouble.
 */
void kfree(const void *objp)
{
	if (!objp) return;

	/* find cache back-pointer */
	void **p = (void **)objp - 1;

	ddekit_log(DEBUG_MALLOC, "objp=%p cache=%p (%d)",
	           p, *p, *p ? kmem_cache_size(*p) : 0);

	if (*p)
		/* free from cache */
		kmem_cache_free(*p, p);
	else
		/* no cache for this size - use ddekit free */
		ddekit_large_free(p);
}


/**
 * Allocate memory
 * @size: how many bytes of memory are required.
 * @flags: the type of memory to allocate.
 *
 * kmalloc is the normal method of allocating memory
 * in the kernel.
 */
void *__kmalloc(size_t size, gfp_t flags)
{
	/* add space for back-pointer */
	size += sizeof(void *);

	/* find appropriate cache */
	struct kmem_cache *cache = find_cache(size);

	void **p;
	if (cache) {
		/* allocate from cache */
		p = kmem_cache_alloc(cache, flags);
		if (!p) {
			printk("__kmalloc: kmem_cache_alloc %s fails\n",
			       ((char **)cache)[0]);
		}
	}
	else {
		/* no cache for this size - use ddekit malloc */
		p = ddekit_large_malloc(size);
		if (flags & __GFP_ZERO)
			memset (p, 0, size);
		if (!p) {
			printk("__kmalloc: ddekit_large_malloc %d fails\n",
			       size);
		}
	}

	ddekit_log(DEBUG_MALLOC, "size=%d, cache=%p (%d) => %p",
	           size, cache, cache ? kmem_cache_size(cache) : 0, p);

	/* return pointer to actual chunk */
	if (p) {
		*p = cache;
		p++;
	}
	return p;
}


size_t ksize(const void *p)
{
	struct kmem_cache *cache = (struct kmem_cache *)*((void**)p - 1);
	if (cache)
		return kmem_cache_size(cache);
	return -1;
}


void *dma_alloc_coherent(struct device *dev, size_t size, 
                         dma_addr_t *dma_handle, gfp_t flag)
{
	void *ret = (void *)__get_free_pages(flag, get_order(size));

	if (ret != NULL) {
		memset(ret, 0, size);
		*dma_handle = virt_to_bus(ret);
	}
	return ret;
}


void dma_free_coherent(struct device *dev, size_t size,
                       void *vaddr, dma_addr_t dma_handle)
{
	free_pages((unsigned long)vaddr, get_order(size));
}


/********************
 ** Initialization **
 ********************/

/**
 * dde_linux kmalloc initialization
 */
void l4dde26_kmalloc_init(void)
{
	struct cache_sizes  *sizes = malloc_sizes;
	const char         **names = malloc_names;

	/* init malloc sizes array */
	for (; sizes->cs_size != ULONG_MAX; ++sizes, ++names)
		sizes->cs_cachep = kmem_cache_create(*names, sizes->cs_size, 0, 0, 0);
}
