blob: b775c71114d03ec142b17c56cf3a27995202bfb6 [file] [log] [blame]
/*
* 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);
}