/*
 * In the absence of a suitable I/O and memory resource manager,
 * this will have to do.
 */

#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 "mem_region.h"

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

/* An ordered linked list */
static resource_entry_t memlist = { 0, 0, NULL, NULL };

static resource_entry_t *find_gap(resource_entry_t *root,
				  resource_entry_t *entry)
{
    unsigned long flags;
    resource_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)
{
    resource_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;
    resource_entry_t *p, *entry;

    entry = kmalloc(sizeof(resource_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(resource_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;
    resource_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(resource_entry_t));
	    restore_flags(flags);
	    return 0;
	}
    }
    restore_flags(flags);
    return -EINVAL;
}

