/*
 * Copyright (c) 1999,2000 The University of Waikato, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This code has been developed by the University of Waikato WAND
 * research group along with the DAG PCI network capture cards. For
 * further information please visit http://dag.cs.waikato.ac.nz.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      Waikato, Hamilton, NZ, and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE UNIVERSITY AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE UNIVERSITY OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $Id: dagmem_main.c 14265 2011-06-06 23:57:53Z alexey.korolev $
 */

/*
 * This is dagmem, the memory allocation module for the DAG network capture board
 * driver. For performance reasons it is good to have a large area of physically
 * contigous memory reserved for DMA data transfer from the DAG into PC memory.
 * Unfortunately, contigous memory is hard to allocate once the operating system
 * has been run (due to the VM system usage pattern). This is why the DAG driver
 * cannot allocate contigous DMA memory by itself once it is loaded.
 *
 * The old algorithm used before dagmem was to "punch a hole" into memory and
 * reserve it for the dag driver. This has some drawbacks: (1) it is inflexible
 * in terms of changes (number of DAGs supported, size of memory allocated per DAG,
 * ...), (2) it cannot be freed without rebooting a different kernel image.
 * Dagmem solves all those problems. It gets loaded upon boot time via an entry into
 * /etc/modules. The size per DAG board can be supplied as a parameter (default: 16MB).
 * The number of DAGs can be parametrized, per default dagmem will figure the number
 * of DAG's in the current system by itself.
 *
 * Dagmem depends on the memory allocation implemented in mm/page_alloc.c. It expects
 * a large contigous memory pool associated with the largest entry in free_area[].
 * Since it cannot know NR_MEM_LISTS (a compile time definition of the kernel), it
 * guesses it by starting with an order of 12 (8 MByte contigous chunks) and counting
 * down until something is returned by __get_free_pages(). The gfporder insmod(1)
 * parameter to dagmem can be used to change that starting point, in case you have
 * compiled your kernel with a larger value than 12.
 *
 * Compile with:
 *
 * cc -Wall -Wimplicit -g -O2 -DMODULE -D__KERNEL__ -c dagmem.c
 *
 * Optional:		-DDEBUG
 *			-DNDAGS=<somenumber>
 *
 * Important: with Linux, kernel virtually contigous memory is also physically
 * contigous, as computing the phy address from a kv address and vice versa is
 * only adding or subtracting a constant (usually 0xC0000000). Have a look at
 * include/asm-i386/page.h to convince yourself.
 */

/* #define DEBUG 1 */
#include <linux/module.h>
#include <linux/version.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/pagemap.h>
#include <asm/io.h>

#include <linux/string.h>
#include "config.h"
#include "dagmem.h"
#include "dagpci.h"
#include "dagcompat.h"
#include "dagdebug.h"

DAG_EXPORT_SYMBOL(dagmem_malloc);
DAG_EXPORT_SYMBOL(dagmem_free);

/*
 * Global parameters. Could be supplied at compile time command line.
 * Maximum number of DAG boards supported by dagmem.
 */
#ifndef	NDAGS
#define	NDAGS	16
#endif /* NDAGS */

#define MAX_PARAM_SIZE 128
#define ENDACE_VENDOR 0xEACE
/*
 * Debuggings and warnings.
 */

/*
 * Module information section, does not work nicely with broken modutils-2.1.121,
 * as modinfo always runs into an endless loop.
 */
MODULE_AUTHOR("DAG Team <support@endace.com>");
MODULE_DESCRIPTION("memory allocation module for Endace Measurement Systems DAG network capture cards");
MODULE_SUPPORTED_DEVICE("dag");
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,4)
MODULE_VERSION(PACKAGE_VERSION);
#endif 

#ifdef MODULE_LICENSE
MODULE_LICENSE("Proprietary Endace Measurement Systems Ltd");
#endif

/*
 * Current parameters for the module (see insmod(1))
 * Ahh it is so ugly and awful to have such bunch of intersecting 
 * parameters but you can't manage customers
 */
static int		ndags;
static unsigned int	size = DAGMEM_SIZE;
static uint32_t		align = 1024 * 1024;
int			verbose = 0;
static int		gfporder = DAGMEM_ORDER;

