alistair23-linux/arch/arm/mm/vmregion.c
Russell King 5bc23d32d8 ARM: DMA coherent allocator: align remapped addresses
The DMA coherent remap area is used to provide an uncached mapping
of memory for coherency with DMA engines.  Currently, we look for
any free hole which our allocation will fit in with page alignment.

However, this can lead to fragmentation of the area, and allows small
allocations to cross L1 entry boundaries.  This is undesirable as we
want to move towards allocating sections of memory.

Align allocations according to the size, limiting the alignment between
the page and section sizes.

Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
2010-07-27 10:43:48 +01:00

133 lines
3.1 KiB
C

#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/slab.h>
#include "vmregion.h"
/*
* VM region handling support.
*
* This should become something generic, handling VM region allocations for
* vmalloc and similar (ioremap, module space, etc).
*
* I envisage vmalloc()'s supporting vm_struct becoming:
*
* struct vm_struct {
* struct vmregion region;
* unsigned long flags;
* struct page **pages;
* unsigned int nr_pages;
* unsigned long phys_addr;
* };
*
* get_vm_area() would then call vmregion_alloc with an appropriate
* struct vmregion head (eg):
*
* struct vmregion vmalloc_head = {
* .vm_list = LIST_HEAD_INIT(vmalloc_head.vm_list),
* .vm_start = VMALLOC_START,
* .vm_end = VMALLOC_END,
* };
*
* However, vmalloc_head.vm_start is variable (typically, it is dependent on
* the amount of RAM found at boot time.) I would imagine that get_vm_area()
* would have to initialise this each time prior to calling vmregion_alloc().
*/
struct arm_vmregion *
arm_vmregion_alloc(struct arm_vmregion_head *head, size_t align,
size_t size, gfp_t gfp)
{
unsigned long addr = head->vm_start, end = head->vm_end - size;
unsigned long flags;
struct arm_vmregion *c, *new;
if (head->vm_end - head->vm_start < size) {
printk(KERN_WARNING "%s: allocation too big (requested %#x)\n",
__func__, size);
goto out;
}
new = kmalloc(sizeof(struct arm_vmregion), gfp);
if (!new)
goto out;
spin_lock_irqsave(&head->vm_lock, flags);
list_for_each_entry(c, &head->vm_list, vm_list) {
if ((addr + size) < addr)
goto nospc;
if ((addr + size) <= c->vm_start)
goto found;
addr = ALIGN(c->vm_end, align);
if (addr > end)
goto nospc;
}
found:
/*
* Insert this entry _before_ the one we found.
*/
list_add_tail(&new->vm_list, &c->vm_list);
new->vm_start = addr;
new->vm_end = addr + size;
new->vm_active = 1;
spin_unlock_irqrestore(&head->vm_lock, flags);
return new;
nospc:
spin_unlock_irqrestore(&head->vm_lock, flags);
kfree(new);
out:
return NULL;
}
static struct arm_vmregion *__arm_vmregion_find(struct arm_vmregion_head *head, unsigned long addr)
{
struct arm_vmregion *c;
list_for_each_entry(c, &head->vm_list, vm_list) {
if (c->vm_active && c->vm_start == addr)
goto out;
}
c = NULL;
out:
return c;
}
struct arm_vmregion *arm_vmregion_find(struct arm_vmregion_head *head, unsigned long addr)
{
struct arm_vmregion *c;
unsigned long flags;
spin_lock_irqsave(&head->vm_lock, flags);
c = __arm_vmregion_find(head, addr);
spin_unlock_irqrestore(&head->vm_lock, flags);
return c;
}
struct arm_vmregion *arm_vmregion_find_remove(struct arm_vmregion_head *head, unsigned long addr)
{
struct arm_vmregion *c;
unsigned long flags;
spin_lock_irqsave(&head->vm_lock, flags);
c = __arm_vmregion_find(head, addr);
if (c)
c->vm_active = 0;
spin_unlock_irqrestore(&head->vm_lock, flags);
return c;
}
void arm_vmregion_free(struct arm_vmregion_head *head, struct arm_vmregion *c)
{
unsigned long flags;
spin_lock_irqsave(&head->vm_lock, flags);
list_del(&c->vm_list);
spin_unlock_irqrestore(&head->vm_lock, flags);
kfree(c);
}