/* This software is covered by the GNU GENERAL PUBLIC LICENSE (GPL). */
/* Any Device - Driver - Linux */
/* $Workfile: mmap.c $ */
/* $Revision: 4 $ */
/* $Date: 10/27/07 10:36a $ */
/* $Log: /NEW SOURCESAFE DATABASE/SDK/PLX/gsc/common/driver/linux/mmap.c $
 * 
 * 4     10/27/07 10:36a Dnewbold
 * Additional driver porting updates.
 *
 * 3     4/03/07 4:38p Dnewbold
 * Updated the BAR munmap code.
 *
 * 2     3/29/07 2:40p Dnewbold
 *
 * 1     5/10/06 1:02p Dnewbold
*/

#include "main.h"


/******************************************************************************
*
*	Function:	_check_overlap
*
*	Purpose:
*
*		Check to see if the given region overlaps any existing blocks.
*
*	Arguments:
*
*		offset	The offset for the block to check.
*
*		size	The size of the block to check.
*
*		pid		The structure for the requesting process.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static int _check_overlap(u32 offset, u32 size, const dev_pid_t* pid)
{
	u32	end;
	int	i;
	int	overlap	= 0;
	u32	stop	= offset + size - 1;

	for (i = 0; i < ARRAY_ELEMENTS(pid->mem); i++)
	{
		if (pid->mem[i] == NULL)
			continue;

		if (pid->mem[i]->size == 0)
			continue;

		if (pid->mem[i]->mmap_offset == 0)
			continue;

		if (stop < pid->mem[i]->mmap_offset)
			continue;

		end	= pid->mem[i]->mmap_offset + pid->mem[i]->size - 1;

		if (offset > end)
			continue;

		overlap	= 1;
		break;
	}

	return(overlap);
}



/******************************************************************************
*
*	Function:	_mmap_close
*
*	Purpose:
*
*		Implement the mmap close() procedure.
*
*	Arguments:
*
*		vma	The virtual memory area of interest.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _mmap_close(struct vm_area_struct* vma)
{
	dev_bar_t*		bar;
	dev_data_t*		dev;
	int				i;
	dev_mem_t*		mem;
	unsigned long	offset;
	dev_pid_t*		pid;
	int				test;
	unsigned long	size;

	for (;;)	// A convenience loop.
	{
		dev	= dev_locate(vma->vm_file->f_dentry->d_inode);

		if (dev == NULL)
			break;

		// Gain the necessary access.
		test	= down_interruptible(&dev->sem);

		if (test)
			break;	// We got a signal rather than the lock.

		// Adjust the usage counter.
		MOD_DEC_USE_COUNT;

		if (dev->mmap.usage)
			dev->mmap.usage--;

		// Locate the process specific data.
		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			/* The process data wasn't found. */
			up(&dev->sem);
			break;
		}

		// Locate the memory region specific data.
		offset	= VM_OFFSET_BYTES(vma);
		size	= vma->vm_end - vma->vm_start;
		bar		= bar_vma_locate(dev, vma, &i);

		if (bar)
		{
			if (pid->bar[i].mmap_usage > 0)
				pid->bar[i].mmap_usage--;

			if (pid->bar[i].mmap_usage <= 0)
			{
				bar->mmap_vma			= NULL;
				pid->bar[i].vm_start	= 0;
			}
		}

		mem		= mem_mmap_locate(pid, offset, size, 1);

		if ((mem) && (mem->mmap_users))
			mem->mmap_users--;

		up(&dev->sem);
		break;
	}
}



