/*
 * OHCI HCD (Host Controller Driver) for USB.
 * 
 * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at>
 * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net>
 * 
 * This file is licenced under the GPL.
 * $Id: ohci-mem.c,v 1.2 2002/01/19 00:22:13 dbrownell Exp $
 */

/*-------------------------------------------------------------------------*/

/*
 * There's basically three types of memory:
 *	- data used only by the HCD ... kmalloc is fine
 *	- async and periodic schedules, shared by HC and HCD ... these
 *	  need to use pci_pool or pci_alloc_consistent
 *	- driver buffers, read/written by HC ... single shot DMA mapped 
 *
 * There's also PCI "register" data, which is memory mapped.
 * No memory seen by this driver is pagable.
 */

/*-------------------------------------------------------------------------*/

static struct usb_hcd *ohci_hcd_alloc (void)
{
	struct ohci_hcd *ohci;

	ohci = (struct ohci_hcd *) kmalloc (sizeof *ohci, GFP_KERNEL);
	if (ohci != 0) {
		memset (ohci, 0, sizeof (struct ohci_hcd));
		return &ohci->hcd;
	}
	return 0;
}

static void ohci_hcd_free (struct usb_hcd *hcd)
{
	kfree (hcd_to_ohci (hcd));
}

/*-------------------------------------------------------------------------*/

#ifdef	CONFIG_DEBUG_SLAB
#	define OHCI_MEM_FLAGS	SLAB_POISON
#else
#	define OHCI_MEM_FLAGS	0
#endif
 
#ifndef CONFIG_PCI
#	error "usb-ohci currently requires PCI-based controllers"
	/* to support non-PCI OHCIs, you need custom bus/mem/... glue */
#endif


/* Recover a TD/ED using its collision chain */
static inline void *
dma_to_ed_td (struct hash_list_t * entry, dma_addr_t dma)
{
	struct hash_t * scan = entry->head;
	while (scan && scan->dma != dma)
		scan = scan->next;
	return scan->virt;
}

static struct ed *
dma_to_ed (struct ohci_hcd *hc, dma_addr_t ed_dma)
{
	return (struct ed *) dma_to_ed_td(&(hc->ed_hash [ED_HASH_FUNC(ed_dma)]),
				      ed_dma);
}

static struct td *
dma_to_td (struct ohci_hcd *hc, dma_addr_t td_dma)
{
	td_dma &= TD_MASK;
	return (struct td *) dma_to_ed_td(&(hc->td_hash [TD_HASH_FUNC(td_dma)]),
				      td_dma);
}

// FIXME:  when updating the hashtables this way, mem_flags is unusable...

/* Add a hash entry for a TD/ED; return true on success */
static int
hash_add_ed_td (
	struct hash_list_t *entry,
	void *virt,
	dma_addr_t dma,
	int mem_flags
)
{
	struct hash_t * scan;
	
	scan = (struct hash_t *) kmalloc (sizeof *scan, mem_flags);
	if (!scan)
		return 0;
	
	if (!entry->tail) {
		entry->head = entry->tail = scan;
	} else {
		entry->tail->next = scan;
		entry->tail = scan;
	}
	
	scan->virt = virt;
	scan->dma = dma;
	scan->next = NULL;
	return 1;
}

static inline int
hash_add_ed (struct ohci_hcd *hc, struct ed *ed, int mem_flags)
{
	return hash_add_ed_td (&(hc->ed_hash [ED_HASH_FUNC (ed->dma)]),
			ed, ed->dma, mem_flags);
}

static inline int
hash_add_td (struct ohci_hcd *hc, struct td *td, int mem_flags)
{
	return hash_add_ed_td (&(hc->td_hash [TD_HASH_FUNC (td->td_dma)]),
			td, td->td_dma, mem_flags);
}


static void
hash_free_ed_td (struct hash_list_t *entry, void *virt)
{
	struct hash_t *scan, *prev;
	scan = prev = entry->head;

	// Find and unlink hash entry
	while (scan && scan->virt != virt) {
		prev = scan;
		scan = scan->next;
	}
	if (scan) {
		if (scan == entry->head) {
			if (entry->head == entry->tail)
				entry->head = entry->tail = NULL;
			else
				entry->head = scan->next;
		} else if (scan == entry->tail) {
			entry->tail = prev;
			prev->next = NULL;
		} else
			prev->next = scan->next;
		kfree(scan);
	}
}

static inline void
hash_free_ed (struct ohci_hcd *hc, struct ed * ed)
{
	hash_free_ed_td (&(hc->ed_hash[ED_HASH_FUNC(ed->dma)]), ed);
}

static inline void
hash_free_td (struct ohci_hcd *hc, struct td * td)
{
	hash_free_ed_td (&(hc->td_hash[TD_HASH_FUNC(td->td_dma)]), td);
}


static int ohci_mem_init (struct ohci_hcd *ohci)
{
	ohci->td_cache = pci_pool_create ("ohci_td", ohci->hcd.pdev,
		sizeof (struct td),
		32 /* byte alignment */,
		0 /* no page-crossing issues */,
		GFP_KERNEL | OHCI_MEM_FLAGS);
	if (!ohci->td_cache)
		return -ENOMEM;
	ohci->ed_cache = pci_pool_create ("ohci_ed", ohci->hcd.pdev,
		sizeof (struct ed),
		16 /* byte alignment */,
		0 /* no page-crossing issues */,
		GFP_KERNEL | OHCI_MEM_FLAGS);
	if (!ohci->ed_cache) {
		pci_pool_destroy (ohci->td_cache);
		return -ENOMEM;
	}
	return 0;
}

static void ohci_mem_cleanup (struct ohci_hcd *ohci)
{
	if (ohci->td_cache) {
		pci_pool_destroy (ohci->td_cache);
		ohci->td_cache = 0;
	}
	if (ohci->ed_cache) {
		pci_pool_destroy (ohci->ed_cache);
		ohci->ed_cache = 0;
	}
}

/* TDs ... */
static struct td *
td_alloc (struct ohci_hcd *hc, int mem_flags)
{
	dma_addr_t	dma;
	struct td	*td;

	td = pci_pool_alloc (hc->td_cache, mem_flags, &dma);
	if (td) {
		td->td_dma = dma;
		/* hash it for later reverse mapping */
		if (!hash_add_td (hc, td, mem_flags)) {
			pci_pool_free (hc->td_cache, td, dma);
			return NULL;
		}
	}
	return td;
}

static void
td_free (struct ohci_hcd *hc, struct td *td)
{
	hash_free_td (hc, td);
	pci_pool_free (hc->td_cache, td, td->td_dma);
}


/* EDs ... */
static struct ed *
ed_alloc (struct ohci_hcd *hc, int mem_flags)
{
	dma_addr_t	dma;
	struct ed	*ed;

	ed = pci_pool_alloc (hc->ed_cache, mem_flags, &dma);
	if (ed) {
		memset (ed, 0, sizeof (*ed));
		ed->dma = dma;
		/* hash it for later reverse mapping */
		if (!hash_add_ed (hc, ed, mem_flags)) {
			pci_pool_free (hc->ed_cache, ed, dma);
			return NULL;
		}
	}
	return ed;
}

static void
ed_free (struct ohci_hcd *hc, struct ed *ed)
{
	hash_free_ed (hc, ed);
	pci_pool_free (hc->ed_cache, ed, ed->dma);
}

