1efd8e5aaSPaolo Bonzini /* 2efd8e5aaSPaolo Bonzini * Copyright (C) 2012, 2017, Red Hat Inc. 3efd8e5aaSPaolo Bonzini * 4efd8e5aaSPaolo Bonzini * This allocator provides contiguous physical addresses with page 5efd8e5aaSPaolo Bonzini * granularity. 6efd8e5aaSPaolo Bonzini */ 7efd8e5aaSPaolo Bonzini 8efd8e5aaSPaolo Bonzini #include "libcflat.h" 9efd8e5aaSPaolo Bonzini #include "asm/spinlock.h" 10efd8e5aaSPaolo Bonzini #include "asm/page.h" 11937e2392SPaolo Bonzini #include "asm/io.h" 12dcda215bSPaolo Bonzini #include "alloc.h" 13937e2392SPaolo Bonzini #include "alloc_phys.h" 14937e2392SPaolo Bonzini #include "alloc_page.h" 150d622fcbSClaudio Imbrenda #include <bitops.h> 16937e2392SPaolo Bonzini #include "vmalloc.h" 17efd8e5aaSPaolo Bonzini 183f6fee0dSClaudio Imbrenda #define VM_MAGIC 0x7E57C0DE 193f6fee0dSClaudio Imbrenda 203f6fee0dSClaudio Imbrenda #define GET_METADATA(x) (((struct metadata *)(x)) - 1) 213f6fee0dSClaudio Imbrenda #define GET_MAGIC(x) (*((unsigned long *)(x) - 1)) 223f6fee0dSClaudio Imbrenda 233f6fee0dSClaudio Imbrenda struct metadata { 243f6fee0dSClaudio Imbrenda unsigned long npages; 253f6fee0dSClaudio Imbrenda unsigned long magic; 263f6fee0dSClaudio Imbrenda }; 273f6fee0dSClaudio Imbrenda 28efd8e5aaSPaolo Bonzini static struct spinlock lock; 29efd8e5aaSPaolo Bonzini static void *vfree_top = 0; 30937e2392SPaolo Bonzini static void *page_root; 31efd8e5aaSPaolo Bonzini 320d622fcbSClaudio Imbrenda /* 330d622fcbSClaudio Imbrenda * Allocate a certain number of pages from the virtual address space (without 340d622fcbSClaudio Imbrenda * physical backing). 350d622fcbSClaudio Imbrenda * 360d622fcbSClaudio Imbrenda * nr is the number of pages to allocate 370d622fcbSClaudio Imbrenda * alignment_pages is the alignment of the allocation *in pages* 383f6fee0dSClaudio Imbrenda * metadata indicates whether an extra (unaligned) page needs to be allocated 393f6fee0dSClaudio Imbrenda * right before the main (aligned) allocation. 403f6fee0dSClaudio Imbrenda * 413f6fee0dSClaudio Imbrenda * The return value points to the first allocated virtual page, which will 423f6fee0dSClaudio Imbrenda * be the (potentially unaligned) metadata page if the metadata flag is 433f6fee0dSClaudio Imbrenda * specified. 440d622fcbSClaudio Imbrenda */ 453f6fee0dSClaudio Imbrenda static void *do_alloc_vpages(ulong nr, unsigned int align_order, bool metadata) 46efd8e5aaSPaolo Bonzini { 474aabe7c0SClaudio Imbrenda uintptr_t ptr; 484aabe7c0SClaudio Imbrenda 49efd8e5aaSPaolo Bonzini spin_lock(&lock); 504aabe7c0SClaudio Imbrenda ptr = (uintptr_t)vfree_top; 514aabe7c0SClaudio Imbrenda ptr -= PAGE_SIZE * nr; 520d622fcbSClaudio Imbrenda ptr &= GENMASK_ULL(63, PAGE_SHIFT + align_order); 533f6fee0dSClaudio Imbrenda if (metadata) 543f6fee0dSClaudio Imbrenda ptr -= PAGE_SIZE; 554aabe7c0SClaudio Imbrenda vfree_top = (void *)ptr; 56efd8e5aaSPaolo Bonzini spin_unlock(&lock); 574aabe7c0SClaudio Imbrenda 584aabe7c0SClaudio Imbrenda /* Cannot return vfree_top here, we are outside the lock! */ 594aabe7c0SClaudio Imbrenda return (void *)ptr; 60efd8e5aaSPaolo Bonzini } 61efd8e5aaSPaolo Bonzini 623f6fee0dSClaudio Imbrenda void *alloc_vpages_aligned(ulong nr, unsigned int align_order) 633f6fee0dSClaudio Imbrenda { 643f6fee0dSClaudio Imbrenda return do_alloc_vpages(nr, align_order, false); 653f6fee0dSClaudio Imbrenda } 663f6fee0dSClaudio Imbrenda 670d622fcbSClaudio Imbrenda void *alloc_vpages(ulong nr) 680d622fcbSClaudio Imbrenda { 690d622fcbSClaudio Imbrenda return alloc_vpages_aligned(nr, 0); 700d622fcbSClaudio Imbrenda } 710d622fcbSClaudio Imbrenda 72efd8e5aaSPaolo Bonzini void *alloc_vpage(void) 73efd8e5aaSPaolo Bonzini { 74efd8e5aaSPaolo Bonzini return alloc_vpages(1); 75efd8e5aaSPaolo Bonzini } 76efd8e5aaSPaolo Bonzini 77dcda215bSPaolo Bonzini void *vmap(phys_addr_t phys, size_t size) 78dcda215bSPaolo Bonzini { 79dcda215bSPaolo Bonzini void *mem, *p; 803874bb46SClaudio Imbrenda size_t pages; 81dcda215bSPaolo Bonzini 826ea7326aSClaudio Imbrenda size = PAGE_ALIGN(size); 83dcda215bSPaolo Bonzini pages = size / PAGE_SIZE; 84dcda215bSPaolo Bonzini mem = p = alloc_vpages(pages); 85dcda215bSPaolo Bonzini 86dcda215bSPaolo Bonzini phys &= ~(unsigned long long)(PAGE_SIZE - 1); 87dcda215bSPaolo Bonzini while (pages--) { 88dcda215bSPaolo Bonzini install_page(page_root, phys, p); 89dcda215bSPaolo Bonzini phys += PAGE_SIZE; 90dcda215bSPaolo Bonzini p += PAGE_SIZE; 91dcda215bSPaolo Bonzini } 92dcda215bSPaolo Bonzini return mem; 93dcda215bSPaolo Bonzini } 94dcda215bSPaolo Bonzini 950d622fcbSClaudio Imbrenda /* 963f6fee0dSClaudio Imbrenda * Allocate one page, for an object with specified alignment. 973f6fee0dSClaudio Imbrenda * The resulting pointer will be aligned to the required alignment, but 983f6fee0dSClaudio Imbrenda * intentionally not page-aligned. 993f6fee0dSClaudio Imbrenda * The metadata for single pages allocation is just the magic value, 1003f6fee0dSClaudio Imbrenda * which is placed right before the pointer, like for bigger allocations. 1013f6fee0dSClaudio Imbrenda */ 1023f6fee0dSClaudio Imbrenda static void *vm_alloc_one_page(size_t alignment) 1033f6fee0dSClaudio Imbrenda { 1043f6fee0dSClaudio Imbrenda void *p; 1053f6fee0dSClaudio Imbrenda 1063f6fee0dSClaudio Imbrenda /* this guarantees that there will be space for the magic value */ 1073f6fee0dSClaudio Imbrenda assert(alignment >= sizeof(uintptr_t)); 1083f6fee0dSClaudio Imbrenda assert(alignment < PAGE_SIZE); 1093f6fee0dSClaudio Imbrenda p = alloc_vpage(); 1103f6fee0dSClaudio Imbrenda install_page(page_root, virt_to_phys(alloc_page()), p); 1113f6fee0dSClaudio Imbrenda p = (void *)((uintptr_t)p + alignment); 1123f6fee0dSClaudio Imbrenda /* write the magic value right before the returned address */ 1133f6fee0dSClaudio Imbrenda GET_MAGIC(p) = VM_MAGIC; 1143f6fee0dSClaudio Imbrenda return p; 1153f6fee0dSClaudio Imbrenda } 1163f6fee0dSClaudio Imbrenda 1173f6fee0dSClaudio Imbrenda /* 1180d622fcbSClaudio Imbrenda * Allocate virtual memory, with the specified minimum alignment. 1193f6fee0dSClaudio Imbrenda * If the allocation fits in one page, only one page is allocated. Otherwise 1203f6fee0dSClaudio Imbrenda * enough pages are allocated for the object, plus one to keep metadata 1213f6fee0dSClaudio Imbrenda * information about the allocation. 1220d622fcbSClaudio Imbrenda */ 123dcda215bSPaolo Bonzini static void *vm_memalign(size_t alignment, size_t size) 124dcda215bSPaolo Bonzini { 1253f6fee0dSClaudio Imbrenda struct metadata *m; 1260d622fcbSClaudio Imbrenda phys_addr_t pa; 1273f6fee0dSClaudio Imbrenda uintptr_t p; 1283f6fee0dSClaudio Imbrenda void *mem; 1293f6fee0dSClaudio Imbrenda size_t i; 130dcda215bSPaolo Bonzini 1313f6fee0dSClaudio Imbrenda if (!size) 1323f6fee0dSClaudio Imbrenda return NULL; 1330d622fcbSClaudio Imbrenda assert(is_power_of_2(alignment)); 1340d622fcbSClaudio Imbrenda 1353f6fee0dSClaudio Imbrenda if (alignment < sizeof(uintptr_t)) 1363f6fee0dSClaudio Imbrenda alignment = sizeof(uintptr_t); 1373f6fee0dSClaudio Imbrenda /* it fits in one page, allocate only one page */ 1383f6fee0dSClaudio Imbrenda if (alignment + size <= PAGE_SIZE) 1393f6fee0dSClaudio Imbrenda return vm_alloc_one_page(alignment); 1400d622fcbSClaudio Imbrenda size = PAGE_ALIGN(size) / PAGE_SIZE; 1410d622fcbSClaudio Imbrenda alignment = get_order(PAGE_ALIGN(alignment) / PAGE_SIZE); 1423f6fee0dSClaudio Imbrenda mem = do_alloc_vpages(size, alignment, true); 1433f6fee0dSClaudio Imbrenda p = (uintptr_t)mem; 1443f6fee0dSClaudio Imbrenda /* skip the metadata page */ 1453f6fee0dSClaudio Imbrenda mem = (void *)(p + PAGE_SIZE); 1463f6fee0dSClaudio Imbrenda /* 1473f6fee0dSClaudio Imbrenda * time to actually allocate the physical pages to back our virtual 1483f6fee0dSClaudio Imbrenda * allocation; note that we need to allocate one extra page (for the 1493f6fee0dSClaudio Imbrenda * metadata), hence the <= 1503f6fee0dSClaudio Imbrenda */ 1513f6fee0dSClaudio Imbrenda for (i = 0; i <= size; i++, p += PAGE_SIZE) { 1520d622fcbSClaudio Imbrenda pa = virt_to_phys(alloc_page()); 1530d622fcbSClaudio Imbrenda assert(pa); 1543f6fee0dSClaudio Imbrenda install_page(page_root, pa, (void *)p); 155dcda215bSPaolo Bonzini } 1563f6fee0dSClaudio Imbrenda m = GET_METADATA(mem); 1573f6fee0dSClaudio Imbrenda m->npages = size; 1583f6fee0dSClaudio Imbrenda m->magic = VM_MAGIC; 159dcda215bSPaolo Bonzini return mem; 160dcda215bSPaolo Bonzini } 161dcda215bSPaolo Bonzini 162dcda215bSPaolo Bonzini static void vm_free(void *mem, size_t size) 163dcda215bSPaolo Bonzini { 1643f6fee0dSClaudio Imbrenda struct metadata *m; 1653f6fee0dSClaudio Imbrenda uintptr_t ptr, end; 1663f6fee0dSClaudio Imbrenda 1673f6fee0dSClaudio Imbrenda /* the pointer is not page-aligned, it was a single-page allocation */ 1683f6fee0dSClaudio Imbrenda if (!IS_ALIGNED((uintptr_t)mem, PAGE_SIZE)) { 1693f6fee0dSClaudio Imbrenda assert(GET_MAGIC(mem) == VM_MAGIC); 1703f6fee0dSClaudio Imbrenda ptr = virt_to_pte_phys(page_root, mem) & PAGE_MASK; 1713f6fee0dSClaudio Imbrenda free_page(phys_to_virt(ptr)); 1723f6fee0dSClaudio Imbrenda return; 173dcda215bSPaolo Bonzini } 1743f6fee0dSClaudio Imbrenda 1753f6fee0dSClaudio Imbrenda /* the pointer is page-aligned, it was a multi-page allocation */ 1763f6fee0dSClaudio Imbrenda m = GET_METADATA(mem); 1773f6fee0dSClaudio Imbrenda assert(m->magic == VM_MAGIC); 1783f6fee0dSClaudio Imbrenda assert(m->npages > 0); 1793f6fee0dSClaudio Imbrenda /* free all the pages including the metadata page */ 1803f6fee0dSClaudio Imbrenda ptr = (uintptr_t)mem - PAGE_SIZE; 1813f6fee0dSClaudio Imbrenda end = ptr + m->npages * PAGE_SIZE; 1823f6fee0dSClaudio Imbrenda for ( ; ptr < end; ptr += PAGE_SIZE) 1833f6fee0dSClaudio Imbrenda free_page(phys_to_virt(virt_to_pte_phys(page_root, (void *)ptr))); 1843f6fee0dSClaudio Imbrenda /* free the last one separately to avoid overflow issues */ 1853f6fee0dSClaudio Imbrenda free_page(phys_to_virt(virt_to_pte_phys(page_root, (void *)ptr))); 186dcda215bSPaolo Bonzini } 187dcda215bSPaolo Bonzini 188dcda215bSPaolo Bonzini static struct alloc_ops vmalloc_ops = { 189dcda215bSPaolo Bonzini .memalign = vm_memalign, 190dcda215bSPaolo Bonzini .free = vm_free, 191dcda215bSPaolo Bonzini .align_min = PAGE_SIZE, 192dcda215bSPaolo Bonzini }; 193dcda215bSPaolo Bonzini 19448a0145fSPaolo Bonzini void __attribute__((__weak__)) find_highmem(void) 19548a0145fSPaolo Bonzini { 19648a0145fSPaolo Bonzini } 19748a0145fSPaolo Bonzini 19817b9f93eSClaudio Imbrenda void init_alloc_vpage(void *top) 19917b9f93eSClaudio Imbrenda { 20017b9f93eSClaudio Imbrenda spin_lock(&lock); 20117b9f93eSClaudio Imbrenda assert(alloc_ops != &vmalloc_ops); 20217b9f93eSClaudio Imbrenda vfree_top = top; 20317b9f93eSClaudio Imbrenda spin_unlock(&lock); 20417b9f93eSClaudio Imbrenda } 20517b9f93eSClaudio Imbrenda 206937e2392SPaolo Bonzini void setup_vm() 207937e2392SPaolo Bonzini { 208937e2392SPaolo Bonzini phys_addr_t base, top; 209dcda215bSPaolo Bonzini 210dcda215bSPaolo Bonzini if (alloc_ops == &vmalloc_ops) 211dcda215bSPaolo Bonzini return; 212dcda215bSPaolo Bonzini 213937e2392SPaolo Bonzini phys_alloc_get_unused(&base, &top); 214bf62a925SAndrew Jones assert(base != top || page_alloc_initialized()); 21548a0145fSPaolo Bonzini /* 21648a0145fSPaolo Bonzini * Give low memory immediately to the page allocator, 21748a0145fSPaolo Bonzini * so that it can be used to allocate page tables. 21848a0145fSPaolo Bonzini */ 219bf62a925SAndrew Jones if (!page_alloc_initialized()) { 220*8131e91aSClaudio Imbrenda base = PAGE_ALIGN(base) >> PAGE_SHIFT; 221*8131e91aSClaudio Imbrenda top = top >> PAGE_SHIFT; 222*8131e91aSClaudio Imbrenda page_alloc_init_area(AREA_ANY_NUMBER, base, top); 223*8131e91aSClaudio Imbrenda page_alloc_ops_enable(); 224bf62a925SAndrew Jones } 22548a0145fSPaolo Bonzini 22648a0145fSPaolo Bonzini find_highmem(); 22748a0145fSPaolo Bonzini phys_alloc_get_unused(&base, &top); 228937e2392SPaolo Bonzini page_root = setup_mmu(top); 22948a0145fSPaolo Bonzini if (base != top) { 230*8131e91aSClaudio Imbrenda base = PAGE_ALIGN(base) >> PAGE_SHIFT; 231*8131e91aSClaudio Imbrenda top = top >> PAGE_SHIFT; 232*8131e91aSClaudio Imbrenda page_alloc_init_area(AREA_ANY_NUMBER, base, top); 23348a0145fSPaolo Bonzini } 23448a0145fSPaolo Bonzini 23517b9f93eSClaudio Imbrenda spin_lock(&lock); 23617b9f93eSClaudio Imbrenda assert(alloc_ops != &vmalloc_ops); 237dcda215bSPaolo Bonzini alloc_ops = &vmalloc_ops; 23817b9f93eSClaudio Imbrenda spin_unlock(&lock); 239937e2392SPaolo Bonzini } 240