Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk> Acked-by: Greg Ungerer <gerg@uclinux.org>
		
			
				
	
	
		
			131 lines
		
	
	
	
		
			3.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			131 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 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 = c->vm_end;
 | 
						|
		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);
 | 
						|
}
 |