blob: f714cdd9578fd7bd59b2e743425328357af59dfd [file] [log] [blame]
/*
* DMA Pool allocator
*
* Copyright 2001 David Brownell
* Copyright 2007 Intel Corporation
* Author: Matthew Wilcox <willy@linux.intel.com>
*
* This software may be redistributed and/or modified under the terms of
* the GNU General Public License ("GPL") version 2 as published by the
* Free Software Foundation.
*
* This allocator returns small blocks of a given size which are DMA-able by
* the given device. It uses the dma_alloc_coherent page allocator to get
* new pages, then splits them up into blocks of the required size.
* Many older drivers still have their own code to do this.
*
* The current design of this allocator is fairly simple. The pool is
* represented by the 'struct dma_pool' which keeps a doubly-linked list of
* allocated pages. Each page in the page_list is split into blocks of at
* least 'size' bytes. Free blocks are tracked in an unsorted singly-linked
* list of free blocks within the page. Used blocks aren't tracked, but we
* keep a count of how many are currently allocated from each page.
*/
#include <linux_compat.h>
struct dma_pool {
struct list_head page_list;
spinlock_t lock;
size_t size;
void *dev;
size_t allocation;
size_t boundary;
char name[32];
struct list_head pools;
};
struct dma_page {
struct list_head page_list;
void *vaddr;
dma_addr_t dma;
unsigned int in_use;
unsigned int offset;
};
/**
* dma_pool_create - Creates a pool of consistent memory blocks, for dma.
*/
struct dma_pool *dma_pool_create(const char *name, void *dev,
size_t size, size_t align, size_t boundary)
{
struct dma_pool *retval;
size_t allocation;
if (align == 0)
align = 1;
else if (align & (align - 1))
return NULL;
if (size == 0)
return NULL;
else if (size < 4)
size = 4;
if ((size % align) != 0)
size = ALIGN(size, align);
allocation = MAX_T(size_t, size, PAGE_SIZE);
if (!boundary)
boundary = allocation;
else if ((boundary < size) || (boundary & (boundary - 1)))
return NULL;
retval = kmalloc(sizeof(*retval), MEM_WAIT);
if (!retval)
return retval;
strlcpy(retval->name, name, sizeof(retval->name));
retval->dev = dev; /* FIXME */
INIT_LIST_HEAD(&retval->page_list);
spinlock_init(&retval->lock);
retval->size = size;
retval->boundary = boundary;
retval->allocation = allocation;
INIT_LIST_HEAD(&retval->pools);
/* TODO device_create_file */
return retval;
}
void dma_pool_destroy(struct dma_pool *pool)
{
/* TODO */
}
static void pool_initialise_page(struct dma_pool *pool, struct dma_page *page)
{
unsigned int offset = 0;
unsigned int next_boundary = pool->boundary;
do {
unsigned int next = offset + pool->size;
if (unlikely((next + pool->size) >= next_boundary)) {
next = next_boundary;
next_boundary += pool->boundary;
}
*(int *)(page->vaddr + offset) = next;
offset = next;
} while (offset < pool->allocation);
}
static struct dma_page *pool_alloc_page(struct dma_pool *pool, int mem_flags)
{
struct dma_page *page;
page = kmalloc(sizeof(*page), mem_flags);
if (!page)
return NULL;
page->vaddr = dma_alloc_coherent(pool->dev, pool->allocation,
&page->dma, mem_flags);
if (page->vaddr) {
pool_initialise_page(pool, page);
page->in_use = 0;
page->offset = 0;
} else {
kfree(page);
page = NULL;
}
return page;
}
void *dma_pool_alloc(struct dma_pool *pool, int mem_flags, dma_addr_t *handle)
{
struct dma_page *page;
size_t offset;
void *retval;
/* FIXME take care of locks */
list_for_each_entry(page, &pool->page_list, page_list) {
if (page->offset < pool->allocation)
goto ready;
}
page = pool_alloc_page(pool, mem_flags);
if (!page)
return NULL;
list_add(&page->page_list, &pool->page_list);
ready:
page->in_use++;
offset = page->offset;
page->offset = *(int *)(page->vaddr + offset); /* "next" */
retval = offset + page->vaddr;
*handle = offset + page->dma;
return retval;
}
void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr)
{
/* TODO */
}