1292be092SMarc Marí /*
2292be092SMarc Marí * libqos malloc support
3292be092SMarc Marí *
4292be092SMarc Marí * Copyright (c) 2014
5292be092SMarc Marí *
6292be092SMarc Marí * Author:
7292be092SMarc Marí * John Snow <jsnow@redhat.com>
8292be092SMarc Marí *
9292be092SMarc Marí * This work is licensed under the terms of the GNU GPL, version 2 or later.
10292be092SMarc Marí * See the COPYING file in the top-level directory.
11292be092SMarc Marí */
12292be092SMarc Marí
13681c28a3SPeter Maydell #include "qemu/osdep.h"
14*b243c73cSXuzhou Cheng #include "libqos-malloc.h"
1587776ab7SPaolo Bonzini #include "qemu/host-utils.h"
16292be092SMarc Marí
17f6f363c1SJohn Snow typedef struct MemBlock {
18f6f363c1SJohn Snow QTAILQ_ENTRY(MemBlock) MLIST_ENTNAME;
19f6f363c1SJohn Snow uint64_t size;
20f6f363c1SJohn Snow uint64_t addr;
21f6f363c1SJohn Snow } MemBlock;
22f6f363c1SJohn Snow
23f6f363c1SJohn Snow #define DEFAULT_PAGE_SIZE 4096
24f6f363c1SJohn Snow
mlist_delete(MemList * list,MemBlock * node)25292be092SMarc Marí static void mlist_delete(MemList *list, MemBlock *node)
26292be092SMarc Marí {
27292be092SMarc Marí g_assert(list && node);
28292be092SMarc Marí QTAILQ_REMOVE(list, node, MLIST_ENTNAME);
29292be092SMarc Marí g_free(node);
30292be092SMarc Marí }
31292be092SMarc Marí
mlist_find_key(MemList * head,uint64_t addr)32292be092SMarc Marí static MemBlock *mlist_find_key(MemList *head, uint64_t addr)
33292be092SMarc Marí {
34292be092SMarc Marí MemBlock *node;
35292be092SMarc Marí QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
36292be092SMarc Marí if (node->addr == addr) {
37292be092SMarc Marí return node;
38292be092SMarc Marí }
39292be092SMarc Marí }
40292be092SMarc Marí return NULL;
41292be092SMarc Marí }
42292be092SMarc Marí
mlist_find_space(MemList * head,uint64_t size)43292be092SMarc Marí static MemBlock *mlist_find_space(MemList *head, uint64_t size)
44292be092SMarc Marí {
45292be092SMarc Marí MemBlock *node;
46292be092SMarc Marí
47292be092SMarc Marí QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
48292be092SMarc Marí if (node->size >= size) {
49292be092SMarc Marí return node;
50292be092SMarc Marí }
51292be092SMarc Marí }
52292be092SMarc Marí return NULL;
53292be092SMarc Marí }
54292be092SMarc Marí
mlist_sort_insert(MemList * head,MemBlock * insr)55292be092SMarc Marí static MemBlock *mlist_sort_insert(MemList *head, MemBlock *insr)
56292be092SMarc Marí {
57292be092SMarc Marí MemBlock *node;
58292be092SMarc Marí g_assert(head && insr);
59292be092SMarc Marí
60292be092SMarc Marí QTAILQ_FOREACH(node, head, MLIST_ENTNAME) {
61292be092SMarc Marí if (insr->addr < node->addr) {
62292be092SMarc Marí QTAILQ_INSERT_BEFORE(node, insr, MLIST_ENTNAME);
63292be092SMarc Marí return insr;
64292be092SMarc Marí }
65292be092SMarc Marí }
66292be092SMarc Marí
67292be092SMarc Marí QTAILQ_INSERT_TAIL(head, insr, MLIST_ENTNAME);
68292be092SMarc Marí return insr;
69292be092SMarc Marí }
70292be092SMarc Marí
mlist_boundary(MemBlock * node)71292be092SMarc Marí static inline uint64_t mlist_boundary(MemBlock *node)
72292be092SMarc Marí {
73292be092SMarc Marí return node->size + node->addr;
74292be092SMarc Marí }
75292be092SMarc Marí
mlist_join(MemList * head,MemBlock * left,MemBlock * right)76292be092SMarc Marí static MemBlock *mlist_join(MemList *head, MemBlock *left, MemBlock *right)
77292be092SMarc Marí {
78292be092SMarc Marí g_assert(head && left && right);
79292be092SMarc Marí
80292be092SMarc Marí left->size += right->size;
81292be092SMarc Marí mlist_delete(head, right);
82292be092SMarc Marí return left;
83292be092SMarc Marí }
84292be092SMarc Marí
mlist_coalesce(MemList * head,MemBlock * node)85292be092SMarc Marí static void mlist_coalesce(MemList *head, MemBlock *node)
86292be092SMarc Marí {
87292be092SMarc Marí g_assert(node);
88292be092SMarc Marí MemBlock *left;
89292be092SMarc Marí MemBlock *right;
90292be092SMarc Marí char merge;
91292be092SMarc Marí
92292be092SMarc Marí do {
93292be092SMarc Marí merge = 0;
94eae3eb3eSPaolo Bonzini left = QTAILQ_PREV(node, MLIST_ENTNAME);
95292be092SMarc Marí right = QTAILQ_NEXT(node, MLIST_ENTNAME);
96292be092SMarc Marí
97292be092SMarc Marí /* clowns to the left of me */
98292be092SMarc Marí if (left && mlist_boundary(left) == node->addr) {
99292be092SMarc Marí node = mlist_join(head, left, node);
100292be092SMarc Marí merge = 1;
101292be092SMarc Marí }
102292be092SMarc Marí
103292be092SMarc Marí /* jokers to the right */
104292be092SMarc Marí if (right && mlist_boundary(node) == right->addr) {
105292be092SMarc Marí node = mlist_join(head, node, right);
106292be092SMarc Marí merge = 1;
107292be092SMarc Marí }
108292be092SMarc Marí
109292be092SMarc Marí } while (merge);
110292be092SMarc Marí }
111292be092SMarc Marí
mlist_new(uint64_t addr,uint64_t size)112f6f363c1SJohn Snow static MemBlock *mlist_new(uint64_t addr, uint64_t size)
113f6f363c1SJohn Snow {
114f6f363c1SJohn Snow MemBlock *block;
115f6f363c1SJohn Snow
116f6f363c1SJohn Snow if (!size) {
117f6f363c1SJohn Snow return NULL;
118f6f363c1SJohn Snow }
119790bbb97SMarc-André Lureau block = g_new0(MemBlock, 1);
120f6f363c1SJohn Snow
121f6f363c1SJohn Snow block->addr = addr;
122f6f363c1SJohn Snow block->size = size;
123f6f363c1SJohn Snow
124f6f363c1SJohn Snow return block;
125f6f363c1SJohn Snow }
126f6f363c1SJohn Snow
mlist_fulfill(QGuestAllocator * s,MemBlock * freenode,uint64_t size)127292be092SMarc Marí static uint64_t mlist_fulfill(QGuestAllocator *s, MemBlock *freenode,
128292be092SMarc Marí uint64_t size)
129292be092SMarc Marí {
130292be092SMarc Marí uint64_t addr;
131292be092SMarc Marí MemBlock *usednode;
132292be092SMarc Marí
133292be092SMarc Marí g_assert(freenode);
134292be092SMarc Marí g_assert_cmpint(freenode->size, >=, size);
135292be092SMarc Marí
136292be092SMarc Marí addr = freenode->addr;
137292be092SMarc Marí if (freenode->size == size) {
138292be092SMarc Marí /* re-use this freenode as our used node */
139085248aeSJohn Snow QTAILQ_REMOVE(s->free, freenode, MLIST_ENTNAME);
140292be092SMarc Marí usednode = freenode;
141292be092SMarc Marí } else {
142292be092SMarc Marí /* adjust the free node and create a new used node */
143292be092SMarc Marí freenode->addr += size;
144292be092SMarc Marí freenode->size -= size;
145292be092SMarc Marí usednode = mlist_new(addr, size);
146292be092SMarc Marí }
147292be092SMarc Marí
148085248aeSJohn Snow mlist_sort_insert(s->used, usednode);
149292be092SMarc Marí return addr;
150292be092SMarc Marí }
151292be092SMarc Marí
152292be092SMarc Marí /* To assert the correctness of the list.
153292be092SMarc Marí * Used only if ALLOC_PARANOID is set. */
mlist_check(QGuestAllocator * s)154292be092SMarc Marí static void mlist_check(QGuestAllocator *s)
155292be092SMarc Marí {
156292be092SMarc Marí MemBlock *node;
157292be092SMarc Marí uint64_t addr = s->start > 0 ? s->start - 1 : 0;
158292be092SMarc Marí uint64_t next = s->start;
159292be092SMarc Marí
160085248aeSJohn Snow QTAILQ_FOREACH(node, s->free, MLIST_ENTNAME) {
161292be092SMarc Marí g_assert_cmpint(node->addr, >, addr);
162292be092SMarc Marí g_assert_cmpint(node->addr, >=, next);
163292be092SMarc Marí addr = node->addr;
164292be092SMarc Marí next = node->addr + node->size;
165292be092SMarc Marí }
166292be092SMarc Marí
167292be092SMarc Marí addr = s->start > 0 ? s->start - 1 : 0;
168292be092SMarc Marí next = s->start;
169085248aeSJohn Snow QTAILQ_FOREACH(node, s->used, MLIST_ENTNAME) {
170292be092SMarc Marí g_assert_cmpint(node->addr, >, addr);
171292be092SMarc Marí g_assert_cmpint(node->addr, >=, next);
172292be092SMarc Marí addr = node->addr;
173292be092SMarc Marí next = node->addr + node->size;
174292be092SMarc Marí }
175292be092SMarc Marí }
176292be092SMarc Marí
mlist_alloc(QGuestAllocator * s,uint64_t size)177292be092SMarc Marí static uint64_t mlist_alloc(QGuestAllocator *s, uint64_t size)
178292be092SMarc Marí {
179292be092SMarc Marí MemBlock *node;
180292be092SMarc Marí
181085248aeSJohn Snow node = mlist_find_space(s->free, size);
182292be092SMarc Marí if (!node) {
183292be092SMarc Marí fprintf(stderr, "Out of guest memory.\n");
184292be092SMarc Marí g_assert_not_reached();
185292be092SMarc Marí }
186292be092SMarc Marí return mlist_fulfill(s, node, size);
187292be092SMarc Marí }
188292be092SMarc Marí
mlist_free(QGuestAllocator * s,uint64_t addr)189292be092SMarc Marí static void mlist_free(QGuestAllocator *s, uint64_t addr)
190292be092SMarc Marí {
191292be092SMarc Marí MemBlock *node;
192292be092SMarc Marí
193292be092SMarc Marí if (addr == 0) {
194292be092SMarc Marí return;
195292be092SMarc Marí }
196292be092SMarc Marí
197085248aeSJohn Snow node = mlist_find_key(s->used, addr);
198292be092SMarc Marí if (!node) {
199292be092SMarc Marí fprintf(stderr, "Error: no record found for an allocation at "
200292be092SMarc Marí "0x%016" PRIx64 ".\n",
201292be092SMarc Marí addr);
202292be092SMarc Marí g_assert_not_reached();
203292be092SMarc Marí }
204292be092SMarc Marí
205292be092SMarc Marí /* Rip it out of the used list and re-insert back into the free list. */
206085248aeSJohn Snow QTAILQ_REMOVE(s->used, node, MLIST_ENTNAME);
207085248aeSJohn Snow mlist_sort_insert(s->free, node);
208085248aeSJohn Snow mlist_coalesce(s->free, node);
209292be092SMarc Marí }
210292be092SMarc Marí
211292be092SMarc Marí /*
212292be092SMarc Marí * Mostly for valgrind happiness, but it does offer
213292be092SMarc Marí * a chokepoint for debugging guest memory leaks, too.
214292be092SMarc Marí */
alloc_destroy(QGuestAllocator * allocator)215eb5937baSPaolo Bonzini void alloc_destroy(QGuestAllocator *allocator)
216292be092SMarc Marí {
217292be092SMarc Marí MemBlock *node;
218292be092SMarc Marí MemBlock *tmp;
219292be092SMarc Marí QAllocOpts mask;
220292be092SMarc Marí
221292be092SMarc Marí /* Check for guest leaks, and destroy the list. */
222085248aeSJohn Snow QTAILQ_FOREACH_SAFE(node, allocator->used, MLIST_ENTNAME, tmp) {
223292be092SMarc Marí if (allocator->opts & (ALLOC_LEAK_WARN | ALLOC_LEAK_ASSERT)) {
224292be092SMarc Marí fprintf(stderr, "guest malloc leak @ 0x%016" PRIx64 "; "
225292be092SMarc Marí "size 0x%016" PRIx64 ".\n",
226292be092SMarc Marí node->addr, node->size);
227292be092SMarc Marí }
228292be092SMarc Marí if (allocator->opts & (ALLOC_LEAK_ASSERT)) {
229292be092SMarc Marí g_assert_not_reached();
230292be092SMarc Marí }
231292be092SMarc Marí g_free(node);
232292be092SMarc Marí }
233292be092SMarc Marí
234292be092SMarc Marí /* If we have previously asserted that there are no leaks, then there
235292be092SMarc Marí * should be only one node here with a specific address and size. */
236292be092SMarc Marí mask = ALLOC_LEAK_ASSERT | ALLOC_PARANOID;
237085248aeSJohn Snow QTAILQ_FOREACH_SAFE(node, allocator->free, MLIST_ENTNAME, tmp) {
238292be092SMarc Marí if ((allocator->opts & mask) == mask) {
239292be092SMarc Marí if ((node->addr != allocator->start) ||
240292be092SMarc Marí (node->size != allocator->end - allocator->start)) {
241292be092SMarc Marí fprintf(stderr, "Free list is corrupted.\n");
242292be092SMarc Marí g_assert_not_reached();
243292be092SMarc Marí }
244292be092SMarc Marí }
245292be092SMarc Marí
246292be092SMarc Marí g_free(node);
247292be092SMarc Marí }
248292be092SMarc Marí
249085248aeSJohn Snow g_free(allocator->used);
250085248aeSJohn Snow g_free(allocator->free);
251292be092SMarc Marí }
252292be092SMarc Marí
guest_alloc(QGuestAllocator * allocator,size_t size)253292be092SMarc Marí uint64_t guest_alloc(QGuestAllocator *allocator, size_t size)
254292be092SMarc Marí {
255292be092SMarc Marí uint64_t rsize = size;
256292be092SMarc Marí uint64_t naddr;
257292be092SMarc Marí
258b1b66c3bSJohn Snow if (!size) {
259b1b66c3bSJohn Snow return 0;
260b1b66c3bSJohn Snow }
261b1b66c3bSJohn Snow
262292be092SMarc Marí rsize += (allocator->page_size - 1);
263292be092SMarc Marí rsize &= -allocator->page_size;
264292be092SMarc Marí g_assert_cmpint((allocator->start + rsize), <=, allocator->end);
265292be092SMarc Marí g_assert_cmpint(rsize, >=, size);
266292be092SMarc Marí
267292be092SMarc Marí naddr = mlist_alloc(allocator, rsize);
268292be092SMarc Marí if (allocator->opts & ALLOC_PARANOID) {
269292be092SMarc Marí mlist_check(allocator);
270292be092SMarc Marí }
271292be092SMarc Marí
272292be092SMarc Marí return naddr;
273292be092SMarc Marí }
274292be092SMarc Marí
guest_free(QGuestAllocator * allocator,uint64_t addr)275292be092SMarc Marí void guest_free(QGuestAllocator *allocator, uint64_t addr)
276292be092SMarc Marí {
27728452758SFam Zheng if (!addr) {
27828452758SFam Zheng return;
27928452758SFam Zheng }
280292be092SMarc Marí mlist_free(allocator, addr);
281292be092SMarc Marí if (allocator->opts & ALLOC_PARANOID) {
282292be092SMarc Marí mlist_check(allocator);
283292be092SMarc Marí }
284292be092SMarc Marí }
285af77f2cdSJohn Snow
alloc_init(QGuestAllocator * s,QAllocOpts opts,uint64_t start,uint64_t end,size_t page_size)286eb5937baSPaolo Bonzini void alloc_init(QGuestAllocator *s, QAllocOpts opts,
287eb5937baSPaolo Bonzini uint64_t start, uint64_t end,
288eb5937baSPaolo Bonzini size_t page_size)
289af77f2cdSJohn Snow {
290af77f2cdSJohn Snow MemBlock *node;
291af77f2cdSJohn Snow
292eb5937baSPaolo Bonzini s->opts = opts;
293af77f2cdSJohn Snow s->start = start;
294af77f2cdSJohn Snow s->end = end;
295af77f2cdSJohn Snow
296790bbb97SMarc-André Lureau s->used = g_new(MemList, 1);
297790bbb97SMarc-André Lureau s->free = g_new(MemList, 1);
298085248aeSJohn Snow QTAILQ_INIT(s->used);
299085248aeSJohn Snow QTAILQ_INIT(s->free);
300af77f2cdSJohn Snow
301af77f2cdSJohn Snow node = mlist_new(s->start, s->end - s->start);
302085248aeSJohn Snow QTAILQ_INSERT_HEAD(s->free, node, MLIST_ENTNAME);
303af77f2cdSJohn Snow
304eb5937baSPaolo Bonzini s->page_size = page_size;
305f6f363c1SJohn Snow }
306259342d3SJohn Snow
alloc_set_flags(QGuestAllocator * allocator,QAllocOpts opts)307259342d3SJohn Snow void alloc_set_flags(QGuestAllocator *allocator, QAllocOpts opts)
308259342d3SJohn Snow {
309259342d3SJohn Snow allocator->opts |= opts;
310259342d3SJohn Snow }
311085248aeSJohn Snow
migrate_allocator(QGuestAllocator * src,QGuestAllocator * dst)312085248aeSJohn Snow void migrate_allocator(QGuestAllocator *src,
313085248aeSJohn Snow QGuestAllocator *dst)
314085248aeSJohn Snow {
315085248aeSJohn Snow MemBlock *node, *tmp;
316085248aeSJohn Snow MemList *tmpused, *tmpfree;
317085248aeSJohn Snow
318085248aeSJohn Snow /* The general memory layout should be equivalent,
319085248aeSJohn Snow * though opts can differ. */
320085248aeSJohn Snow g_assert_cmphex(src->start, ==, dst->start);
321085248aeSJohn Snow g_assert_cmphex(src->end, ==, dst->end);
322085248aeSJohn Snow
323085248aeSJohn Snow /* Destroy (silently, regardless of options) the dest-list: */
324085248aeSJohn Snow QTAILQ_FOREACH_SAFE(node, dst->used, MLIST_ENTNAME, tmp) {
325085248aeSJohn Snow g_free(node);
326085248aeSJohn Snow }
327085248aeSJohn Snow QTAILQ_FOREACH_SAFE(node, dst->free, MLIST_ENTNAME, tmp) {
328085248aeSJohn Snow g_free(node);
329085248aeSJohn Snow }
330085248aeSJohn Snow
331085248aeSJohn Snow tmpused = dst->used;
332085248aeSJohn Snow tmpfree = dst->free;
333085248aeSJohn Snow
334085248aeSJohn Snow /* Inherit the lists of the source allocator: */
335085248aeSJohn Snow dst->used = src->used;
336085248aeSJohn Snow dst->free = src->free;
337085248aeSJohn Snow
338085248aeSJohn Snow /* Source is now re-initialized, the source memory is 'invalid' now: */
339085248aeSJohn Snow src->used = tmpused;
340085248aeSJohn Snow src->free = tmpfree;
341085248aeSJohn Snow QTAILQ_INIT(src->used);
342085248aeSJohn Snow QTAILQ_INIT(src->free);
343085248aeSJohn Snow node = mlist_new(src->start, src->end - src->start);
344085248aeSJohn Snow QTAILQ_INSERT_HEAD(src->free, node, MLIST_ENTNAME);
345085248aeSJohn Snow }
346