static char	a_dsize[MAX_PARAM_SIZE], *dsize = a_dsize;
static char	a_dalign[MAX_PARAM_SIZE], *dalign = a_dalign;
static char a_csize[MAX_PARAM_SIZE], *csize = a_csize;
static char a_dag64[MAX_PARAM_SIZE], *dag64 = a_dag64;
static char a_nid[MAX_PARAM_SIZE], *nid = a_nid;
static char a_device_id_map[MAX_PARAM_SIZE], *device_id_map = a_device_id_map;


DAG_MODULE_PARAM(ndags, int, 0);
DAG_MODULE_PARAM(size, int, 0);
DAG_MODULE_PARAM(align, int, 0);
DAG_MODULE_PARAM(verbose, int, 0);
DAG_MODULE_PARAM(gfporder, int, 0);

DAG_MODULE_PARAM_STRING(dsize, a_dsize, sizeof(a_dsize), 0);
DAG_MODULE_PARAM_STRING(dalign, a_dalign, sizeof(a_dalign), 0);
DAG_MODULE_PARAM_STRING(csize, a_csize, sizeof(a_csize), 0);
DAG_MODULE_PARAM_STRING(dag64, a_dag64, sizeof(a_dag64), 0);
DAG_MODULE_PARAM_STRING(nid, a_nid, sizeof(a_nid), 0);
DAG_MODULE_PARAM_STRING(device_id_map, a_device_id_map,sizeof(a_device_id_map),0);

MODULE_PARM_DESC(ndags, "Number of DAG cards present. Default 0 means autodetect");
MODULE_PARM_DESC(size, "Deprecated");
MODULE_PARM_DESC(dsize, "Memory to allocate per DAG card. KB, MB, GB suffixes, default 32MB");
MODULE_PARM_DESC(align, "Deprecated");
MODULE_PARM_DESC(dalign, "Buffer alignment. KB, MB suffixes, default 1MB");
MODULE_PARM_DESC(verbose, "Increase driver verbosity");
MODULE_PARM_DESC(gfporder, "Largest allocation order to try, default 10");
MODULE_PARM_DESC(dag64, "Specify if driver will allocate memory in 64bit address space, default is 0");
MODULE_PARM_DESC(nid, "Specify preferred NUMA node id for card memory, default is 0");

/* This struct describes contigous region in physical memory */
struct dagmem_pool_entry {
	struct list_head list;
	dma_addr_t start;	/* Start phys address */
	dma_addr_t end;		/* End phys address */
	int bad_entry;		/* Flag to notify that the entry is bad */
};

struct dagmem_card {
	dma_addr_t paddr;	/* Physical address - NULL means no allocation */
	dma_addr_t bus_addr;	/* Card buffer memory BUS address */
	unsigned long size;	/* Size of allocation */
	atomic_t inuse;		/* Need to avoid dual allocations */
	int nid;		/* NUMA Node ID (For future use) */
	int dag64;		/* Special option to if set 64bit addreses are allowed */
};

struct dagmem_desc {
	struct dag_kmem_cache *slab;		/* Slab allocator for contigous regions */
	struct list_head pool;			/* Lists of contigous regions descriptors */
	unsigned int ndags;			/* Number of allocations for cards */
	unsigned int order;			/* Allocations prefered order value */
	unsigned long align;			/* Allocations alignment rule*/
	struct dagmem_card card_mem[NDAGS];	/* Descriptor of per card memory allocation */
};

static struct dagmem_desc *this = NULL;

/*
 * Set of functions to perform module parameters parsing
 */ 

/* Service function to parse extended itneger values with KB or MB at
 * the end*/
static void dagmem_parse_int_specs(char * param, unsigned int* retval)
{
	char	*more;

	if(param[0] == '\0') 
		return;

	*retval = simple_strtoul(param, &more, 0);
	switch(*more) {
		case '\0':
			break;
		case 'M':
		case 'm':
			*retval *= 1024 * 1024;
			break;
		case 'K':
		case 'k':
			*retval *= 1024;
			break;
		default:
			DAG_INFO("dagmem: unrecognized character '%c' specification\n", *more);
			break;			
	}
	if((*more != '\0') && (*++more != '\0'))
		DAG_INFO("dagmem: extra character '%c' after param=<N> specification\n", *more);

	if(*retval & (PAGE_SIZE - 1)) {
		*retval = DAGMEM_SIZE;
		DAG_INFO("dagmem: given value is not page aligned! The default value of %u bytes is taken \n", size);
	}

	return;
}

