/*
 *	linux/mm/mmap.c
 *
 * Written by obz.
 */
#include <linux/stat.h>
#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/errno.h>
#include <linux/mman.h>
#include <linux/string.h>

#include <asm/segment.h>
#include <asm/system.h>

/*
 * description of effects of mapping type and prot in current implementation.
 * this is due to the current handling of page faults in memory.c. the expected
 * behavior is in parens:
 *
 * map_type	prot
 *		PROT_NONE	PROT_READ	PROT_WRITE	PROT_EXEC
 * MAP_SHARED	r: (no) yes	r: (yes) yes	r: (no) yes	r: (no) no
 *		w: (no) yes	w: (no) copy	w: (yes) yes	w: (no) no
 *		x: (no) no	x: (no) no	x: (no) no	x: (yes) no
 *		
 * MAP_PRIVATE	r: (no) yes	r: (yes) yes	r: (no) yes	r: (no) no
 *		w: (no) copy	w: (no) copy	w: (copy) copy	w: (no) no
 *		x: (no) no	x: (no) no	x: (no) no	x: (yes) no
 *
 */

#define CODE_SPACE(addr) ((((addr)+4095)&~4095) < \
			  current->start_code + current->end_code)

int do_mmap(struct file * file, unsigned long addr, unsigned long len,
	unsigned long prot, unsigned long flags, unsigned long off)
{
	int mask, error;

	if (addr > TASK_SIZE || len > TASK_SIZE || addr > TASK_SIZE-len)
		return -EINVAL;

	/*
	 * do simple checking here so the lower-level routines won't have
	 * to. we assume access permissions have been handled by the open
	 * of the memory object, so we don't do any here.
	 */

	switch (flags & MAP_TYPE) {
	case MAP_SHARED:
		if ((prot & PROT_WRITE) && !(file->f_mode & 2))
			return -EINVAL;
		/* fall through */
	case MAP_PRIVATE:
		if (!(file->f_mode & 1))
			return -EINVAL;
		break;

	default:
		return -EINVAL;
	}

	/*
	 * obtain the address to map to. we verify (or select) it and ensure
	 * that it represents a valid section of the address space.
	 */

	if (flags & MAP_FIXED) {
		if ((addr & 0xfff) || addr == 0)
			return -EINVAL;
		if (len > TASK_SIZE || addr > TASK_SIZE - len)
			return -ENOMEM;
	} else {
		struct vm_area_struct * vmm;

		/* Maybe this works.. Ugly it is. */
		addr = 0x40000000;
		while (addr+len < 0x60000000) {
			for (vmm = current->mmap ; vmm ; vmm = vmm->vm_next) {
				if (addr >= vmm->vm_end)
					continue;
				if (addr + len <= vmm->vm_start)
					continue;
				addr = (vmm->vm_end + 0xfff) & 0xfffff000;
				break;
			}
			if (!vmm)
				break;
		}
		if (addr+len >= 0x60000000)
			return -ENOMEM;
	}

	/*
	 * determine the object being mapped and call the appropriate
	 * specific mapper. the address has already been validated, but
	 * not unmapped
	 */
	if (!file->f_op || !file->f_op->mmap)
		return -ENODEV;
	mask = 0;
	if (prot & (PROT_READ | PROT_EXEC))
		mask |= PAGE_READONLY;
	if (prot & PROT_WRITE)
		if ((flags & MAP_TYPE) == MAP_PRIVATE)
			mask |= PAGE_COW;
		else
			mask |= PAGE_RW;
	if (!mask)
		return -EINVAL;

	error = file->f_op->mmap(file->f_inode, file, addr, len, mask, off);
	if (!error)
		return addr;

	if (!current->errno)
		current->errno = -error;
	return -1;
}

extern "C" int sys_mmap(unsigned long *buffer)
{
	unsigned long fd;
	struct file * file;

	fd = get_fs_long(buffer+4);
	if (fd >= NR_OPEN || !(file = current->filp[fd]))
		return -EBADF;
	return do_mmap(file, get_fs_long(buffer), get_fs_long(buffer+1),
		get_fs_long(buffer+2), get_fs_long(buffer+3), get_fs_long(buffer+5));
}

