xref: /kvm-unit-tests/lib/vmalloc.c (revision 3f6fee0d4da4237f9aecf25cf51fa50c3934c7ff)
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 
18*3f6fee0dSClaudio Imbrenda #define VM_MAGIC 0x7E57C0DE
19*3f6fee0dSClaudio Imbrenda 
20*3f6fee0dSClaudio Imbrenda #define GET_METADATA(x) (((struct metadata *)(x)) - 1)
21*3f6fee0dSClaudio Imbrenda #define GET_MAGIC(x) (*((unsigned long *)(x) - 1))
22*3f6fee0dSClaudio Imbrenda 
23*3f6fee0dSClaudio Imbrenda struct metadata {
24*3f6fee0dSClaudio Imbrenda 	unsigned long npages;
25*3f6fee0dSClaudio Imbrenda 	unsigned long magic;
26*3f6fee0dSClaudio Imbrenda };
27*3f6fee0dSClaudio 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*
38*3f6fee0dSClaudio Imbrenda  * metadata indicates whether an extra (unaligned) page needs to be allocated
39*3f6fee0dSClaudio Imbrenda  * right before the main (aligned) allocation.
40*3f6fee0dSClaudio Imbrenda  *
41*3f6fee0dSClaudio Imbrenda  * The return value points to the first allocated virtual page, which will
42*3f6fee0dSClaudio Imbrenda  * be the (potentially unaligned) metadata page if the metadata flag is
43*3f6fee0dSClaudio Imbrenda  * specified.
440d622fcbSClaudio Imbrenda  */
45*3f6fee0dSClaudio 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);
53*3f6fee0dSClaudio Imbrenda 	if (metadata)
54*3f6fee0dSClaudio 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 
62*3f6fee0dSClaudio Imbrenda void *alloc_vpages_aligned(ulong nr, unsigned int align_order)
63*3f6fee0dSClaudio Imbrenda {
64*3f6fee0dSClaudio Imbrenda 	return do_alloc_vpages(nr, align_order, false);
65*3f6fee0dSClaudio Imbrenda }
66*3f6fee0dSClaudio 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 /*
96*3f6fee0dSClaudio Imbrenda  * Allocate one page, for an object with specified alignment.
97*3f6fee0dSClaudio Imbrenda  * The resulting pointer will be aligned to the required alignment, but
98*3f6fee0dSClaudio Imbrenda  * intentionally not page-aligned.
99*3f6fee0dSClaudio Imbrenda  * The metadata for single pages allocation is just the magic value,
100*3f6fee0dSClaudio Imbrenda  * which is placed right before the pointer, like for bigger allocations.
101*3f6fee0dSClaudio Imbrenda  */
102*3f6fee0dSClaudio Imbrenda static void *vm_alloc_one_page(size_t alignment)
103*3f6fee0dSClaudio Imbrenda {
104*3f6fee0dSClaudio Imbrenda 	void *p;
105*3f6fee0dSClaudio Imbrenda 
106*3f6fee0dSClaudio Imbrenda 	/* this guarantees that there will be space for the magic value */
107*3f6fee0dSClaudio Imbrenda 	assert(alignment >= sizeof(uintptr_t));
108*3f6fee0dSClaudio Imbrenda 	assert(alignment < PAGE_SIZE);
109*3f6fee0dSClaudio Imbrenda 	p = alloc_vpage();
110*3f6fee0dSClaudio Imbrenda 	install_page(page_root, virt_to_phys(alloc_page()), p);
111*3f6fee0dSClaudio Imbrenda 	p = (void *)((uintptr_t)p + alignment);
112*3f6fee0dSClaudio Imbrenda 	/* write the magic value right before the returned address */
113*3f6fee0dSClaudio Imbrenda 	GET_MAGIC(p) = VM_MAGIC;
114*3f6fee0dSClaudio Imbrenda 	return p;
115*3f6fee0dSClaudio Imbrenda }
116*3f6fee0dSClaudio Imbrenda 
117*3f6fee0dSClaudio Imbrenda /*
1180d622fcbSClaudio Imbrenda  * Allocate virtual memory, with the specified minimum alignment.
119*3f6fee0dSClaudio Imbrenda  * If the allocation fits in one page, only one page is allocated. Otherwise
120*3f6fee0dSClaudio Imbrenda  * enough pages are allocated for the object, plus one to keep metadata
121*3f6fee0dSClaudio Imbrenda  * information about the allocation.
1220d622fcbSClaudio Imbrenda  */
123dcda215bSPaolo Bonzini static void *vm_memalign(size_t alignment, size_t size)
124dcda215bSPaolo Bonzini {
125*3f6fee0dSClaudio Imbrenda 	struct metadata *m;
1260d622fcbSClaudio Imbrenda 	phys_addr_t pa;
127*3f6fee0dSClaudio Imbrenda 	uintptr_t p;
128*3f6fee0dSClaudio Imbrenda 	void *mem;
129*3f6fee0dSClaudio Imbrenda 	size_t i;
130dcda215bSPaolo Bonzini 
131*3f6fee0dSClaudio Imbrenda 	if (!size)
132*3f6fee0dSClaudio Imbrenda 		return NULL;
1330d622fcbSClaudio Imbrenda 	assert(is_power_of_2(alignment));
1340d622fcbSClaudio Imbrenda 
135*3f6fee0dSClaudio Imbrenda 	if (alignment < sizeof(uintptr_t))
136*3f6fee0dSClaudio Imbrenda 		alignment = sizeof(uintptr_t);
137*3f6fee0dSClaudio Imbrenda 	/* it fits in one page, allocate only one page */
138*3f6fee0dSClaudio Imbrenda 	if (alignment + size <= PAGE_SIZE)
139*3f6fee0dSClaudio 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);
142*3f6fee0dSClaudio Imbrenda 	mem = do_alloc_vpages(size, alignment, true);
143*3f6fee0dSClaudio Imbrenda 	p = (uintptr_t)mem;
144*3f6fee0dSClaudio Imbrenda 	/* skip the metadata page */
145*3f6fee0dSClaudio Imbrenda 	mem = (void *)(p + PAGE_SIZE);
146*3f6fee0dSClaudio Imbrenda 	/*
147*3f6fee0dSClaudio Imbrenda 	 * time to actually allocate the physical pages to back our virtual
148*3f6fee0dSClaudio Imbrenda 	 * allocation; note that we need to allocate one extra page (for the
149*3f6fee0dSClaudio Imbrenda 	 * metadata), hence the <=
150*3f6fee0dSClaudio Imbrenda 	 */
151*3f6fee0dSClaudio Imbrenda 	for (i = 0; i <= size; i++, p += PAGE_SIZE) {
1520d622fcbSClaudio Imbrenda 		pa = virt_to_phys(alloc_page());
1530d622fcbSClaudio Imbrenda 		assert(pa);
154*3f6fee0dSClaudio Imbrenda 		install_page(page_root, pa, (void *)p);
155dcda215bSPaolo Bonzini 	}
156*3f6fee0dSClaudio Imbrenda 	m = GET_METADATA(mem);
157*3f6fee0dSClaudio Imbrenda 	m->npages = size;
158*3f6fee0dSClaudio 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 {
164*3f6fee0dSClaudio Imbrenda 	struct metadata *m;
165*3f6fee0dSClaudio Imbrenda 	uintptr_t ptr, end;
166*3f6fee0dSClaudio Imbrenda 
167*3f6fee0dSClaudio Imbrenda 	/* the pointer is not page-aligned, it was a single-page allocation */
168*3f6fee0dSClaudio Imbrenda 	if (!IS_ALIGNED((uintptr_t)mem, PAGE_SIZE)) {
169*3f6fee0dSClaudio Imbrenda 		assert(GET_MAGIC(mem) == VM_MAGIC);
170*3f6fee0dSClaudio Imbrenda 		ptr = virt_to_pte_phys(page_root, mem) & PAGE_MASK;
171*3f6fee0dSClaudio Imbrenda 		free_page(phys_to_virt(ptr));
172*3f6fee0dSClaudio Imbrenda 		return;
173dcda215bSPaolo Bonzini 	}
174*3f6fee0dSClaudio Imbrenda 
175*3f6fee0dSClaudio Imbrenda 	/* the pointer is page-aligned, it was a multi-page allocation */
176*3f6fee0dSClaudio Imbrenda 	m = GET_METADATA(mem);
177*3f6fee0dSClaudio Imbrenda 	assert(m->magic == VM_MAGIC);
178*3f6fee0dSClaudio Imbrenda 	assert(m->npages > 0);
179*3f6fee0dSClaudio Imbrenda 	/* free all the pages including the metadata page */
180*3f6fee0dSClaudio Imbrenda 	ptr = (uintptr_t)mem - PAGE_SIZE;
181*3f6fee0dSClaudio Imbrenda 	end = ptr + m->npages * PAGE_SIZE;
182*3f6fee0dSClaudio Imbrenda 	for ( ; ptr < end; ptr += PAGE_SIZE)
183*3f6fee0dSClaudio Imbrenda 		free_page(phys_to_virt(virt_to_pte_phys(page_root, (void *)ptr)));
184*3f6fee0dSClaudio Imbrenda 	/* free the last one separately to avoid overflow issues */
185*3f6fee0dSClaudio 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()) {
2206ea7326aSClaudio Imbrenda 		base = PAGE_ALIGN(base);
221937e2392SPaolo Bonzini 		top = top & -PAGE_SIZE;
222937e2392SPaolo Bonzini 		free_pages(phys_to_virt(base), top - base);
223bf62a925SAndrew Jones 	}
22448a0145fSPaolo Bonzini 
22548a0145fSPaolo Bonzini 	find_highmem();
22648a0145fSPaolo Bonzini 	phys_alloc_get_unused(&base, &top);
227937e2392SPaolo Bonzini 	page_root = setup_mmu(top);
22848a0145fSPaolo Bonzini 	if (base != top) {
2296ea7326aSClaudio Imbrenda 		base = PAGE_ALIGN(base);
23048a0145fSPaolo Bonzini 		top = top & -PAGE_SIZE;
23148a0145fSPaolo Bonzini 		free_pages(phys_to_virt(base), top - base);
23248a0145fSPaolo Bonzini 	}
23348a0145fSPaolo Bonzini 
23417b9f93eSClaudio Imbrenda 	spin_lock(&lock);
23517b9f93eSClaudio Imbrenda 	assert(alloc_ops != &vmalloc_ops);
236dcda215bSPaolo Bonzini 	alloc_ops = &vmalloc_ops;
23717b9f93eSClaudio Imbrenda 	spin_unlock(&lock);
238937e2392SPaolo Bonzini }
239