#include #include #include #include #include #include #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, const void *caller) { unsigned long start = head->vm_start, addr = head->vm_end; 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, end:%lx, start:%lx)\n", __func__, size, head->vm_end, head->vm_start); goto out; } new = kmalloc(sizeof(struct arm_vmregion), gfp); if (!new) goto out; new->caller = caller; spin_lock_irqsave(&head->vm_lock, flags); addr = rounddown(addr - size, align); list_for_each_entry_reverse(c, &head->vm_list, vm_list) { if (addr >= c->vm_end) goto found; addr = rounddown(c->vm_start - size, align); if (addr < start) goto nospc; } found: /* * Insert this entry after the one we found. */ list_add(&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); } #ifdef CONFIG_PROC_FS static int arm_vmregion_show(struct seq_file *m, void *p) { struct arm_vmregion *c = list_entry(p, struct arm_vmregion, vm_list); seq_printf(m, "0x%08lx-0x%08lx %7lu", c->vm_start, c->vm_end, c->vm_end - c->vm_start); if (c->caller) seq_printf(m, " %pS", (void *)c->caller); seq_putc(m, '\n'); return 0; } static void *arm_vmregion_start(struct seq_file *m, loff_t *pos) { struct arm_vmregion_head *h = m->private; spin_lock_irq(&h->vm_lock); return seq_list_start(&h->vm_list, *pos); } static void *arm_vmregion_next(struct seq_file *m, void *p, loff_t *pos) { struct arm_vmregion_head *h = m->private; return seq_list_next(p, &h->vm_list, pos); } static void arm_vmregion_stop(struct seq_file *m, void *p) { struct arm_vmregion_head *h = m->private; spin_unlock_irq(&h->vm_lock); } static const struct seq_operations arm_vmregion_ops = { .start = arm_vmregion_start, .stop = arm_vmregion_stop, .next = arm_vmregion_next, .show = arm_vmregion_show, }; static int arm_vmregion_open(struct inode *inode, struct file *file) { struct arm_vmregion_head *h = PDE(inode)->data; int ret = seq_open(file, &arm_vmregion_ops); if (!ret) { struct seq_file *m = file->private_data; m->private = h; } return ret; } static const struct file_operations arm_vmregion_fops = { .open = arm_vmregion_open, .read = seq_read, .llseek = seq_lseek, .release = seq_release, }; int arm_vmregion_create_proc(const char *path, struct arm_vmregion_head *h) { proc_create_data(path, S_IRUSR, NULL, &arm_vmregion_fops, h); return 0; } #else int arm_vmregion_create_proc(const char *path, struct arm_vmregion_head *h) { return 0; } #endif