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

#include "main.h"



/******************************************************************************
*
*	Function:	_bar_read
*
*	Purpose:
*
*		Read and decode a PCI Base Address Register.
*
*	Arguments:
*
*		pci		The PCI structure for the device to read.
*
*		bar		The data is recorded here.
*
*		offset	The offset of the BAR register in PCI space.
*
*		index	The index of the BAR register (i.e. the X in BARX).
*
*		mmap	The cumulative size of the memory regions for mmap()
*				is recorded here.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _bar_read(
	struct pci_dev*	pci,
	dev_bar_t*		bar,
	int				offset,
	int				index,
	u32*			mmap)
{
	int	mapable	= 0;
	u32	mask;
	u32	reg;

	memset(bar, 0, sizeof(dev_bar_t));
	bar->pci_offset	= offset;
	bar->index		= index;
	pci_read_config_dword(pci, offset, &reg);
	/* Use a PCI feature to get the address mask. */
	pci_write_config_dword(pci, offset, ~0);
	pci_read_config_dword(pci, offset, &bar->size);
	/* Restore the register. */
	pci_write_config_dword(pci, offset, reg);

	if (reg & 1)
	{
		// This is an I/O space region.
		bar->mem_spc	= 0;
		mask			= 0x3;
	}
	else
	{
		// This is a memory space region.
		bar->mem_spc	= 1;
		mask			= 0xF;
		mapable			= reg ? 1 : 0;
	}

	bar->reg		= reg;
	bar->flags		= reg & mask;
	bar->address	= reg & ~mask;
	bar->size		= bar->size & ~mask;
	bar->size		= 1 + ~(bar->size & ~mask);

	if (mapable)
	{
		bar->mmap_offset	= mmap[0];
		bar->mmap_size		= ROUND_TO_PAGE_BOUNDARY(bar->size);
		bar->mmap_inset		= bar->address % PAGE_SIZE;


		if (index < 5)
			mmap[0]	+= bar->mmap_size;
	}
}



/******************************************************************************
*
*	Function:	_bar_mem_overlay_check
*
*	Purpose:
*
*		See if the given BAR memory region resides within a page that has
*		already been mapped into the driver's memory space. This applies to
*		those systems which do not map each BAR memory region on a page
*		boundary. The assumption being made in this code is that each BAR
*		memory regions falls within only a single memory page.
*
*		NOTE: If the BAR memory region is not page alligned and it is more
*				than a single page, this code, and the code using it, will
*				need to be modified.
*
*	Arguments:
*
*		dev		The device whose BAR regions to check.
*
*		bar		The BAR region of interest.
*
*	Returned:
*
*		NULL	The region does not reside within another region.
*		else	The virtual address of the page that contains the given BAR
*				region.
*
******************************************************************************/

static void* _bar_mem_overlay_check(dev_data_t* dev, dev_bar_t* bar)
{
	unsigned long	add_bar;
	unsigned long	add_ref;
	int				i;
	dev_bar_t*		ref;
	void*			vaddr	= NULL;

	add_bar	= bar->address - bar->mmap_inset;

	for (i = 0; i < 6; i++)
	{
		ref	= &dev->bars.bar[i];

		if (ref->address == 0)	// The BAR region is unused.
			continue;

		if (ref->mem_spc == 0)	// The BAR region is I/O space.
			continue;

		if (ref->vaddr == NULL)	// The BAR hasn't been mapped.
			continue;

		add_ref	= ref->address - ref->mmap_inset;

		if (add_ref != add_bar)	// The BAR is mapped to a different page.
			continue;

		// The two BAR regions are mapped to the same page.
		vaddr	= ref->vaddr - ref->mmap_inset;
		break;
	}

	return(vaddr);
}



/******************************************************************************
*
*	Function:	_bar_region_check
*
*	Purpose:
*
*		See if all of the given BAR region is available.
*
*	Arguments:
*
*		bar		The region data to check.
*
*	Returned:
*
*		0		The required region is available.
*		>0		The number of errors seen.
*
******************************************************************************/

static int _bar_region_check(dev_bar_t* bar)
{
	int	errs;

	if (bar->size == 0)
	{
		// The region isn't required.
		errs	= 0;
	}
	else if (bar->mem_spc)
	{
		// The region is in memory space.
		errs	= REGION_MEM_CHECK(bar->address, bar->size);
		errs	= (errs < 0) ? 1 : 0;
	}
	else
	{
		// The region is in I/O space.
		errs	= REGION_IO_CHECK(bar->address, bar->size);
		errs	= (errs < 0) ? 1 : 0;
	}

	return(errs);
}



/******************************************************************************
*
*	Function:	_bar_region_release
*
*	Purpose:
*
*		Release ownership of the given BAR region.
*
*	Arguments:
*
*		bar		The region data to use.
*
*	Returned:
*
*		None.
*
******************************************************************************/

static void _bar_region_release(dev_bar_t* bar)
{
	if (bar->requested == 0)
	{
		// The region has not been requested.
	}
	else if (bar->mem_spc)
	{
		// The region is in memory space.

		if (bar->mapped)
			iounmap(bar->vaddr);

		REGION_MEM_RELEASE(bar->address, bar->size);
	}
	else
	{
		// The region is in I/O space.
		REGION_IO_RELEASE(bar->address, bar->size);
	}
}