/* Service function to parse device id map table (pair ID : size) */
static int dagmem_params_by_id(int dev_id, struct dagmem_card *card)
{
	int val, i, length, result_id;
	char temp[1];
	char dev_map_copy[MAX_PARAM_SIZE];
	char *token, *str = dev_map_copy;
	char *device_desc, *device_id;
	unsigned int lsize;
	
	strcpy(dev_map_copy,device_id_map);
	/* Card and Memory hole size in one character array. */
	for (;;) {
		/* Extract per ID fields*/
		device_desc = strsep(&str, ",");
		if (!device_desc)
			break;
		/* Extract ID and parse*/
		device_id = strsep(&device_desc, ":");
		length = strlen(device_id);
		result_id = 0;
		for (i = 0; i < length ; i++) {
			temp[0] = device_id[i];
			temp[1] = '\0';
			sscanf(temp, "%x", &val);
			val = (val << (4 * ((length - 1) - i)));
			result_id +=val;
		}
		/* Found */
		if (result_id == dev_id) {
			/* Extract size*/
			token = strsep(&device_desc,":");
			if (token) {
				dagmem_parse_int_specs(token, &lsize);
				card->size = lsize;
			}

			/* Extract 64bit attribute */
			token = strsep(&device_desc,":");
			if (token && !strncmp(token,"64bit",5))
				card->dag64 = 1;
			return 0;
		}
	}
	return -ENOENT;
}

/* Service function to obtain number of dag cards available
 * if device_id_map is suplied it will fill in params also */
static void dagmem_scan_pci(struct dagmem_desc *dmd)
{
	struct pci_dev	*from = NULL;
	int vendor = ENDACE_VENDOR;
	int dag = 0;

	for(dag = 0; dag < NDAGS; dag++) {
		from = dag_pci_find_dev(vendor,PCI_ANY_ID,from);

		if (from) {
			DAG_DEBUG("%s: vendor %x device %x \n", __func__,from->vendor,from->device);

			/* If bar1 exists, may be vdag/dock */
			if(pci_resource_len(from, 1) >= 0x100000) {
				DAG_DEBUG("dagmem: dag%d: appears virtual, skipping\n", dag);
				dmd->card_mem[dag].size = 0;
				continue;
			}

			if (device_id_map[0] != '\0')
				dagmem_params_by_id(from->device,
						    &dmd->card_mem[dag]);
		}
		else
			break;
	}

	if (ndags == 0)
		ndags = dag;
	DAG_DEBUG("%s: count of ndags %d \n", __func__, ndags);

	if (ndags > NDAGS) {
		DAG_INFO("%s: only supporting %d out of %d dags\n", __func__, NDAGS, ndags);
		ndags = NDAGS;
	}

	dmd->ndags = ndags;

	return;
}


