/*
 * Copyright (c) 2002 Endace Measurement Systems Ltd, Hamilton, New Zealand.
 * All rights reserved.
 *
 * This source code is proprietary to Endace Measurement Systems and no part
 * of it may be redistributed, published or disclosed except as outlined in
 * the written contract supplied with this product.
 *
 * $Id: dagmem.c 10107 2008-10-15 04:18:20Z vladimir $
 *
 * DAG Memory allocation module.
 *
 * XXX gotchas:
 *	Driver currently does not free memory on module unload.
 *	This is due to the fact that we pretend to be a PCI device
 *	to probe for Dags, but never attach them, so do not get
 *	notified ever. The proper way of doing things is to write
 *	a private MOD_UNLOAD function which can deal with the problem.
 *	Not doing this at the moment, due to time constraints.
 *				Joerg 2002/03/05
 *
 * For high performance, the DAG network capture boards require the
 * supply of large contigous memory in order to efficiently do DMA.
 * On the PC this memory needs to be allocated early during kernel boot,
 * the virtual memory system will split it into smaller and smaller pieces
 * and there is no chance of getting big chunks at any time later. To
 * allow for loading/unloading of the DAG driver at any time, the memory
 * allocation has been separated as a standalone module. Simply put some
 * statements similiar to these ones into your /boot/loader.rc:
 *	load kernel
 *	load dagmem
 *	autoboot
 * to get dagmem do it's job. Although this driver can still be unloaded
 * at a later time (if memory has been freed by everyone and there is
 * no DAG driver resident in the kernel to call dagmem_malloc()) and
 * memory will be freed, runtime reloading dagmem is unlikely to make you
 * happy because contigmalloc() is very likely to fail.
 *
 * A number of configuration parameters may be specified as environment
 * variables to the kernel (again, at boot time, possibly into loader.rc):
 *
 *   Kernel environment	DAGMEM_DEFAULT_*	Value	Comment	
 *	dagmem_ndags		NDAGS		0	upper limit on
 *							configured slots,
 *							0 means autoconfig
 *	dagmem_size		SIZE		32MB	ideal for many
 *	dagmem_minphys		MINPHYS		0ul	usually no issue
 *	dagmem_maxphys		MAXPHYS		256MB	depends on DAG's DMA
 *	dagmem_align		ALIGN		4K	depends on DAG's DMA
 *	dagmem_boundary		BOUNDARY	0ul	depends on DAG's DMA
 *
 * Allocation of fixed size memory from predefined slots.
 *
 * Bugs: dagmem does know about DAG's PCI device id's in autoconfiguration
 * mode. With new DAG's coming up, this code needs to be updated as well.
 *
 * XXX still need to implement unloading properly
 * changed the way pci device ids's are dicoverd to use the common endace 
 * interface which is used for names and PCI IDS  vladimir 
 */
#include	<sys/types.h>
#include	<sys/module.h>
#include	<sys/systm.h>
#include	<sys/errno.h>
#include	<sys/param.h>
#include	<sys/kernel.h>
#include	<sys/conf.h>
#include	<sys/uio.h>
#include	<sys/malloc.h>
#include	<sys/bus.h>

#if __FreeBSD_version < 600000
#include	<machine/bus_memio.h>
#include	<machine/bus_pio.h>
#endif
#include	<machine/bus.h>
#include	<machine/resource.h>
#include	<machine/stdarg.h>
#include	<vm/vm.h>
#include	<vm/pmap.h>

#if (__FreeBSD_version < 504000)
#include	<pci/pcivar.h>
#else
#include <dev/pci/pcivar.h>	/* For get_pci macros! */
#endif

#include	<dagdebug.h>
#include	<dagmem.h>
#include	<dagpci.h>


typedef struct dagmem {
	void		*vaddr;
	int		taken;
	device_t	dev;
} dagmem_t;

static int	dagmem_ndags = DAGMEM_DEFAULT_NDAGS; /* # of dagmem's	*/
static u_long	dagmem_size = DAGMEM_DEFAULT_SIZE;   /* for allocation	*/
static u_long	minphys = DAGMEM_DEFAULT_MINPHYS;
static u_long	maxphys = DAGMEM_DEFAULT_MAXPHYS;
static u_long	align = DAGMEM_DEFAULT_ALIGN;
static u_long	boundary = DAGMEM_DEFAULT_BOUNDARY;
static dagmem_t	*dagmem;		/* array of fixed memory chunks	*/