/******************************************************************************
*
*	Function:	_bar_region_request
*
*	Purpose:
*
*		Request ownership of the given BAR region.
*
*	Arguments:
*
*		dev		The device whose BAR regions to check.
*
*		bar		The region data to use.
*
*	Returned:
*
*		0		The region were granted, or not required.
*		>0		The number of errors seen.
*
******************************************************************************/

static int _bar_region_request(dev_data_t* dev, dev_bar_t* bar)
{
	int		errs;
	void*	test;

	if (bar->size == 0)
	{
		// The region is unused.
		errs	= 0;
	}
	else if (bar->mem_spc)
	{
		// The region is in memory space.
		test	= REGION_MEM_REQUEST(	bar->address,
										bar->size,
										DRV_DEV_DEVICE_NAME_LOWER);
		errs	= test ? 0 : 1;

		if (errs == 0)
		{
			bar->requested	= 1;
			bar->overlay	= _bar_mem_overlay_check(dev, bar);

			if (bar->overlay)
			{
				bar->vaddr	= bar->overlay + bar->mmap_inset;
			}
			else
			{
				bar->mapped	= 1;
				bar->vaddr	= ioremap(bar->address, bar->size);
			}
		}
	}
	else
	{
		// The region is in I/O space.
		test	= REGION_IO_REQUEST(bar->address,
									bar->size,
									DRV_DEV_DEVICE_NAME_LOWER);
		errs	= test ? 0 : 1;

		if (errs == 0)
		{
			bar->requested	= 1;
			bar->vaddr		= (void*) (long) bar->address;
		}
	}

	return(errs);
}



/******************************************************************************
*
*	Function:	bar_info_get_ioctl
*
*	Purpose:
*
*		Report the information we have on the specific BAR region.
*
*	Arguments:
*
*		dev	The data for the device of interest.
*
*		bar	The data for the Base Address Register of interest.
*
*	Returned:
*
*		0	All went well.
*		< 0	The negative errno code for the problem.
*
******************************************************************************/