/******************************************************************************
*
*	Function:	_mmap_open
*
*	Purpose:
*
*		Implement the mmap open() procedure.
*
*	Arguments:
*
*		vma	The virtual memory area of interest.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _mmap_open(struct vm_area_struct* vma)
{
	dev_bar_t*		bar;
	dev_data_t*		dev;
	int				i;
	dev_mem_t*		mem;
	unsigned long	offset	= -1;
	dev_pid_t*		pid;
	int				test;
	unsigned long	size	= -1;

	for (;;)	// A convenience loop.
	{
		dev	= dev_locate(vma->vm_file->f_dentry->d_inode);

		if (dev == NULL)
			break;

		// Get the necessary access.
		test	= down_interruptible(&dev->sem);

		if (test)
		{
			/* We got a signal rather than the semaphore. */
			break;
		}

		/* Increment our usage count. */
		dev->mmap.usage++;	// Do we need this counter?
		MOD_INC_USE_COUNT;

		// Locate the process specific data.
		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			/* The process data wasn't found. */
			up(&dev->sem);
			break;
		}

		// Locate the memory region specific data.
		bar		= bar_vma_locate(dev, vma, &i);

		if (bar)
		{
			// The mapped region is a BARx area.
			pid->bar[i].mmap_usage++;
		}
		else
		{
			// The mapped regions may be memory.
			offset	= VM_OFFSET_BYTES(vma);
			size	= vma->vm_end - vma->vm_start;

			mem		= mem_mmap_locate(pid, offset, size, 1);

			if (mem)
				mem->mmap_users++;
		}

		// We're done.
		up(&dev->sem);
		break;
	}
}



/******************************************************************************
*
*	Function:	mmap
*
*	Purpose:
*
*		Implement the mmap() procedure.
*
*	Arguments:
*
*		filp	The kernel file structure for the open device.
*
*		vma		The virtual memory area of interest.
*
*	Returned:
*
*		0		All went well.
*		<0		An error code for the problem seen.
*
******************************************************************************/

