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