/* Main function to parse all input params */
static void dagmem_parse_params(struct dagmem_desc *dmd)
{
	int loop;

	/* Parse dsize parameters ***/
	dagmem_parse_int_specs(dsize, &size);
	for (loop = 0; loop < NDAGS;loop ++)
		dmd->card_mem[loop].size = size;

	/* Parse csize parameters */
	if(csize[0] != '\0') {
		char *token,*str;
		int index = 0;
		unsigned int lsize;

		str = csize;
		while(index < NDAGS) {
			token = strsep(&str,":");
			if(token == NULL)
				break;

			dagmem_parse_int_specs(token, &lsize);
			dmd->card_mem[index++].size = lsize;
		}
	}

	/* Parse and check gfporder parameter  */
	if (gfporder >= MAX_ORDER) {
		gfporder = MAX_ORDER - 1;
		DAG_VERB("dagmem gfporder can't be more than %d. The gfporder value is set to %d \n", 
					MAX_ORDER - 1, gfporder);
	}
	dmd->order = gfporder;

	/* Parse alignment parameter
	 * It can't be less than GFP_ORDER size otherwise we have 
	 * to care about several allocation per mem chunk */
	dagmem_parse_int_specs(dalign, &align);
	align = DAG_ALIGN(align, PAGE_SIZE << gfporder);
	dmd->align = align;

	/* Parse 64 compatibility fileds */
	if(dag64[0] != '\0') {
		char *token,*str;
		int index = 0;

		str = dag64;
		while(index < NDAGS) {
			token = strsep(&str,":");
			if(token == NULL)
				break;

			dmd->card_mem[index++].dag64 = simple_strtoul(token, NULL, 0);
		}
	}

	/* Parse NUMA node fields */
	if(nid[0] != '\0') {
		char *token,*str;
		int index = 0;
		int numa_node;

		str = nid;
		while(index < NDAGS) {
			token = strsep(&str,":");
			if(token == NULL)
				break;

			numa_node = simple_strtoul(token, NULL, 0);
			dmd->card_mem[index++].nid = dag_correct_node(numa_node);
		}
	}

	/* Read number of dags if none is defined before and assign size if
	device id table is given */
	dagmem_scan_pci(dmd);
}

/* Interface function which provides dag driver with allocated
 * contigous memory */
dma_addr_t dagmem_malloc(dma_addr_t *phys_addr, size_t *msize, uint32_t mflags, 
					uint32_t card_index)
{
	struct dagmem_card *card;

	DAG_VERB("dagmem size %u card index %d\n", (uint32_t)*msize, card_index);
	BUG_ON(!this);
	card = &this->card_mem[card_index];

	if(*msize > card->size) {
		DAG_ERROR("%u larger than supported size %u\n", (unsigned)*msize, size);
		*msize = card->size;
	}
	if(*msize == 0)
		*msize = card->size;

	if(atomic_read(&card->inuse) || !card->size)
		return 0;

	atomic_inc(&card->inuse);

#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	MOD_INC_USE_COUNT;	/* prevents unloading */
#endif
	DAG_VERB("dagmem returning address 0x%llx\n", (u64)card->bus_addr);
	*phys_addr = card->paddr;
	return card->bus_addr;
}

/* Interface function which releases usecounter for allocated
 * contigous memory */
void dagmem_free(dma_addr_t reservation)
{
	int		dag;

	BUG_ON(!this);
	for(dag = 0 ; dag < this->ndags ; dag++) {
		if(this->card_mem[dag].bus_addr == reservation)
			break;
	}
	/* Nothing found - it's really bad*/
	BUG_ON(dag == ndags);

	atomic_dec(&this->card_mem[dag].inuse);
	DAG_VERB("free card %d memory \n", dag);
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
	MOD_DEC_USE_COUNT;
#endif
}
/* This function sets memory attributes in order to prevent it's 
 * swapping also it changes page->_count to workaround ugly 
 * inconsistence of linux-mm with high order allocs and further 
 * vm_insert_page */
#define DAGMEM_UNMARK 0
#define DAGMEM_MARK 1
static void dagmem_xxmark(struct dagmem_desc *dmd, int mark)
{
	int	dag;
	dma_addr_t offt;
	struct page *pg;

	/* Clean up reserved blocks*/
	for (dag=0; dag < dmd->ndags; dag++) {

		/* Don't try to mark pci io space */
		if(dmd->card_mem[dag].paddr != dmd->card_mem[dag].bus_addr)
			continue;

		offt = dmd->card_mem[dag].paddr;
		if (!offt)
			continue;
		while (offt < dmd->card_mem[dag].paddr + dmd->card_mem[dag].size) {
			pg = pfn_to_page(offt >> PAGE_SHIFT);
			if (mark == DAGMEM_MARK) {
			/* In 2.4 kernels we have remap_page_range call which requires 
			set of PG_reserved flag, makes no sense in 2.6 or when nopage 
			approach is used */
				set_bit(PG_reserved, &((pg)->flags));
#ifdef HAVE_VM_INSERT_PAGE
			/* we need to split compound page to do further memory mapping. Unfortunately there are no 
			interfaces in kernel to do this. So we use very low level things here (don't use get_page - 
			this is for already split pages only ). */
    				if (offt & ((PAGE_SIZE << dmd->order) - 1))
					atomic_inc(&pg->_count);
#endif
			}
			if (mark == DAGMEM_UNMARK) {
				clear_bit(PG_reserved, &((pg)->flags));
#ifdef HAVE_VM_INSERT_PAGE
				if (offt & ((PAGE_SIZE << dmd->order) - 1))
					atomic_dec(&pg->_count);
#endif
			}
			offt += PAGE_SIZE;
		}
	}
}

