/*======================================================================

    Resource management routines

    rsrc_mgr.c 1.1 1995/04/20 20:48:45 (David Hinds)
    
======================================================================*/

#ifdef MODULE
#include <linux/autoconf.h>
#include <linux/module.h>
#include <linux/version.h>
#endif

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/malloc.h>
#include <linux/ioport.h>

#include "cs_types.h"
#include "rsrc_mgr.h"

/*====================================================================*/

typedef struct memory_entry_t {
    u_long base, num;
    char *name;
    struct memory_entry_t *next;
} memory_entry_t;

/* An ordered linked list of allocated memory blocks */
static memory_entry_t memlist = { 0, 0, NULL, NULL };

/* Memory resource database */
resource_entry_t mem_db = { 0, 0, &mem_db };

/* IO port resource database */
resource_entry_t io_db = { 0, 0, &io_db };

/*======================================================================

    These functions manage the database of allocated memory blocks.
    
======================================================================*/

static memory_entry_t *find_gap(memory_entry_t *root,
				memory_entry_t *entry)
{
    unsigned long flags;
    memory_entry_t *p;
    
    if (entry->base > entry->base+entry->num-1)
	return NULL;
    save_flags(flags);
    cli();
    for (p = root; ; p = p->next) {
	if ((p != root) && (p->base+p->num-1 >= entry->base)) {
	    p = NULL;
	    break;
	}
	if ((p->next == NULL) ||
	    (p->next->base > entry->base+entry->num-1))
	    break;
    }
    restore_flags(flags);
    return p;
}

int check_mem_region(u_long base, u_long num)
{
    memory_entry_t entry;
    entry.base = base;
    entry.num = num;
    return (find_gap(&memlist, &entry) == NULL) ? -EBUSY : 0;
}

int register_mem_region(u_long base, u_long num, char *name)
{
    unsigned long flags;
    memory_entry_t *p, *entry;

    entry = kmalloc(sizeof(memory_entry_t), GFP_KERNEL);
    entry->base = base;
    entry->num = num;
    entry->name = name;

    save_flags(flags);
    cli();
    p = find_gap(&memlist, entry);
    if (p == NULL) {
	restore_flags(flags);
	kfree_s(entry, sizeof(memory_entry_t));
	return -EBUSY;
    }
    entry->next = p->next;
    p->next = entry;
    restore_flags(flags);
    return 0;
}

int release_mem_region(u_long base, u_long num)
{
    unsigned long flags;
    memory_entry_t *p, *q;

    save_flags(flags);
    cli();
    for (p = &memlist; ; p = q) {
	q = p->next;
	if (q == NULL) break;
	if ((q->base == base) && (q->num == num)) {
	    p->next = q->next;
	    kfree_s(q, sizeof(memory_entry_t));
	    restore_flags(flags);
	    return 0;
	}
    }
    restore_flags(flags);
    return -EINVAL;
}

/*======================================================================

    These manage the internal databases of available resources
    
======================================================================*/

int add_interval(resource_entry_t *map, u_long base, u_long num)
{
    resource_entry_t *p, *q;

    for (p = map; ; p = p->next) {
	if ((p != map) && (p->base+p->num-1 >= base))
	    return -1;
	if ((p->next == map) || (p->next->base > base+num-1))
	    break;
    }
    q = kmalloc(sizeof(resource_entry_t), GFP_KERNEL);
    q->base = base; q->num = num;
    q->next = p->next; p->next = q;
    return 0;
} /* add_interval */

/*====================================================================*/

int sub_interval(resource_entry_t *map, u_long base, u_long num)
{
    resource_entry_t *p, *q;

    for (p = map; ; p = q) {
	q = p->next;
	if (q == map)
	    break;
	if ((q->base+q->num > base) && (base+num > q->base)) {
	    if (q->base >= base) {
		if (q->base+q->num <= base+num) {
		    /* Delete whole block */
		    p->next = q->next;
		    kfree_s(q, sizeof(resource_entry_t));
		}
		else {
		    /* Cut off bit from the front */
		    q->num = q->base + q->num - base - num;
		    q->base = base + num;
		}
	    }
	    else if (q->base+q->num <= base+num) {
		/* Cut off bit from the end */
		q->num = base - q->base;
	    }
	    else {
		/* Split the block into two pieces */
		p = kmalloc(sizeof(resource_entry_t), GFP_KERNEL);
		p->base = base+num;
		
		p->num = q->base+q->num - p->base;
		q->num = base - q->base;
		p->next = q->next ; q->next = p;
	    }
	}
    }
    return 0;
} /* sub_interval */

/*======================================================================

    These find ranges of I/O ports or memory addresses that are not
    currently allocated by other devices.
    
======================================================================*/

int find_io_region(ioaddr_t *base, ioaddr_t num, char *name)
{
    u_long align;
    resource_entry_t *m;
    
    if (*base != 0) {
	for (m = io_db.next; m != &io_db; m = m->next) {
	    if ((*base >= m->base) && (*base+num <= m->base+m->num)) {
		if (check_region(*base, num))
		    return -1;
		else {
		    request_region(*base, num, name);
		    return 0;
		}
	    }
	}
	return -1;
    }
    
    for (align = 1; align < num; align *= 2) ;
    for (m = io_db.next; m != &io_db; m = m->next) {
	for (*base = m->base;
	     *base+align <= m->base + m->num;
	     *base += align)
	    if (check_region(*base, num) == 0) {
		request_region(*base, num, name);
		return 0;
	    }
    }
    return -1;
} /* find_io_region */

int find_mem_region(u_long *base, u_long num, char *name)
{
    u_long align;
    resource_entry_t *m;
    
    if (*base != 0) {
	for (m = mem_db.next; m != &mem_db; m = m->next) {
	    if ((*base >= m->base) && (*base+num <= m->base+m->num))
		return register_mem_region(*base, num, name);
	}
	return -1;
    }
    
    for (align = 4096; align < num; align *= 2) ;
    for (m = mem_db.next; m != &mem_db; m = m->next) {
	for (*base = m->base;
	     *base+align <= m->base+m->num;
	     *base += align)
	    if (register_mem_region(*base, num, name) == 0)
		return 0;
    }
    return -1;
} /* find_mem_region */