# if 0
/*
 * There is currently no function to request the number of devices of
 * a certain type. We'd love to use pci_conf_match(), but this does
 * only return success/fail, not the number of matches, which we need.
 * So - we have to do it by ourselfes. Hopefully, the PCI code does not
 * divert too much over time to keep us busy with updates here.
 */
# include	<pci/pcireg.h>
# include	<pci/pcivar.h>

extern STAILQ_HEAD(devlist, pci_devinfo) pci_devq;	/* in pci/pci.c */
extern u_int32_t pci_generation;			/* in pci/pci.c */

void
dagmem_init(void *type)
{
	/* argument is a type pointer, currently ignored */
	struct pci_devinfo	*dinfo;
	int			ndags = 0, edags = 0;
	unsigned long		minphys = DAGMEM_DEFAULT_MINPHYS,
				maxphys = DAGMEM_DEFAULT_MAXPHYS,
				align = DAGMEM_DEFAULT_ALIGN,
				boundary = DAGMEM_DEFAULT_BOUNDARY;

	if(pci_generation == 0) {
		/*
		 * This is the first time after kernel load that
		 * we are getting called. Unfortunately, no devices
		 * have been configured at this time. We wait until
		 * device configuration has completed. The trick is
		 * that devconfig takes place with interrupts disabled.
		 * By putting ourselfes onto the callout queue we
		 * will be awaken as soon as devconfig is complete -
		 * just about right to steal the dag memory pages.
		 */
		DD(("dagmem: too early\n"));
		(void)timeout(dagmem_init, type, 1);
		return;
	}
	DD(("dagmem: just right, pci configuration complete\n"));
	for( dinfo = STAILQ_FIRST(&pci_devq) ; dinfo != NULL ;
				dinfo = STAILQ_NEXT(dinfo, pci_links)) {
		if(dinfo->conf.pc_vendor == 0x121b)	/* ATMLtd */
			switch(dinfo->conf.pc_device) {
			  case 0xd210:	/* DAG 2.1X OC3 */
				DD(("dagmem: device 0x%x\n",
							dinfo->conf.pc_device));
				ndags++;
				break;
			  /* other DAG's expected here */
			  default:
				/* do nothing, could be a real ATMLtd */
				break;
			}
	}
	DD(("dagmem: found %d dags\n", ndags));

	/*
	 * Tuning configuration parameters for proper allocation.
	 */
	(void)getenv_int("dagmem_ndags", &edags);
	if((edags > 0) && (edags < ndags))
		dagmem_ndags = edags;
	else
		dagmem_ndags = ndags;
	(void)getenv_int("dagmem_size", (int *)&dagmem_size);
	(void)getenv_int("dagmem_minphys", (int *)&minphys);
	(void)getenv_int("dagmem_maxphys", (int *)&maxphys);
	(void)getenv_int("dagmem_align", (int *)&align);
	(void)getenv_int("dagmem_boundary", (int *)&boundary);

	DD(("dagmem: configuring %d dags size 0x%lx\n",
						dagmem_ndags, dagmem_size));

	dagmem = malloc(dagmem_ndags*sizeof(dagmem_t), M_DEVBUF, M_NOWAIT);
	if(!dagmem) {
		DD(("dagmem: out of memory\n"));
		return;	/* XXX need to do something about it */
	}
	bzero(dagmem, dagmem_ndags*sizeof(dagmem_t));

	for(ndags = 0 ; ndags < dagmem_ndags ; ndags++) {
		dagmem[ndags].vaddr = contigmalloc(dagmem_size,
				M_DEVBUF, M_NOWAIT,
				minphys, maxphys, align, boundary);
		if(dagmem[ndags].vaddr == NULL) {
			DD(("dagmem: no memory for dag%d\n", ndags));
			dagmem[ndags].taken = 1;
			return;
		} else {
			DD(("dagmem: dag%d memory at vaddr %p\n",
						ndags, dagmem[ndags].vaddr));
		}
	}
}
# endif

/*
 * Entry points for people to use dagmem service.
 */
void *
dagmem_malloc(unsigned long size, struct malloc_type *type, int flags)
{
	intrmask_t	s;		/* for locking purposes		*/
	int		unit;		/* slots in mem array		*/
	void		*vaddr = NULL;	/* what people are looking for	*/

	if(size > dagmem_size)
		return NULL;	/* no pardon */

	s = splvm();
	for( unit = 0 ; unit < dagmem_ndags ; unit++ ) {
		if(!dagmem[unit].taken) {
			dagmem[unit].taken++;
			vaddr = dagmem[unit].vaddr;
			break;
		}
	}
	splx(s);
	DD(("dagmem: malloc returning %p\n", vaddr));
	return vaddr;	/* DAG's have to deal with failed allocation */
}

