| /* | 
 |  * 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 */ | 
 | } |