blob: 0722a68a49e7f6c5329e46be00d5206c3e8552aa [file] [log] [blame]
/*
* Support for Medifield PNW Camera Imaging ISP subsystem.
*
* Copyright (c) 2010 Intel Corporation. All Rights Reserved.
*
* Copyright (c) 2010 Silicon Hive www.siliconhive.com.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License version
* 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*
*/
/*
* This file contains function for ISP virtual address management in ISP driver
*/
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/page.h>
#include "atomisp_internal.h"
#include "mmu/isp_mmu.h"
#include "hmm/hmm_vm.h"
#include "hmm/hmm_common.h"
static unsigned int vm_node_end(unsigned int start, unsigned int pgnr)
{
return start + pgnr_to_size(pgnr);
}
static int addr_in_vm_node(unsigned int addr,
struct hmm_vm_node *node)
{
return (addr >= node->start) && (addr < (node->start + node->size));
}
int hmm_vm_init(struct hmm_vm *vm, unsigned int start,
unsigned int size)
{
if (!vm)
return -1;
vm->start = start;
vm->pgnr = size_to_pgnr_ceil(size);
vm->size = pgnr_to_size(vm->pgnr);
INIT_LIST_HEAD(&vm->vm_node_list);
spin_lock_init(&vm->lock);
vm->cache = kmem_cache_create("atomisp_vm", sizeof(struct hmm_vm_node),
0, 0, NULL);
return vm->cache != NULL ? 0 : -ENOMEM;
}
void hmm_vm_clean(struct hmm_vm *vm)
{
struct hmm_vm_node *node, *tmp;
struct list_head new_head;
if (!vm)
return;
spin_lock(&vm->lock);
list_replace_init(&vm->vm_node_list, &new_head);
spin_unlock(&vm->lock);
list_for_each_entry_safe(node, tmp, &new_head, list) {
list_del(&node->list);
kmem_cache_free(vm->cache, node);
}
kmem_cache_destroy(vm->cache);
}
static struct hmm_vm_node *alloc_hmm_vm_node(unsigned int pgnr,
struct hmm_vm *vm)
{
struct hmm_vm_node *node;
node = kmem_cache_alloc(vm->cache, GFP_KERNEL);
if (!node) {
dev_err(atomisp_dev, "out of memory.\n");
return NULL;
}
INIT_LIST_HEAD(&node->list);
node->pgnr = pgnr;
node->size = pgnr_to_size(pgnr);
node->vm = vm;
return node;
}
struct hmm_vm_node *hmm_vm_alloc_node(struct hmm_vm *vm, unsigned int pgnr)
{
struct list_head *head;
struct hmm_vm_node *node, *cur, *next;
unsigned int vm_start, vm_end;
unsigned int addr;
unsigned int size;
if (!vm)
return NULL;
vm_start = vm->start;
vm_end = vm_node_end(vm->start, vm->pgnr);
size = pgnr_to_size(pgnr);
addr = vm_start;
head = &vm->vm_node_list;
node = alloc_hmm_vm_node(pgnr, vm);
if (!node) {
dev_err(atomisp_dev, "no memory to allocate hmm vm node.\n");
return NULL;
}
spin_lock(&vm->lock);
/*
* if list is empty, the loop code will not be executed.
*/
list_for_each_entry(cur, head, list) {
/* Add gap between vm areas as helper to not hide overflow */
addr = PAGE_ALIGN(vm_node_end(cur->start, cur->pgnr) + 1);
if (list_is_last(&cur->list, head)) {
if (addr + size > vm_end) {
/* vm area does not have space anymore */
spin_unlock(&vm->lock);
kmem_cache_free(vm->cache, node);
dev_err(atomisp_dev,
"no enough virtual address space.\n");
return NULL;
}
/* We still have vm space to add new node to tail */
break;
}
next = list_entry(cur->list.next, struct hmm_vm_node, list);
if ((next->start - addr) > size)
break;
}
node->start = addr;
node->vm = vm;
list_add(&node->list, &cur->list);
spin_unlock(&vm->lock);
return node;
}
void hmm_vm_free_node(struct hmm_vm_node *node)
{
struct hmm_vm *vm;
if (!node)
return;
vm = node->vm;
spin_lock(&vm->lock);
list_del(&node->list);
spin_unlock(&vm->lock);
kmem_cache_free(vm->cache, node);
}
struct hmm_vm_node *hmm_vm_find_node_start(struct hmm_vm *vm, unsigned int addr)
{
struct hmm_vm_node *node;
if (!vm)
return NULL;
spin_lock(&vm->lock);
list_for_each_entry(node, &vm->vm_node_list, list) {
if (node->start == addr) {
spin_unlock(&vm->lock);
return node;
}
}
spin_unlock(&vm->lock);
return NULL;
}
struct hmm_vm_node *hmm_vm_find_node_in_range(struct hmm_vm *vm,
unsigned int addr)
{
struct hmm_vm_node *node;
if (!vm)
return NULL;
spin_lock(&vm->lock);
list_for_each_entry(node, &vm->vm_node_list, list) {
if (addr_in_vm_node(addr, node)) {
spin_unlock(&vm->lock);
return node;
}
}
spin_unlock(&vm->lock);
return NULL;
}