void
dagmem_free(void *addr, struct malloc_type *type)
{
	intrmask_t	s;
	int		unit;
	int		freed = 0;

	if(addr == NULL)
		return;	/* be friendly */
	s = splvm();
	for( unit = 0 ; unit < dagmem_ndags ; unit++ ) {
		if(dagmem[unit].vaddr == addr) {
			dagmem[unit].taken = 0;
			DD(("dagmem: vaddr %p freed\n", addr));
			freed++;
			break;
		}
	}
	splx(s);
	if(!freed)
		DD(("dagmem: trying to free bogus vaddr %p!\n", addr));
}

/*
 * Support routines
 */
static void
dagdebug(char *fmt, ...)
{
	va_list	ap;
	/* if( we have proper debugging level ) */
	/* could be changed to call log() */
	va_start(ap, fmt);
	vprintf(fmt, ap);
	va_end(ap);
}

/*
 * Driver loading and unloading (module support)
 */
/*      
 * The PCI bus is little endian, so device IDs are product.vendor.
 */

static int
dagmem_probe(device_t dev)
{  
	unsigned devid = pci_get_devid(dev);   
	
	static int	once = 1;
	static int	unit = 0;
	int		i;

	DD(("dagmem probe %p 0x%.8x\n", dev, devid));

	if(once) {
		(void)getenv_int("dagmem_ndags", &dagmem_ndags);
		(void)getenv_int("dagmem_size", (int *)&dagmem_size);
		(void)getenv_int("dagmem_minphys", (int *)&minphys);
		(void)getenv_int("dagmem_maxphys", (int *)&maxphys);
		(void)getenv_int("dagmem_align", (int *)&align);
		(void)getenv_int("dagmem_boundary", (int *)&boundary);

		DD(("dagmem: ndags %d size %u minphys %u maxphys %u align %u boundary %u\n",
				dagmem_ndags, dagmem_size, minphys, maxphys, align, boundary));

		dagmem = malloc(dagmem_ndags*sizeof(dagmem_t), M_DEVBUF, M_NOWAIT);
		if(dagmem == NULL) {
			DA(("dagmem: no memory for struct dagmem\n"));
			goto done;	/* very strange indeed */
		}
		bzero(dagmem, dagmem_ndags*sizeof(dagmem_t));

		once = 0;	/* never do this again */
	}

	/*
	 * Since we do not claim the device (what a proper PCI device driver
	 * should do) we may get called twice for the same device by the PCI
	 * device driver frame work. In order to catch this case we tag the
	 * Dags we already have allocated memory for.
	 */
	for( i = 0 ; i < dagmem_ndags ; i++)
		if(dagmem[i].dev == dev) {
			DD(("dagmem: duplicate call for %p\n", dev));
			goto done;
		}

	if( ( (devid & 0xFFFF) == PCI_VENDOR_ID_DAG) ||  ( (devid & 0xFFFF) == PCI_VENDOR_ID_ENDACE) ) {
			dagmem[unit].vaddr = contigmalloc(dagmem_size,
				M_DEVBUF, M_NOWAIT,
				minphys, maxphys, align, boundary);
			dagmem[unit].dev = dev;
			if(dagmem[unit].vaddr == NULL) {
				DA(("dagmem%u: no contiguous memory\n", unit));
				dagmem[unit].taken = 1;
				return ENXIO;
			} else {
				DD(("dagmem%u: %u bytes at %p\n", unit,
					dagmem_size, dagmem[unit].vaddr));
			}
			DA(("dagmem%u: <%s> %uMB at virt %p phys %.8p\n",
				unit, dag_device_name(devid>>16,1), dagmem_size/(1024*1024),
				dagmem[unit].vaddr, vtophys(dagmem[unit].vaddr)));
			unit++;
	}

done:
	return ENXIO;
}



/*
 * Driver loading and unloading support
 */
static device_method_t dagmem_methods[] = {
	DEVMETHOD(device_probe,		dagmem_probe),

	{0,0}
};

static driver_t dagmem_driver = {
	"dagmem",
	dagmem_methods,
	0,
};

static devclass_t	dagmem_devclass;
           
DRIVER_MODULE(dagmem, pci, dagmem_driver, dagmem_devclass, 0, 0);
MODULE_VERSION(dagmem, 0);

