132 lines
		
	
	
	
		
			3.1 KiB
			
		
	
	
	
		
			C
		
	
	
	
	
	
		
		
			
		
	
	
			132 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);
							 | 
						||
| 
								 | 
							
								}
							 |