int mmap(struct file* filp, struct vm_area_struct* vma)
{
	dev_bar_t*		bar;
	dev_data_t*		dev;
	int				i;
	dev_mem_t*		mem;
	unsigned long	offset;
	dev_pid_t*		pid;
	int				status	= -EINVAL;
	unsigned long	size;
	int				test;

	for (;;)	/* We'll use a loop for convenience. */
	{
		/*
		*	Validate some of the parameters.
		*/

		dev	= (dev_data_t*) filp->private_data;

		if (dev == NULL)
		{
			/* The referenced device doesn't exist. */
			status	= -ENODEV;
			break;
		}

		/*
		*	Gain the neccessary access.
		*/

		test	= down_interruptible(&dev->sem);

		if (test)
		{
			/* We got a signal rather than the semaphore. */
			status	= -ERESTARTSYS;
			break;
		}

		pid	= pid_locate(dev);

		if (pid == NULL)
		{
			/* The process data wasn't found. */
			status	= -EBUSY;
			up(&dev->sem);
			break;
		}

		/*
		*	See which memory area this request applies to.
		*/

		offset	= VM_OFFSET_BYTES(vma);
		size	= vma->vm_end - vma->vm_start;
		bar		= bar_mmap_locate(dev, offset, size, &i);

		if (bar)
		{
			vma->vm_flags	|= VM_IO | VM_RESERVED;
			status	= REMAP_PAGE_RANGE(	vma,
										vma->vm_start,
										bar->address,
										bar->mmap_size,
										vma->vm_page_prot);

			if (status)
			{
				/* The request failed. */
				pid->bar[i].vm_start	= 0;
				status					= -EAGAIN;
				up(&dev->sem);
			}
			else
			{
				bar->mmap_vma			= vma;
				pid->bar[i].vm_start	= vma->vm_start;
				status					= 0;
				vma->vm_file			= filp;
				vma->vm_ops				= &global.vmops;
				up(&dev->sem);
				_mmap_open(vma);
			}

			break;
		}

		mem	= mem_mmap_locate(pid, offset, size, 1);

		if (mem)
		{
			status		= REMAP_PAGE_RANGE(
							vma,
							vma->vm_start,
							mem->phy_adrs,
							size,
							vma->vm_page_prot);

			if (status)
			{
				/* The request failed. */
				status	= -EAGAIN;
				up(&dev->sem);
				break;
			}

			// We don't add the VM_RESERVED flag as it was already specified
			// when the memory was allocated. If we set it here also, then the
			// memory will never get freed.

			status			= 0;
			vma->vm_file	= filp;
			vma->vm_ops		= &global.vmops;
			up(&dev->sem);
			_mmap_open(vma);
			break;

		}

		/* The region is invalid. */
		status	= -EINVAL;
		up(&dev->sem);
		break;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	mmap_mem_compute_settings
*
*	Purpose:
*
*		Compute the settings needed for mapping a memory block into user space.
*
*	Arguments:
*
*		dev		The data for the device of interest.
*
*		pid		The data for the process making the request.
*
*		mem		The data for the memory block to compute settings for.
*
*	Returned:
*
*		0		All went well.
*		<0		An error code for the problem seen.
*
******************************************************************************/

int mmap_mem_compute_settings(
	dev_data_t*				dev,
	dev_pid_t*				pid,
	dev_mem_t*				mem)
{
	int	hit	= 0;
	int	i;
	u32	size;
	u32	start;
	int	status;

	mem->mmap_offset	= 0;
	start				= dev->bars.mmap_size + PAGE_SIZE;
	size				= ROUND_TO_PAGE_BOUNDARY(mem->size);

	for (i = 0; i < ARRAY_ELEMENTS(pid->mem); i++)
	{
		if (pid->mem[i] == NULL)
			continue;

		if (pid->mem[i]->size == 0)
			continue;

		hit	= _check_overlap(start, size, pid);

		if (hit == 0)
		{
			mem->mmap_offset	= start;
			break;
		}

		start	+= ROUND_TO_PAGE_BOUNDARY(pid->mem[i]->size);
	}

	status	= hit ? -EBUSY : 0;
	return(status);
}


/******************************************************************************
*
*	Function:	mmap_nopage_common
*
*	Purpose:
*
*		Implement the common portions of the mmap nopage() procedure.
*
*	Arguments:
*
*		vma			The virtual memory area of interest.
*
*		address		The virtual address of the page being accessed.
*
*		pg			We record the page pointer here.
*					This will be zero when the mapping fails.
*
*	Returned:
*
*		None.
*
******************************************************************************/

void mmap_nopage_common(struct vm_area_struct*	vma,
						unsigned long			address,
						struct page**			pg)
{
	dev_data_t*		dev;
	dev_mem_t*		mem;
	unsigned long	memptr;
	unsigned long	offset;
	dev_pid_t*		pid;
	u32				size;

	for (;;)	/* A convenience loop. */
	{
		dev	= dev_locate(vma->vm_file->f_dentry->d_inode);

		if (dev == NULL)
			break;

		pid	= pid_locate(dev);

		if (pid == NULL)
			break;

		offset	= VM_OFFSET_BYTES(vma);
		size	= address - vma->vm_start;
		mem		= mem_mmap_locate(pid, offset, size, 0);

		if (mem)
		{
			memptr	= mem->krn_adrs + size;
			pg[0]	= VIRT_TO_PAGE(memptr);
			GET_PAGE(pg[0]);
			break;
		}

		/* This is an invalid access. */
		break;
	}
}



/******************************************************************************
*
*	Function:	mmap_setup
*
*	Purpose:
*
*		Perform any work needed to setup for the use of the mmap feature.
*
*	Arguments:
*
*		None.
*
*	Returned:
*
*		None.
*
******************************************************************************/

void mmap_setup(void)
{
	global.vmops.open	= _mmap_open;
	global.vmops.close	= _mmap_close;
	VM_OPS_FAULT(global.vmops, mmap_fault);
	VM_OPS_NOPAGE(global.vmops, mmap_nopage);
	VM_OPS_UNMAP(global.vmops, mmap_unmap);
}



