version: 19.02-rc1
[dpdk.git] / lib / librte_eal / common / malloc_elem.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2010-2014 Intel Corporation
3  */
4 #include <inttypes.h>
5 #include <stdint.h>
6 #include <stddef.h>
7 #include <stdio.h>
8 #include <string.h>
9 #include <unistd.h>
10 #include <sys/queue.h>
11
12 #include <rte_memory.h>
13 #include <rte_eal.h>
14 #include <rte_launch.h>
15 #include <rte_per_lcore.h>
16 #include <rte_lcore.h>
17 #include <rte_debug.h>
18 #include <rte_common.h>
19 #include <rte_spinlock.h>
20
21 #include "eal_internal_cfg.h"
22 #include "eal_memalloc.h"
23 #include "malloc_elem.h"
24 #include "malloc_heap.h"
25
26 size_t
27 malloc_elem_find_max_iova_contig(struct malloc_elem *elem, size_t align)
28 {
29         void *cur_page, *contig_seg_start, *page_end, *cur_seg_end;
30         void *data_start, *data_end;
31         rte_iova_t expected_iova;
32         struct rte_memseg *ms;
33         size_t page_sz, cur, max;
34
35         page_sz = (size_t)elem->msl->page_sz;
36         data_start = RTE_PTR_ADD(elem, MALLOC_ELEM_HEADER_LEN);
37         data_end = RTE_PTR_ADD(elem, elem->size - MALLOC_ELEM_TRAILER_LEN);
38         /* segment must start after header and with specified alignment */
39         contig_seg_start = RTE_PTR_ALIGN_CEIL(data_start, align);
40
41         /* return if aligned address is already out of malloc element */
42         if (contig_seg_start > data_end)
43                 return 0;
44
45         /* if we're in IOVA as VA mode, or if we're in legacy mode with
46          * hugepages, all elements are IOVA-contiguous. however, we can only
47          * make these assumptions about internal memory - externally allocated
48          * segments have to be checked.
49          */
50         if (!elem->msl->external &&
51                         (rte_eal_iova_mode() == RTE_IOVA_VA ||
52                                 (internal_config.legacy_mem &&
53                                         rte_eal_has_hugepages())))
54                 return RTE_PTR_DIFF(data_end, contig_seg_start);
55
56         cur_page = RTE_PTR_ALIGN_FLOOR(contig_seg_start, page_sz);
57         ms = rte_mem_virt2memseg(cur_page, elem->msl);
58
59         /* do first iteration outside the loop */
60         page_end = RTE_PTR_ADD(cur_page, page_sz);
61         cur_seg_end = RTE_MIN(page_end, data_end);
62         cur = RTE_PTR_DIFF(cur_seg_end, contig_seg_start) -
63                         MALLOC_ELEM_TRAILER_LEN;
64         max = cur;
65         expected_iova = ms->iova + page_sz;
66         /* memsegs are contiguous in memory */
67         ms++;
68
69         cur_page = RTE_PTR_ADD(cur_page, page_sz);
70
71         while (cur_page < data_end) {
72                 page_end = RTE_PTR_ADD(cur_page, page_sz);
73                 cur_seg_end = RTE_MIN(page_end, data_end);
74
75                 /* reset start of contiguous segment if unexpected iova */
76                 if (ms->iova != expected_iova) {
77                         /* next contiguous segment must start at specified
78                          * alignment.
79                          */
80                         contig_seg_start = RTE_PTR_ALIGN(cur_page, align);
81                         /* new segment start may be on a different page, so find
82                          * the page and skip to next iteration to make sure
83                          * we're not blowing past data end.
84                          */
85                         ms = rte_mem_virt2memseg(contig_seg_start, elem->msl);
86                         cur_page = ms->addr;
87                         /* don't trigger another recalculation */
88                         expected_iova = ms->iova;
89                         continue;
90                 }
91                 /* cur_seg_end ends on a page boundary or on data end. if we're
92                  * looking at data end, then malloc trailer is already included
93                  * in the calculations. if we're looking at page end, then we
94                  * know there's more data past this page and thus there's space
95                  * for malloc element trailer, so don't count it here.
96                  */
97                 cur = RTE_PTR_DIFF(cur_seg_end, contig_seg_start);
98                 /* update max if cur value is bigger */
99                 if (cur > max)
100                         max = cur;
101
102                 /* move to next page */
103                 cur_page = page_end;
104                 expected_iova = ms->iova + page_sz;
105                 /* memsegs are contiguous in memory */
106                 ms++;
107         }
108
109         return max;
110 }
111
112 /*
113  * Initialize a general malloc_elem header structure
114  */
115 void
116 malloc_elem_init(struct malloc_elem *elem, struct malloc_heap *heap,
117                 struct rte_memseg_list *msl, size_t size,
118                 struct malloc_elem *orig_elem, size_t orig_size)
119 {
120         elem->heap = heap;
121         elem->msl = msl;
122         elem->prev = NULL;
123         elem->next = NULL;
124         memset(&elem->free_list, 0, sizeof(elem->free_list));
125         elem->state = ELEM_FREE;
126         elem->size = size;
127         elem->pad = 0;
128         elem->orig_elem = orig_elem;
129         elem->orig_size = orig_size;
130         set_header(elem);
131         set_trailer(elem);
132 }
133
134 void
135 malloc_elem_insert(struct malloc_elem *elem)
136 {
137         struct malloc_elem *prev_elem, *next_elem;
138         struct malloc_heap *heap = elem->heap;
139
140         /* first and last elements must be both NULL or both non-NULL */
141         if ((heap->first == NULL) != (heap->last == NULL)) {
142                 RTE_LOG(ERR, EAL, "Heap is probably corrupt\n");
143                 return;
144         }
145
146         if (heap->first == NULL && heap->last == NULL) {
147                 /* if empty heap */
148                 heap->first = elem;
149                 heap->last = elem;
150                 prev_elem = NULL;
151                 next_elem = NULL;
152         } else if (elem < heap->first) {
153                 /* if lower than start */
154                 prev_elem = NULL;
155                 next_elem = heap->first;
156                 heap->first = elem;
157         } else if (elem > heap->last) {
158                 /* if higher than end */
159                 prev_elem = heap->last;
160                 next_elem = NULL;
161                 heap->last = elem;
162         } else {
163                 /* the new memory is somewhere inbetween start and end */
164                 uint64_t dist_from_start, dist_from_end;
165
166                 dist_from_end = RTE_PTR_DIFF(heap->last, elem);
167                 dist_from_start = RTE_PTR_DIFF(elem, heap->first);
168
169                 /* check which is closer, and find closest list entries */
170                 if (dist_from_start < dist_from_end) {
171                         prev_elem = heap->first;
172                         while (prev_elem->next < elem)
173                                 prev_elem = prev_elem->next;
174                         next_elem = prev_elem->next;
175                 } else {
176                         next_elem = heap->last;
177                         while (next_elem->prev > elem)
178                                 next_elem = next_elem->prev;
179                         prev_elem = next_elem->prev;
180                 }
181         }
182
183         /* insert new element */
184         elem->prev = prev_elem;
185         elem->next = next_elem;
186         if (prev_elem)
187                 prev_elem->next = elem;
188         if (next_elem)
189                 next_elem->prev = elem;
190 }
191
192 /*
193  * Attempt to find enough physically contiguous memory in this block to store
194  * our data. Assume that element has at least enough space to fit in the data,
195  * so we just check the page addresses.
196  */
197 static bool
198 elem_check_phys_contig(const struct rte_memseg_list *msl,
199                 void *start, size_t size)
200 {
201         return eal_memalloc_is_contig(msl, start, size);
202 }
203
204 /*
205  * calculate the starting point of where data of the requested size
206  * and alignment would fit in the current element. If the data doesn't
207  * fit, return NULL.
208  */
209 static void *
210 elem_start_pt(struct malloc_elem *elem, size_t size, unsigned align,
211                 size_t bound, bool contig)
212 {
213         size_t elem_size = elem->size;
214
215         /*
216          * we're allocating from the end, so adjust the size of element by
217          * alignment size.
218          */
219         while (elem_size >= size) {
220                 const size_t bmask = ~(bound - 1);
221                 uintptr_t end_pt = (uintptr_t)elem +
222                                 elem_size - MALLOC_ELEM_TRAILER_LEN;
223                 uintptr_t new_data_start = RTE_ALIGN_FLOOR((end_pt - size),
224                                 align);
225                 uintptr_t new_elem_start;
226
227                 /* check boundary */
228                 if ((new_data_start & bmask) != ((end_pt - 1) & bmask)) {
229                         end_pt = RTE_ALIGN_FLOOR(end_pt, bound);
230                         new_data_start = RTE_ALIGN_FLOOR((end_pt - size),
231                                         align);
232                         end_pt = new_data_start + size;
233
234                         if (((end_pt - 1) & bmask) != (new_data_start & bmask))
235                                 return NULL;
236                 }
237
238                 new_elem_start = new_data_start - MALLOC_ELEM_HEADER_LEN;
239
240                 /* if the new start point is before the exist start,
241                  * it won't fit
242                  */
243                 if (new_elem_start < (uintptr_t)elem)
244                         return NULL;
245
246                 if (contig) {
247                         size_t new_data_size = end_pt - new_data_start;
248
249                         /*
250                          * if physical contiguousness was requested and we
251                          * couldn't fit all data into one physically contiguous
252                          * block, try again with lower addresses.
253                          */
254                         if (!elem_check_phys_contig(elem->msl,
255                                         (void *)new_data_start,
256                                         new_data_size)) {
257                                 elem_size -= align;
258                                 continue;
259                         }
260                 }
261                 return (void *)new_elem_start;
262         }
263         return NULL;
264 }
265
266 /*
267  * use elem_start_pt to determine if we get meet the size and
268  * alignment request from the current element
269  */
270 int
271 malloc_elem_can_hold(struct malloc_elem *elem, size_t size,     unsigned align,
272                 size_t bound, bool contig)
273 {
274         return elem_start_pt(elem, size, align, bound, contig) != NULL;
275 }
276
277 /*
278  * split an existing element into two smaller elements at the given
279  * split_pt parameter.
280  */
281 static void
282 split_elem(struct malloc_elem *elem, struct malloc_elem *split_pt)
283 {
284         struct malloc_elem *next_elem = elem->next;
285         const size_t old_elem_size = (uintptr_t)split_pt - (uintptr_t)elem;
286         const size_t new_elem_size = elem->size - old_elem_size;
287
288         malloc_elem_init(split_pt, elem->heap, elem->msl, new_elem_size,
289                          elem->orig_elem, elem->orig_size);
290         split_pt->prev = elem;
291         split_pt->next = next_elem;
292         if (next_elem)
293                 next_elem->prev = split_pt;
294         else
295                 elem->heap->last = split_pt;
296         elem->next = split_pt;
297         elem->size = old_elem_size;
298         set_trailer(elem);
299 }
300
301 /*
302  * our malloc heap is a doubly linked list, so doubly remove our element.
303  */
304 static void __rte_unused
305 remove_elem(struct malloc_elem *elem)
306 {
307         struct malloc_elem *next, *prev;
308         next = elem->next;
309         prev = elem->prev;
310
311         if (next)
312                 next->prev = prev;
313         else
314                 elem->heap->last = prev;
315         if (prev)
316                 prev->next = next;
317         else
318                 elem->heap->first = next;
319
320         elem->prev = NULL;
321         elem->next = NULL;
322 }
323
324 static int
325 next_elem_is_adjacent(struct malloc_elem *elem)
326 {
327         return elem->next == RTE_PTR_ADD(elem, elem->size) &&
328                         elem->next->msl == elem->msl &&
329                         (!internal_config.match_allocations ||
330                          elem->orig_elem == elem->next->orig_elem);
331 }
332
333 static int
334 prev_elem_is_adjacent(struct malloc_elem *elem)
335 {
336         return elem == RTE_PTR_ADD(elem->prev, elem->prev->size) &&
337                         elem->prev->msl == elem->msl &&
338                         (!internal_config.match_allocations ||
339                          elem->orig_elem == elem->prev->orig_elem);
340 }
341
342 /*
343  * Given an element size, compute its freelist index.
344  * We free an element into the freelist containing similarly-sized elements.
345  * We try to allocate elements starting with the freelist containing
346  * similarly-sized elements, and if necessary, we search freelists
347  * containing larger elements.
348  *
349  * Example element size ranges for a heap with five free lists:
350  *   heap->free_head[0] - (0   , 2^8]
351  *   heap->free_head[1] - (2^8 , 2^10]
352  *   heap->free_head[2] - (2^10 ,2^12]
353  *   heap->free_head[3] - (2^12, 2^14]
354  *   heap->free_head[4] - (2^14, MAX_SIZE]
355  */
356 size_t
357 malloc_elem_free_list_index(size_t size)
358 {
359 #define MALLOC_MINSIZE_LOG2   8
360 #define MALLOC_LOG2_INCREMENT 2
361
362         size_t log2;
363         size_t index;
364
365         if (size <= (1UL << MALLOC_MINSIZE_LOG2))
366                 return 0;
367
368         /* Find next power of 2 >= size. */
369         log2 = sizeof(size) * 8 - __builtin_clzl(size-1);
370
371         /* Compute freelist index, based on log2(size). */
372         index = (log2 - MALLOC_MINSIZE_LOG2 + MALLOC_LOG2_INCREMENT - 1) /
373                 MALLOC_LOG2_INCREMENT;
374
375         return index <= RTE_HEAP_NUM_FREELISTS-1?
376                 index: RTE_HEAP_NUM_FREELISTS-1;
377 }
378
379 /*
380  * Add the specified element to its heap's free list.
381  */
382 void
383 malloc_elem_free_list_insert(struct malloc_elem *elem)
384 {
385         size_t idx;
386
387         idx = malloc_elem_free_list_index(elem->size - MALLOC_ELEM_HEADER_LEN);
388         elem->state = ELEM_FREE;
389         LIST_INSERT_HEAD(&elem->heap->free_head[idx], elem, free_list);
390 }
391
392 /*
393  * Remove the specified element from its heap's free list.
394  */
395 void
396 malloc_elem_free_list_remove(struct malloc_elem *elem)
397 {
398         LIST_REMOVE(elem, free_list);
399 }
400
401 /*
402  * reserve a block of data in an existing malloc_elem. If the malloc_elem
403  * is much larger than the data block requested, we split the element in two.
404  * This function is only called from malloc_heap_alloc so parameter checking
405  * is not done here, as it's done there previously.
406  */
407 struct malloc_elem *
408 malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
409                 size_t bound, bool contig)
410 {
411         struct malloc_elem *new_elem = elem_start_pt(elem, size, align, bound,
412                         contig);
413         const size_t old_elem_size = (uintptr_t)new_elem - (uintptr_t)elem;
414         const size_t trailer_size = elem->size - old_elem_size - size -
415                 MALLOC_ELEM_OVERHEAD;
416
417         malloc_elem_free_list_remove(elem);
418
419         if (trailer_size > MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
420                 /* split it, too much free space after elem */
421                 struct malloc_elem *new_free_elem =
422                                 RTE_PTR_ADD(new_elem, size + MALLOC_ELEM_OVERHEAD);
423
424                 split_elem(elem, new_free_elem);
425                 malloc_elem_free_list_insert(new_free_elem);
426
427                 if (elem == elem->heap->last)
428                         elem->heap->last = new_free_elem;
429         }
430
431         if (old_elem_size < MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
432                 /* don't split it, pad the element instead */
433                 elem->state = ELEM_BUSY;
434                 elem->pad = old_elem_size;
435
436                 /* put a dummy header in padding, to point to real element header */
437                 if (elem->pad > 0) { /* pad will be at least 64-bytes, as everything
438                                      * is cache-line aligned */
439                         new_elem->pad = elem->pad;
440                         new_elem->state = ELEM_PAD;
441                         new_elem->size = elem->size - elem->pad;
442                         set_header(new_elem);
443                 }
444
445                 return new_elem;
446         }
447
448         /* we are going to split the element in two. The original element
449          * remains free, and the new element is the one allocated.
450          * Re-insert original element, in case its new size makes it
451          * belong on a different list.
452          */
453         split_elem(elem, new_elem);
454         new_elem->state = ELEM_BUSY;
455         malloc_elem_free_list_insert(elem);
456
457         return new_elem;
458 }
459
460 /*
461  * join two struct malloc_elem together. elem1 and elem2 must
462  * be contiguous in memory.
463  */
464 static inline void
465 join_elem(struct malloc_elem *elem1, struct malloc_elem *elem2)
466 {
467         struct malloc_elem *next = elem2->next;
468         elem1->size += elem2->size;
469         if (next)
470                 next->prev = elem1;
471         else
472                 elem1->heap->last = elem1;
473         elem1->next = next;
474 }
475
476 struct malloc_elem *
477 malloc_elem_join_adjacent_free(struct malloc_elem *elem)
478 {
479         /*
480          * check if next element exists, is adjacent and is free, if so join
481          * with it, need to remove from free list.
482          */
483         if (elem->next != NULL && elem->next->state == ELEM_FREE &&
484                         next_elem_is_adjacent(elem)) {
485                 void *erase;
486                 size_t erase_len;
487
488                 /* we will want to erase the trailer and header */
489                 erase = RTE_PTR_SUB(elem->next, MALLOC_ELEM_TRAILER_LEN);
490                 erase_len = MALLOC_ELEM_OVERHEAD + elem->next->pad;
491
492                 /* remove from free list, join to this one */
493                 malloc_elem_free_list_remove(elem->next);
494                 join_elem(elem, elem->next);
495
496                 /* erase header, trailer and pad */
497                 memset(erase, 0, erase_len);
498         }
499
500         /*
501          * check if prev element exists, is adjacent and is free, if so join
502          * with it, need to remove from free list.
503          */
504         if (elem->prev != NULL && elem->prev->state == ELEM_FREE &&
505                         prev_elem_is_adjacent(elem)) {
506                 struct malloc_elem *new_elem;
507                 void *erase;
508                 size_t erase_len;
509
510                 /* we will want to erase trailer and header */
511                 erase = RTE_PTR_SUB(elem, MALLOC_ELEM_TRAILER_LEN);
512                 erase_len = MALLOC_ELEM_OVERHEAD + elem->pad;
513
514                 /* remove from free list, join to this one */
515                 malloc_elem_free_list_remove(elem->prev);
516
517                 new_elem = elem->prev;
518                 join_elem(new_elem, elem);
519
520                 /* erase header, trailer and pad */
521                 memset(erase, 0, erase_len);
522
523                 elem = new_elem;
524         }
525
526         return elem;
527 }
528
529 /*
530  * free a malloc_elem block by adding it to the free list. If the
531  * blocks either immediately before or immediately after newly freed block
532  * are also free, the blocks are merged together.
533  */
534 struct malloc_elem *
535 malloc_elem_free(struct malloc_elem *elem)
536 {
537         void *ptr;
538         size_t data_len;
539
540         ptr = RTE_PTR_ADD(elem, MALLOC_ELEM_HEADER_LEN);
541         data_len = elem->size - MALLOC_ELEM_OVERHEAD;
542
543         elem = malloc_elem_join_adjacent_free(elem);
544
545         malloc_elem_free_list_insert(elem);
546
547         elem->pad = 0;
548
549         /* decrease heap's count of allocated elements */
550         elem->heap->alloc_count--;
551
552         memset(ptr, 0, data_len);
553
554         return elem;
555 }
556
557 /* assume all checks were already done */
558 void
559 malloc_elem_hide_region(struct malloc_elem *elem, void *start, size_t len)
560 {
561         struct malloc_elem *hide_start, *hide_end, *prev, *next;
562         size_t len_before, len_after;
563
564         hide_start = start;
565         hide_end = RTE_PTR_ADD(start, len);
566
567         prev = elem->prev;
568         next = elem->next;
569
570         /* we cannot do anything with non-adjacent elements */
571         if (next && next_elem_is_adjacent(elem)) {
572                 len_after = RTE_PTR_DIFF(next, hide_end);
573                 if (len_after >= MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
574                         /* split after */
575                         split_elem(elem, hide_end);
576
577                         malloc_elem_free_list_insert(hide_end);
578                 } else if (len_after > 0) {
579                         RTE_LOG(ERR, EAL, "Unaligned element, heap is probably corrupt\n");
580                         return;
581                 }
582         }
583
584         /* we cannot do anything with non-adjacent elements */
585         if (prev && prev_elem_is_adjacent(elem)) {
586                 len_before = RTE_PTR_DIFF(hide_start, elem);
587                 if (len_before >= MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
588                         /* split before */
589                         split_elem(elem, hide_start);
590
591                         prev = elem;
592                         elem = hide_start;
593
594                         malloc_elem_free_list_insert(prev);
595                 } else if (len_before > 0) {
596                         RTE_LOG(ERR, EAL, "Unaligned element, heap is probably corrupt\n");
597                         return;
598                 }
599         }
600
601         remove_elem(elem);
602 }
603
604 /*
605  * attempt to resize a malloc_elem by expanding into any free space
606  * immediately after it in memory.
607  */
608 int
609 malloc_elem_resize(struct malloc_elem *elem, size_t size)
610 {
611         const size_t new_size = size + elem->pad + MALLOC_ELEM_OVERHEAD;
612
613         /* if we request a smaller size, then always return ok */
614         if (elem->size >= new_size)
615                 return 0;
616
617         /* check if there is a next element, it's free and adjacent */
618         if (!elem->next || elem->next->state != ELEM_FREE ||
619                         !next_elem_is_adjacent(elem))
620                 return -1;
621         if (elem->size + elem->next->size < new_size)
622                 return -1;
623
624         /* we now know the element fits, so remove from free list,
625          * join the two
626          */
627         malloc_elem_free_list_remove(elem->next);
628         join_elem(elem, elem->next);
629
630         if (elem->size - new_size >= MIN_DATA_SIZE + MALLOC_ELEM_OVERHEAD) {
631                 /* now we have a big block together. Lets cut it down a bit, by splitting */
632                 struct malloc_elem *split_pt = RTE_PTR_ADD(elem, new_size);
633                 split_pt = RTE_PTR_ALIGN_CEIL(split_pt, RTE_CACHE_LINE_SIZE);
634                 split_elem(elem, split_pt);
635                 malloc_elem_free_list_insert(split_pt);
636         }
637         return 0;
638 }
639
640 static inline const char *
641 elem_state_to_str(enum elem_state state)
642 {
643         switch (state) {
644         case ELEM_PAD:
645                 return "PAD";
646         case ELEM_BUSY:
647                 return "BUSY";
648         case ELEM_FREE:
649                 return "FREE";
650         }
651         return "ERROR";
652 }
653
654 void
655 malloc_elem_dump(const struct malloc_elem *elem, FILE *f)
656 {
657         fprintf(f, "Malloc element at %p (%s)\n", elem,
658                         elem_state_to_str(elem->state));
659         fprintf(f, "  len: 0x%zx pad: 0x%" PRIx32 "\n", elem->size, elem->pad);
660         fprintf(f, "  prev: %p next: %p\n", elem->prev, elem->next);
661 }