/* This fonction tries to attach recently allocated memory to head or 
 * tail of already allocated elements in the pool thus forming contigous 
 * memory */
static int dagmem_add_contig_region(struct dagmem_desc *dmd,
							dma_addr_t start, 
							dma_addr_t end)
{
	struct dagmem_pool_entry *pool_entry;
	struct dagmem_pool_entry *head_entry = NULL, *tail_entry = NULL;
	int bad_entry = 0;

	if (!(end & 0xFFFFFFFF) || !(start & 0xFFFFFFFF))
		bad_entry = 1;
	else {
		list_for_each_entry(pool_entry, &dmd->pool, list) {
			/* Never attach to bad entries */
			if (pool_entry->bad_entry)
				continue;
			/* Found a rec attachable to head */
			if (pool_entry->start == end) {
				tail_entry = pool_entry;
				tail_entry->start = start;
			}
			/* Found a rec attachable to tail */		
			if(pool_entry->end == start) {
				head_entry = pool_entry;
				head_entry->end = end;
			}
		}
	}
	if (head_entry && tail_entry) {
		/* Found both - merge to one */
		head_entry->end = tail_entry->end;
		list_del(&tail_entry->list);
		kmem_cache_free(dmd->slab, tail_entry);
	}
	if (!head_entry && !tail_entry) {
		/* Nobody is found to get attached to */
		pool_entry = kmem_cache_alloc(dmd->slab, GFP_KERNEL);
		if (!pool_entry)
			return -ENOMEM;
		pool_entry->start = start;
		pool_entry->end = end;
		pool_entry->bad_entry = bad_entry;
		list_add(&pool_entry->list, &dmd->pool);
	}
	return 0;
}

/* An implementation of LWF algorithm to assign given sizes to contigous
 * regions. It should have more chances to find alocations for given requests
 * than the conventional first fit algorithm used before, however it
 * should consume bit more CPU time
 */
static int dagmem_find_contig(struct dagmem_desc *dmd,
				unsigned long allocated, int node)
{
	dma_addr_t phy_addr;
	unsigned long len, left = 0;
	int dag;
	struct dagmem_pool_entry *reg_entry;

	BUG_ON(list_empty(&dmd->pool));
	reg_entry = list_entry((&dmd->pool)->prev, struct dagmem_pool_entry, list);
	phy_addr = DAG_ALIGN(reg_entry->start, dmd->align);
	len = reg_entry->end > phy_addr ? reg_entry->end - phy_addr : 0;

	for (dag=0; dag < dmd->ndags; dag++) {
		if (dmd->card_mem[dag].nid == node)
			left += dmd->card_mem[dag].size;
	}
	/* No need to search */
	if (left > allocated)
		return -ENOMEM;
	
	for (;;) {
		struct dagmem_card *card, *best_card = NULL;
		unsigned long maxsize = 0;
		for(dag=0; dag < dmd->ndags; dag++) {
			card = &dmd->card_mem[dag];
			/* The main condition for finding contigous memory */
			if (!card->paddr					/* Memory is not allocated yet */
				&& card->size > maxsize				/* Allocation is as large as possible and is not zero */
				&& card->size <= len				/* We fit into allocation*/
				&& (card->dag64 || phy_addr < 0xFFFFFFFF)	/* Allocation withing 32bit addressing for non 64bit cards */
				&& (card->nid == node))				/* Allocation is for given NUMA node */
			{
					best_card = card;
					maxsize = best_card->size;
			}
		}

		if (best_card) {
			/* Found the best candidate for this region*/
			best_card->paddr = phy_addr;
			best_card->bus_addr = phy_addr; /* bus = phys */
			phy_addr = DAG_ALIGN(phy_addr + best_card->size, dmd->align);
			len = reg_entry->end > phy_addr ? reg_entry->end - phy_addr : 0;
			left -= best_card->size;
			if (!left)
				return 0;
		} else {
			reg_entry = list_entry(reg_entry->list.prev, struct dagmem_pool_entry, list);
			if (!reg_entry || (&reg_entry->list == &dmd->pool)) {
				/* no mem found cleanup what we have found before */
				for (dag=0; dag < dmd->ndags; dag++) {
					if (dmd->card_mem[dag].nid == node) {
						dmd->card_mem[dag].paddr = 0x0;
						dmd->card_mem[dag].bus_addr = 0x0;
					}
				}
				return -ENOMEM;
			}
			phy_addr = DAG_ALIGN(reg_entry->start, dmd->align);
			len = reg_entry->end > phy_addr ? reg_entry->end - phy_addr : 0;
		}
	}

}
/* Service function which clears memory not being assosiated with 
 * the card */
