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 */
do_alloc_vpages(ulong nr,unsigned int align_order,bool metadata)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
alloc_vpages_aligned(ulong nr,unsigned int align_order)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
alloc_vpages(ulong nr)670d622fcbSClaudio Imbrenda void *alloc_vpages(ulong nr)
680d622fcbSClaudio Imbrenda {
690d622fcbSClaudio Imbrenda return alloc_vpages_aligned(nr, 0);
700d622fcbSClaudio Imbrenda }
710d622fcbSClaudio Imbrenda
alloc_vpage(void)72efd8e5aaSPaolo Bonzini void *alloc_vpage(void)
73efd8e5aaSPaolo Bonzini {
74efd8e5aaSPaolo Bonzini return alloc_vpages(1);
75efd8e5aaSPaolo Bonzini }
76efd8e5aaSPaolo Bonzini
vmap(phys_addr_t phys,size_t size)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 */
vm_alloc_one_page(size_t alignment)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 */
vm_memalign(size_t alignment,size_t size)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
vm_free(void * mem)162f90ddba3SClaudio Imbrenda static void vm_free(void *mem)
163dcda215bSPaolo Bonzini {
1643f6fee0dSClaudio Imbrenda struct metadata *m;
16512513346SClaudio Imbrenda uintptr_t ptr, page, i;
1663f6fee0dSClaudio Imbrenda
16712513346SClaudio Imbrenda if (!mem)
16812513346SClaudio Imbrenda return;
1693f6fee0dSClaudio Imbrenda /* the pointer is not page-aligned, it was a single-page allocation */
1703f6fee0dSClaudio Imbrenda if (!IS_ALIGNED((uintptr_t)mem, PAGE_SIZE)) {
1713f6fee0dSClaudio Imbrenda assert(GET_MAGIC(mem) == VM_MAGIC);
17212513346SClaudio Imbrenda page = virt_to_pte_phys(page_root, mem) & PAGE_MASK;
17312513346SClaudio Imbrenda assert(page);
17412513346SClaudio Imbrenda free_page(phys_to_virt(page));
1753f6fee0dSClaudio Imbrenda return;
176dcda215bSPaolo Bonzini }
1773f6fee0dSClaudio Imbrenda
1783f6fee0dSClaudio Imbrenda /* the pointer is page-aligned, it was a multi-page allocation */
1793f6fee0dSClaudio Imbrenda m = GET_METADATA(mem);
1803f6fee0dSClaudio Imbrenda assert(m->magic == VM_MAGIC);
1813f6fee0dSClaudio Imbrenda assert(m->npages > 0);
18212513346SClaudio Imbrenda assert(m->npages < BIT_ULL(BITS_PER_LONG - PAGE_SHIFT));
1833f6fee0dSClaudio Imbrenda /* free all the pages including the metadata page */
18412513346SClaudio Imbrenda ptr = (uintptr_t)m & PAGE_MASK;
18512513346SClaudio Imbrenda for (i = 0 ; i < m->npages + 1; i++, ptr += PAGE_SIZE) {
18612513346SClaudio Imbrenda page = virt_to_pte_phys(page_root, (void *)ptr) & PAGE_MASK;
18712513346SClaudio Imbrenda assert(page);
18812513346SClaudio Imbrenda free_page(phys_to_virt(page));
18912513346SClaudio Imbrenda }
190dcda215bSPaolo Bonzini }
191dcda215bSPaolo Bonzini
192dcda215bSPaolo Bonzini static struct alloc_ops vmalloc_ops = {
193dcda215bSPaolo Bonzini .memalign = vm_memalign,
194dcda215bSPaolo Bonzini .free = vm_free,
195dcda215bSPaolo Bonzini };
196dcda215bSPaolo Bonzini
find_highmem(void)19748a0145fSPaolo Bonzini void __attribute__((__weak__)) find_highmem(void)
19848a0145fSPaolo Bonzini {
19948a0145fSPaolo Bonzini }
20048a0145fSPaolo Bonzini
init_alloc_vpage(void * top)20117b9f93eSClaudio Imbrenda void init_alloc_vpage(void *top)
20217b9f93eSClaudio Imbrenda {
20317b9f93eSClaudio Imbrenda spin_lock(&lock);
20417b9f93eSClaudio Imbrenda assert(alloc_ops != &vmalloc_ops);
20517b9f93eSClaudio Imbrenda vfree_top = top;
20617b9f93eSClaudio Imbrenda spin_unlock(&lock);
20717b9f93eSClaudio Imbrenda }
20817b9f93eSClaudio Imbrenda
vm_available(void)209*b9289d76SNicholas Piggin bool __attribute__((__weak__)) vm_available(void)
210*b9289d76SNicholas Piggin {
211*b9289d76SNicholas Piggin return true;
212*b9289d76SNicholas Piggin }
213*b9289d76SNicholas Piggin
__setup_vm(void * opaque)2140a5b31d2SSean Christopherson void __setup_vm(void *opaque)
215937e2392SPaolo Bonzini {
216937e2392SPaolo Bonzini phys_addr_t base, top;
217dcda215bSPaolo Bonzini
218*b9289d76SNicholas Piggin assert_msg(vm_available(), "Virtual memory not available. Must check vm_available() before calling setup_vm()");
219*b9289d76SNicholas Piggin
220dcda215bSPaolo Bonzini if (alloc_ops == &vmalloc_ops)
221dcda215bSPaolo Bonzini return;
222dcda215bSPaolo Bonzini
223937e2392SPaolo Bonzini phys_alloc_get_unused(&base, &top);
224bf62a925SAndrew Jones assert(base != top || page_alloc_initialized());
22548a0145fSPaolo Bonzini /*
22648a0145fSPaolo Bonzini * Give low memory immediately to the page allocator,
22748a0145fSPaolo Bonzini * so that it can be used to allocate page tables.
22848a0145fSPaolo Bonzini */
229bf62a925SAndrew Jones if (!page_alloc_initialized()) {
2308131e91aSClaudio Imbrenda base = PAGE_ALIGN(base) >> PAGE_SHIFT;
2318131e91aSClaudio Imbrenda top = top >> PAGE_SHIFT;
2328131e91aSClaudio Imbrenda page_alloc_init_area(AREA_ANY_NUMBER, base, top);
2338131e91aSClaudio Imbrenda page_alloc_ops_enable();
234bf62a925SAndrew Jones }
23548a0145fSPaolo Bonzini
23648a0145fSPaolo Bonzini find_highmem();
23748a0145fSPaolo Bonzini phys_alloc_get_unused(&base, &top);
2380a5b31d2SSean Christopherson page_root = setup_mmu(top, opaque);
23948a0145fSPaolo Bonzini if (base != top) {
2408131e91aSClaudio Imbrenda base = PAGE_ALIGN(base) >> PAGE_SHIFT;
2418131e91aSClaudio Imbrenda top = top >> PAGE_SHIFT;
2428131e91aSClaudio Imbrenda page_alloc_init_area(AREA_ANY_NUMBER, base, top);
24348a0145fSPaolo Bonzini }
24448a0145fSPaolo Bonzini
24517b9f93eSClaudio Imbrenda spin_lock(&lock);
24617b9f93eSClaudio Imbrenda assert(alloc_ops != &vmalloc_ops);
247dcda215bSPaolo Bonzini alloc_ops = &vmalloc_ops;
24817b9f93eSClaudio Imbrenda spin_unlock(&lock);
249937e2392SPaolo Bonzini }
2500a5b31d2SSean Christopherson
setup_vm(void)2510a5b31d2SSean Christopherson void setup_vm(void)
2520a5b31d2SSean Christopherson {
2530a5b31d2SSean Christopherson __setup_vm(NULL);
2540a5b31d2SSean Christopherson }
255