extern "C" int sys_munmap(unsigned long addr, size_t len)
{
	struct vm_area_struct *mpnt, **p, *free;

	if ((addr & 0xfff) || addr > 0x7fffffff || addr == 0 || addr + len > TASK_SIZE)
		return -EINVAL;

	/* This needs a bit of work - we need to figure out how to
	   deal with areas that overlap with something that we are using */

	p = &current->mmap;
	free = NULL;
	/*
	 * Check if this memory area is ok - put it on the temporary
	 * list if so..
	 */
	while ((mpnt = *p) != NULL) {
		if (addr > mpnt->vm_start && addr < mpnt->vm_end)
			goto bad_munmap;
		if (addr+len > mpnt->vm_start && addr + len < mpnt->vm_end)
			goto bad_munmap;
		if (addr <= mpnt->vm_start && addr + len >= mpnt->vm_end) {
			*p = mpnt->vm_next;
			mpnt->vm_next = free;
			free = mpnt;
			continue;
		}
		p = &mpnt->vm_next;
	}
	/*
	 * Ok - we have the memory areas we should free on the 'free' list,
	 * so release them, and unmap the page range..
	 */
	while (free) {
		mpnt = free;
		free = free->vm_next;
		if (mpnt->vm_ops->close)
			mpnt->vm_ops->close(mpnt);
		kfree(mpnt);
	}

	unmap_page_range(addr, len);
	return 0;
bad_munmap:
/*
 * the arguments we got were bad: put the temporary list back into the mmap list
 */
	while (free) {
		mpnt = free;
		free = free->vm_next;
		mpnt->vm_next = current->mmap;
		current->mmap = mpnt;
	}
	return -EINVAL;
}

/* This is used for a general mmap of a disk file */
int generic_mmap(struct inode * inode, struct file * file,
	unsigned long addr, size_t len, int prot, unsigned long off)
{
  	struct vm_area_struct * mpnt;
	extern struct vm_operations_struct file_mmap;
	struct buffer_head * bh;

	if (off & (inode->i_sb->s_blocksize - 1))
		return -EINVAL;

	if (len > high_memory || off > high_memory - len) /* avoid overflow */
		return -ENXIO;

	if (get_limit(USER_DS)  != TASK_SIZE)
		return -EINVAL;

	if (!inode->i_sb || !S_ISREG(inode->i_mode) || !permission(inode,MAY_READ)) {
		iput(inode);
		return -EACCES;
	}
	if (!inode->i_op || !inode->i_op->bmap) {
		iput(inode);
		return -ENOEXEC;
	}
	if (!(bh = bread(inode->i_dev,bmap(inode,0),inode->i_sb->s_blocksize))) {
		iput(inode);
		return -EACCES;
	}
	if (!IS_RDONLY(inode)) {
		inode->i_atime = CURRENT_TIME;
		inode->i_dirt = 1;
	}
	brelse(bh);

	mpnt = (struct vm_area_struct * ) kmalloc(sizeof(struct vm_area_struct), GFP_KERNEL);
	if (!mpnt){
		iput(inode);
		return -ENOMEM;
	}

	unmap_page_range(addr, len);	
	mpnt->vm_task = current;
	mpnt->vm_start = addr;
	mpnt->vm_end = addr + len;
	mpnt->vm_page_prot = prot;
	mpnt->vm_share = NULL;
	mpnt->vm_inode = inode;
	inode->i_count++;
	mpnt->vm_offset = off;
	mpnt->vm_ops = &file_mmap;
	mpnt->vm_next = current->mmap;
	current->mmap = mpnt;
#if 0
	printk("VFS: Loaded mmap at %08x -  %08x\n",
		mpnt->vm_start,	mpnt->vm_end);
#endif
	return 0;
}