int bar_info_get_ioctl(dev_data_t* dev, gsc_drv_bar_t* bar)
{
	dev_pid_t*	pid;
	int			status;

	pid	= pid_locate(dev);

	if (pid == NULL)
	{
		// The process data wasn't found.
		status		= -EINVAL;
		bar->status	= GSC_INVALID_DEVICE_INFO;
	}
	else if (bar->index >= ARRAY_ELEMENTS(dev->bars.bar))
	{
		// The specified Base Address Register doesn't exist.
		status		= -EINVAL;
		bar->status	= GSC_INVALID_INDEX;
	}
	else
	{
		status				= 0;
		bar->status			= GSC_SUCCESS;
		bar->reg			= dev->bars.bar[bar->index].reg;
		bar->size			= dev->bars.bar[bar->index].size;
		bar->mmap			= pid->bar[bar->index].vm_start ? 1 : 0;
		bar->mmap_offset	= dev->bars.bar[bar->index].mmap_offset;
		bar->mmap_size		= dev->bars.bar[bar->index].mmap_size;
		bar->mmap_start		= pid->bar[bar->index].vm_start;
		bar->mmap_inset		= dev->bars.bar[bar->index].mmap_inset;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	bar_mmap_disable_ioctl
*
*	Purpose:
*
*		Disable mapping by the application. This is done after the munmap()
*		call to let the driver know the mapping has been disabled.
*
*	Arguments:
*
*		dev	The data for the device of interest.
*
*		bar	The data for the Base Address Register of interest.
*
*	Returned:
*
*		0	All went well.
*		<0	An error code for the problem seen.
*
******************************************************************************/

int bar_mmap_disable_ioctl(dev_data_t* dev, gsc_drv_bar_t* bar)
{
	dev_pid_t*	pid;
	int			status;

	pid	= pid_locate(dev);

	if (pid == NULL)
	{
		// The process data wasn't found.
		status	= -ENODEV;
	}
	else if (bar->index >= ARRAY_ELEMENTS(dev->bars.bar))
	{
		// The specified Base Address Register doesn't exist.
		status	= -EINVAL;
	}
	else if (dev->bars.bar[bar->index].vaddr == 0)
	{
		// The specified Base Address Register region isn't used.
		status	= -EINVAL;
	}
	else if (dev->bars.bar[bar->index].mem_spc == 0)
	{
		// The specified Base Address Register region isn't mappable.
		status	= -EINVAL;
	}
	else
	{
		status	= 0;
		pid->bar[bar->index].vm_start	= 0;
	}

	return(status);
}



/******************************************************************************
*
*	Function:	bar_mmap_locate
*
*	Purpose:
*
*		Locate the BAR structure matching the given mmap data.
*
*	Arguments:
*
*		dev		The structure for the device of interest.
*
*		offset	The mmap offset value of interest.
*
*		size	The mmap size value of interst.
*
*		index	The BAR index is recorded here when found.
*
*	Returned:
*
*		NULL	The paramerts don't match a BAR region.
*		else	A pointer to the BAR regions that matches.
*
******************************************************************************/

dev_bar_t* bar_mmap_locate(dev_data_t* dev, u32 offset, u32 size, int* index)
{
	dev_bar_t*	bar	= NULL;
	int			i;

	for (i = 0; i < ARRAY_ELEMENTS(dev->bars.bar); i++)
	{
		if ((offset == dev->bars.bar[i].mmap_offset) &&
			(size == dev->bars.bar[i].mmap_size))
		{
			bar			= &dev->bars.bar[i];
			index[0]	= i;
			break;
		}
	}

	return(bar);
}



/******************************************************************************
*
*	Function:	bar_read_all
*
*	Purpose:
*
*		Read the PCI Base Address Registers.
*
*	Arguments:
*
*		pci		The PCI structure for the device to read.
*
*		bars	The data is recorded here.
*
*	Returned:
*
*		None.
*
******************************************************************************/

void bar_read_all(struct pci_dev* pci, dev_bars_t* bars)
{
	memset(bars, 0, sizeof(dev_bars_t));
	bars->mmap_size	= 0;
	_bar_read(pci, &bars->bar[0], PCI_BASE_ADDRESS_0, 0, &bars->mmap_size);
	_bar_read(pci, &bars->bar[1], PCI_BASE_ADDRESS_1, 1, &bars->mmap_size);
	_bar_read(pci, &bars->bar[2], PCI_BASE_ADDRESS_2, 2, &bars->mmap_size);
	_bar_read(pci, &bars->bar[3], PCI_BASE_ADDRESS_3, 3, &bars->mmap_size);
	_bar_read(pci, &bars->bar[4], PCI_BASE_ADDRESS_4, 4, &bars->mmap_size);
	_bar_read(pci, &bars->bar[5], PCI_BASE_ADDRESS_5, 5, &bars->mmap_size);
}



/******************************************************************************
*
*	Function:	bar_regions_check
*
*	Purpose:
*
*		See if all of the given BAR regions are available.
*
*	Arguments:
*
*		bars	The region data to check.
*
*	Returned:
*
*		0		All required regions are available.
*		>0		The number of errors seen.
*
******************************************************************************/

int bar_regions_check(dev_bars_t* bars)
{
	int	errs	= 0;

	errs	+= _bar_region_check(&bars->bar[0]);
	errs	+= _bar_region_check(&bars->bar[1]);
	errs	+= _bar_region_check(&bars->bar[2]);
	errs	+= _bar_region_check(&bars->bar[3]);
	errs	+= _bar_region_check(&bars->bar[4]);
	errs	+= _bar_region_check(&bars->bar[5]);
	return(errs);
}



/******************************************************************************
*
*	Function:	bar_regions_release
*
*	Purpose:
*
*		Release ownership of the given BAR regions.
*
*	Arguments:
*
*		bars	The region data to use.
*
*	Returned:
*
*		None.
*
******************************************************************************/

void bar_regions_release(dev_bars_t* bars)
{
	_bar_region_release(&bars->bar[0]);
	_bar_region_release(&bars->bar[1]);
	_bar_region_release(&bars->bar[2]);
	_bar_region_release(&bars->bar[3]);
	_bar_region_release(&bars->bar[4]);
	_bar_region_release(&bars->bar[5]);
}



/******************************************************************************
*
*	Function:	bar_regions_request
*
*	Purpose:
*
*		Request ownership of the given BAR regions.
*
*	Arguments:
*
*		dev		The device of interest.
*
*	Returned:
*
*		0		All required regions were granted.
*		>0		The number of errors seen.
*
******************************************************************************/

int bar_regions_request(dev_data_t* dev)
{
	int	errs	= 0;

	errs	+= _bar_region_request(dev, &dev->bars.bar[0]);
	errs	+= _bar_region_request(dev, &dev->bars.bar[1]);
	errs	+= _bar_region_request(dev, &dev->bars.bar[2]);
	errs	+= _bar_region_request(dev, &dev->bars.bar[3]);
	errs	+= _bar_region_request(dev, &dev->bars.bar[4]);
	errs	+= _bar_region_request(dev, &dev->bars.bar[5]);
	return(errs);
}



/******************************************************************************
*
*	Function:	bar_vma_locate
*
*	Purpose:
*
*		Locate the BAR structure matching the given mmap data.
*
*	Arguments:
*
*		dev		The structure for the device of interest.
*
*		vma		The mapping structure.
*
*		index	The BAR index is recorded here when found.
*
*	Returned:
*
*		NULL	The paramerts don't match a BAR region.
*		else	A pointer to the BAR regions that matches.
*
******************************************************************************/

dev_bar_t* bar_vma_locate(dev_data_t* dev, void* vma, int* index)
{
	dev_bar_t*	bar	= NULL;
	int			i;

	for (i = 0; i < ARRAY_ELEMENTS(dev->bars.bar); i++)
	{
		if (vma == dev->bars.bar[i].mmap_vma)
		{
			bar			= &dev->bars.bar[i];
			index[0]	= i;
			break;
		}
	}

	return(bar);
}