void dagmem_free_used(struct dagmem_desc *dmd)
{
	int dag;
	dma_addr_t end;
	struct dagmem_card *card;
	
	/* Clean up reserved blocks*/
	for (dag=0; dag < dmd->ndags; dag++) {
		card = &dmd->card_mem[dag];

		/* Don't try to free pci io space */
		if(dmd->card_mem[dag].paddr != dmd->card_mem[dag].bus_addr)
			continue;

		end = card->paddr + card->size;
		if (!card->size || !card->paddr)
			continue;
		while (card->paddr < end) {
			__free_pages(pfn_to_page(card->paddr >> PAGE_SHIFT), dmd->order);
			card->paddr += PAGE_SIZE << dmd->order;
		}
	}
}

/* Service function which clears memory assosiated with 
 * the card */
static void dagmem_free_unused(struct dagmem_desc *dmd)
{
	int dag;
	/* Align should not be less than gfporder allocation size ! */
	struct dagmem_pool_entry *pool_entry, *tmp;

	list_for_each_entry_safe(pool_entry, tmp, &dmd->pool, list) {
		while (pool_entry->start != pool_entry->end) {
			for (dag = 0; dag < dmd->ndags; dag++) {
				if (pool_entry->start == dmd->card_mem[dag].paddr)
					break;
			}
			/* it is not reserved for card - free */
			if (dag == dmd->ndags) {
				__free_pages(pfn_to_page(pool_entry->start >> PAGE_SHIFT), dmd->order);
				pool_entry->start += PAGE_SIZE << dmd->order;
			} else {
				DAG_DEBUG("Reserved memory allocation 0x%llx size %luMB for dag %d \n",
							(u64)pool_entry->start, dmd->card_mem[dag].size>>20, dag);
				pool_entry->start += DAG_ALIGN(dmd->card_mem[dag].size, PAGE_SIZE << dmd->order);
			}
		}
		list_del(&pool_entry->list);
		kmem_cache_free(dmd->slab, pool_entry);
	}
}

/* Main function, it allocates memory and group it in contigous 
 * regions */
static int dagmem_get_node_mem(struct dagmem_desc *dmd, int node)
{
	struct page *page;
	unsigned long alocated = 0;
	struct dagmem_pool_entry *pool_entry;
	int ret = 0;

	DAG_DEBUG("order is %d pages %lu bytes each\n", 1 << dmd->order, PAGE_SIZE);
	for(;;) {
		/* Select default zone to HIGHMEM - if no pages are available
		* in this zone buddy allocator will request pages from other */
		page = dag_alloc_pages(node, GFP_HIGHUSER | __GFP_NOWARN, dmd->order);
		if (!page) {
			ret = -ENOMEM;
			break;
		}
		ret = dagmem_add_contig_region( dmd, page_to_phys(page), 
						page_to_phys(page) + (PAGE_SIZE << dmd->order));
		if (ret)
			break;
		alocated += PAGE_SIZE << dmd->order;
		ret = dagmem_find_contig(dmd, alocated, node);
		if (ret == 0)
			break;
	}

	list_for_each_entry(pool_entry, &dmd->pool, list) {
		DAG_DEBUG("mem region 0x%llx - 0x%llx (%llu MB)\n", 
				(u64)pool_entry->start,
				(u64)pool_entry->end,
				(u64)(pool_entry->end - pool_entry->start)>>20);
	}
	dagmem_free_unused(dmd);
	DAG_DEBUG("done ret %d\n", ret);
	return ret;
}

/* This function builds list of requested nodes and calls 
 * dagmem_get_node_mem for each */
static int dagmem_get_mem(struct dagmem_desc *dmd)
{
	int node_list[NDAGS];
	int dag, node, found;
	int num_nodes = 0, ret = 0;
	unsigned long size = 0;

	/* Fill list of nodes first */
	for (dag = 0; dag < dmd->ndags; dag++) {
		/* Check if memory already assigned (vdag/dock) */
		if(dmd->card_mem[dag].paddr)
			continue;

		found = 0;
		for (node = 0; node < num_nodes; node++) {
			if (dmd->card_mem[dag].nid == node_list[node])
				found = 1;
		}
		if (!found)
			node_list[num_nodes++] = dmd->card_mem[dag].nid;
		size += dmd->card_mem[dag].size;
	}
	/* Weird case. No memory is requested */
	if (!size)
		return ret;

	for (node = 0; node < num_nodes; node++) {
		DAG_DEBUG("allocation for node %d\n", node_list[node]);
		if ((ret = dagmem_get_node_mem(dmd, node_list[node])))
			break; 
	}

	return ret;
}

int dagmem_init(void)
{
	struct dagmem_desc *dmd;
	int loop;

	DAG_DEBUG("started\n");
	if (!(dmd = kmalloc(sizeof(struct dagmem_desc), GFP_KERNEL)))
		return -ENOMEM;
	memset(dmd, 0, sizeof(struct dagmem_desc));
	this = dmd;

	dmd->slab = dag_kmem_cache_create("dag_mem",
					sizeof(struct dagmem_pool_entry),
					0, 0, NULL);
	if (!dmd->slab)
		goto err1;
	INIT_LIST_HEAD(&dmd->pool);

	dagmem_parse_params(dmd);
	/* Main structure filling is done - print results */
	DAG_INFO("dagmem requested memory for %u cards\n", dmd->ndags);
	DAG_VERB("dagmem params: align %lu\n", dmd->align);
	DAG_VERB("dagmem params: order %u'\n", dmd->order);
	DAG_VERB("dagmem params: gfporder %u\n", gfporder);
	DAG_VERB("dagmem params: device id %s\n",device_id_map);
	for (loop = 0; loop < dmd->ndags; loop++)
		DAG_INFO("\tdag%d size=%lu node=%u 64bit=%u\n",
							loop,
							dmd->card_mem[loop].size,
							dmd->card_mem[loop].nid,
							dmd->card_mem[loop].dag64);

	/* This is not so nice but I have to provide behavior 
	 * compatibility with older versions*/
	while (dagmem_get_mem(dmd)) {
		dagmem_free_used(dmd);
		if (!(--dmd->ndags))
		    break;
	}

	dagmem_xxmark(dmd, DAGMEM_MARK);
	DAG_INFO("dagmem alocated memory for %u cards\n", dmd->ndags);
	for (loop = 0; loop < dmd->ndags; loop++)
		DAG_INFO("\tdag%d size=%lu at %llx\n",
					loop,
					dmd->card_mem[loop].size,
					(long long)dmd->card_mem[loop].paddr);
	DAG_DEBUG("allocation done\n");
	return 0;

err1:
	kfree(dmd);
	DAG_ERROR("memory allocation failed\n");
	return -ENOMEM;
}

void dagmem_exit(void)
{
	dagmem_xxmark(this, DAGMEM_UNMARK);
	dagmem_free_used(this);
	kmem_cache_destroy(this->slab);
	kfree(this);
	DAG_DEBUG("cleanup done\n");
}

module_init(dagmem_init);
module_exit(dagmem_exit);
