New upstream version 18.08
[deb_dpdk.git] / lib / librte_eal / common / malloc_elem.c
index 0cadc8a..e0a8ed1 100644 (file)
@@ -1,10 +1,12 @@
 /* SPDX-License-Identifier: BSD-3-Clause
  * Copyright(c) 2010-2014 Intel Corporation
  */
+#include <inttypes.h>
 #include <stdint.h>
 #include <stddef.h>
 #include <stdio.h>
 #include <string.h>
+#include <unistd.h>
 #include <sys/queue.h>
 
 #include <rte_memory.h>
 #include <rte_common.h>
 #include <rte_spinlock.h>
 
+#include "eal_internal_cfg.h"
+#include "eal_memalloc.h"
 #include "malloc_elem.h"
 #include "malloc_heap.h"
 
-#define MIN_DATA_SIZE (RTE_CACHE_LINE_SIZE)
+size_t
+malloc_elem_find_max_iova_contig(struct malloc_elem *elem, size_t align)
+{
+       void *cur_page, *contig_seg_start, *page_end, *cur_seg_end;
+       void *data_start, *data_end;
+       rte_iova_t expected_iova;
+       struct rte_memseg *ms;
+       size_t page_sz, cur, max;
+
+       page_sz = (size_t)elem->msl->page_sz;
+       data_start = RTE_PTR_ADD(elem, MALLOC_ELEM_HEADER_LEN);
+       data_end = RTE_PTR_ADD(elem, elem->size - MALLOC_ELEM_TRAILER_LEN);
+       /* segment must start after header and with specified alignment */
+       contig_seg_start = RTE_PTR_ALIGN_CEIL(data_start, align);
+
+       /* if we're in IOVA as VA mode, or if we're in legacy mode with
+        * hugepages, all elements are IOVA-contiguous.
+        */
+       if (rte_eal_iova_mode() == RTE_IOVA_VA ||
+                       (internal_config.legacy_mem && rte_eal_has_hugepages()))
+               return RTE_PTR_DIFF(data_end, contig_seg_start);
+
+       cur_page = RTE_PTR_ALIGN_FLOOR(contig_seg_start, page_sz);
+       ms = rte_mem_virt2memseg(cur_page, elem->msl);
+
+       /* do first iteration outside the loop */
+       page_end = RTE_PTR_ADD(cur_page, page_sz);
+       cur_seg_end = RTE_MIN(page_end, data_end);
+       cur = RTE_PTR_DIFF(cur_seg_end, contig_seg_start) -
+                       MALLOC_ELEM_TRAILER_LEN;
+       max = cur;
+       expected_iova = ms->iova + page_sz;
+       /* memsegs are contiguous in memory */
+       ms++;
+
+       cur_page = RTE_PTR_ADD(cur_page, page_sz);
+
+       while (cur_page < data_end) {
+               page_end = RTE_PTR_ADD(cur_page, page_sz);
+               cur_seg_end = RTE_MIN(page_end, data_end);
+
+               /* reset start of contiguous segment if unexpected iova */
+               if (ms->iova != expected_iova) {
+                       /* next contiguous segment must start at specified
+                        * alignment.
+                        */
+                       contig_seg_start = RTE_PTR_ALIGN(cur_page, align);
+                       /* new segment start may be on a different page, so find
+                        * the page and skip to next iteration to make sure
+                        * we're not blowing past data end.
+                        */
+                       ms = rte_mem_virt2memseg(contig_seg_start, elem->msl);
+                       cur_page = ms->addr;
+                       /* don't trigger another recalculation */
+                       expected_iova = ms->iova;
+                       continue;
+               }
+               /* cur_seg_end ends on a page boundary or on data end. if we're
+                * looking at data end, then malloc trailer is already included
+                * in the calculations. if we're looking at page end, then we
+                * know there's more data past this page and thus there's space
+                * for malloc element trailer, so don't count it here.
+                */
+               cur = RTE_PTR_DIFF(cur_seg_end, contig_seg_start);
+               /* update max if cur value is bigger */
+               if (cur > max)
+                       max = cur;
+
+               /* move to next page */
+               cur_page = page_end;
+               expected_iova = ms->iova + page_sz;
+               /* memsegs are contiguous in memory */
+               ms++;
+       }
+
+       return max;
+}
 
 /*
  * Initialize a general malloc_elem header structure
  */
 void
-malloc_elem_init(struct malloc_elem *elem,
-               struct malloc_heap *heap, const struct rte_memseg *ms, size_t size)
+malloc_elem_init(struct malloc_elem *elem, struct malloc_heap *heap,
+               struct rte_memseg_list *msl, size_t size)
 {
        elem->heap = heap;
-       elem->ms = ms;
+       elem->msl = msl;
        elem->prev = NULL;
+       elem->next = NULL;
        memset(&elem->free_list, 0, sizeof(elem->free_list));
        elem->state = ELEM_FREE;
        elem->size = size;
@@ -39,15 +120,74 @@ malloc_elem_init(struct malloc_elem *elem,
        set_trailer(elem);
 }
 
+void
+malloc_elem_insert(struct malloc_elem *elem)
+{
+       struct malloc_elem *prev_elem, *next_elem;
+       struct malloc_heap *heap = elem->heap;
+
+       /* first and last elements must be both NULL or both non-NULL */
+       if ((heap->first == NULL) != (heap->last == NULL)) {
+               RTE_LOG(ERR, EAL, "Heap is probably corrupt\n");
+               return;
+       }
+
+       if (heap->first == NULL && heap->last == NULL) {
+               /* if empty heap */
+               heap->first = elem;
+               heap->last = elem;
+               prev_elem = NULL;
+               next_elem = NULL;
+       } else if (elem < heap->first) {
+               /* if lower than start */
+               prev_elem = NULL;
+               next_elem = heap->first;
+               heap->first = elem;
+       } else if (elem > heap->last) {
+               /* if higher than end */
+               prev_elem = heap->last;
+               next_elem = NULL;
+               heap->last = elem;
+       } else {
+               /* the new memory is somewhere inbetween start and end */
+               uint64_t dist_from_start, dist_from_end;
+
+               dist_from_end = RTE_PTR_DIFF(heap->last, elem);
+               dist_from_start = RTE_PTR_DIFF(elem, heap->first);
+
+               /* check which is closer, and find closest list entries */
+               if (dist_from_start < dist_from_end) {
+                       prev_elem = heap->first;
+                       while (prev_elem->next < elem)
+                               prev_elem = prev_elem->next;
+                       next_elem = prev_elem->next;
+               } else {
+                       next_elem = heap->last;
+                       while (next_elem->prev > elem)
+                               next_elem = next_elem->prev;
+                       prev_elem = next_elem->prev;
+               }
+       }
+
+       /* insert new element */
+       elem->prev = prev_elem;
+       elem->next = next_elem;
+       if (prev_elem)
+               prev_elem->next = elem;
+       if (next_elem)
+               next_elem->prev = elem;
+}
+
 /*
- * Initialize a dummy malloc_elem header for the end-of-memseg marker
+ * Attempt to find enough physically contiguous memory in this block to store
+ * our data. Assume that element has at least enough space to fit in the data,
+ * so we just check the page addresses.
  */
-void
-malloc_elem_mkend(struct malloc_elem *elem, struct malloc_elem *prev)
+static bool
+elem_check_phys_contig(const struct rte_memseg_list *msl,
+               void *start, size_t size)
 {
-       malloc_elem_init(elem, prev->heap, prev->ms, 0);
-       elem->prev = prev;
-       elem->state = ELEM_BUSY; /* mark busy so its never merged */
+       return eal_memalloc_is_contig(msl, start, size);
 }
 
 /*
@@ -57,27 +197,59 @@ malloc_elem_mkend(struct malloc_elem *elem, struct malloc_elem *prev)
  */
 static void *
 elem_start_pt(struct malloc_elem *elem, size_t size, unsigned align,
-               size_t bound)
+               size_t bound, bool contig)
 {
-       const size_t bmask = ~(bound - 1);
-       uintptr_t end_pt = (uintptr_t)elem +
-                       elem->size - MALLOC_ELEM_TRAILER_LEN;
-       uintptr_t new_data_start = RTE_ALIGN_FLOOR((end_pt - size), align);
-       uintptr_t new_elem_start;
-
-       /* check boundary */
-       if ((new_data_start & bmask) != ((end_pt - 1) & bmask)) {
-               end_pt = RTE_ALIGN_FLOOR(end_pt, bound);
-               new_data_start = RTE_ALIGN_FLOOR((end_pt - size), align);
-               end_pt = new_data_start + size;
-               if (((end_pt - 1) & bmask) != (new_data_start & bmask))
-                       return NULL;
-       }
+       size_t elem_size = elem->size;
+
+       /*
+        * we're allocating from the end, so adjust the size of element by
+        * alignment size.
+        */
+       while (elem_size >= size) {
+               const size_t bmask = ~(bound - 1);
+               uintptr_t end_pt = (uintptr_t)elem +
+                               elem_size - MALLOC_ELEM_TRAILER_LEN;
+               uintptr_t new_data_start = RTE_ALIGN_FLOOR((end_pt - size),
+                               align);
+               uintptr_t new_elem_start;
+
+               /* check boundary */
+               if ((new_data_start & bmask) != ((end_pt - 1) & bmask)) {
+                       end_pt = RTE_ALIGN_FLOOR(end_pt, bound);
+                       new_data_start = RTE_ALIGN_FLOOR((end_pt - size),
+                                       align);
+                       end_pt = new_data_start + size;
+
+                       if (((end_pt - 1) & bmask) != (new_data_start & bmask))
+                               return NULL;
+               }
+
+               new_elem_start = new_data_start - MALLOC_ELEM_HEADER_LEN;
 
-       new_elem_start = new_data_start - MALLOC_ELEM_HEADER_LEN;
+               /* if the new start point is before the exist start,
+                * it won't fit
+                */
+               if (new_elem_start < (uintptr_t)elem)
+                       return NULL;
 
-       /* if the new start point is before the exist start, it won't fit */
-       return (new_elem_start < (uintptr_t)elem) ? NULL : (void *)new_elem_start;
+               if (contig) {
+                       size_t new_data_size = end_pt - new_data_start;
+
+                       /*
+                        * if physical contiguousness was requested and we
+                        * couldn't fit all data into one physically contiguous
+                        * block, try again with lower addresses.
+                        */
+                       if (!elem_check_phys_contig(elem->msl,
+                                       (void *)new_data_start,
+                                       new_data_size)) {
+                               elem_size -= align;
+                               continue;
+                       }
+               }
+               return (void *)new_elem_start;
+       }
+       return NULL;
 }
 
 /*
@@ -86,9 +258,9 @@ elem_start_pt(struct malloc_elem *elem, size_t size, unsigned align,
  */
 int
 malloc_elem_can_hold(struct malloc_elem *elem, size_t size,    unsigned align,
-               size_t bound)
+               size_t bound, bool contig)
 {
-       return elem_start_pt(elem, size, align, bound) != NULL;
+       return elem_start_pt(elem, size, align, bound, contig) != NULL;
 }
 
 /*
@@ -98,17 +270,57 @@ malloc_elem_can_hold(struct malloc_elem *elem, size_t size,        unsigned align,
 static void
 split_elem(struct malloc_elem *elem, struct malloc_elem *split_pt)
 {
-       struct malloc_elem *next_elem = RTE_PTR_ADD(elem, elem->size);
+       struct malloc_elem *next_elem = elem->next;
        const size_t old_elem_size = (uintptr_t)split_pt - (uintptr_t)elem;
        const size_t new_elem_size = elem->size - old_elem_size;
 
-       malloc_elem_init(split_pt, elem->heap, elem->ms, new_elem_size);
+       malloc_elem_init(split_pt, elem->heap, elem->msl, new_elem_size);
        split_pt->prev = elem;
-       next_elem->prev = split_pt;
+       split_pt->next = next_elem;
+       if (next_elem)
+               next_elem->prev = split_pt;
+       else
+               elem->heap->last = split_pt;
+       elem->next = split_pt;
        elem->size = old_elem_size;
        set_trailer(elem);
 }
 
+/*
+ * our malloc heap is a doubly linked list, so doubly remove our element.
+ */
+static void __rte_unused
+remove_elem(struct malloc_elem *elem)
+{
+       struct malloc_elem *next, *prev;
+       next = elem->next;
+       prev = elem->prev;
+
+       if (next)
+               next->prev = prev;
+       else
+               elem->heap->last = prev;
+       if (prev)
+               prev->next = next;
+       else
+               elem->heap->first = next;
+
+       elem->prev = NULL;
+       elem->next = NULL;
+}
+
+static int
+next_elem_is_adjacent(struct malloc_elem *elem)
+{
+       return elem->next == RTE_PTR_ADD(elem, elem->size);
+}
+
+static int
+prev_elem_is_adjacent(struct malloc_elem *elem)
+{
+       return elem == RTE_PTR_ADD(elem->prev, elem->prev->size);
+}
+
 /*
  * Given an element size, compute its freelist index.
  * We free an element into the freelist containing similarly-sized elements.
@@ -162,8 +374,8 @@ malloc_elem_free_list_insert(struct malloc_elem *elem)
 /*
  * Remove the specified element from its heap's free list.
  */
-static void
-elem_free_list_remove(struct malloc_elem *elem)
+void
+malloc_elem_free_list_remove(struct malloc_elem *elem)
 {
        LIST_REMOVE(elem, free_list);
 }
@@ -176,14 +388,15 @@ elem_free_list_remove(struct malloc_elem *elem)
  */
 struct malloc_elem *
 malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
-               size_t bound)
+               size_t bound, bool contig)
 {
-       struct malloc_elem *new_elem = elem_start_pt(elem, size, align, bound);
+       struct malloc_elem *new_elem = elem_start_pt(elem, size, align, bound,
+                       contig);
        const size_t old_elem_size = (uintptr_t)new_elem - (uintptr_t)elem;
        const size_t trailer_size = elem->size - old_elem_size - size -
                MALLOC_ELEM_OVERHEAD;
 
-       elem_free_list_remove(elem);
+       malloc_elem_free_list_remove(elem);
 
        if (trailer_size > MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
                /* split it, too much free space after elem */
@@ -192,6 +405,9 @@ malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
 
                split_elem(elem, new_free_elem);
                malloc_elem_free_list_insert(new_free_elem);
+
+               if (elem == elem->heap->last)
+                       elem->heap->last = new_free_elem;
        }
 
        if (old_elem_size < MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
@@ -230,9 +446,66 @@ malloc_elem_alloc(struct malloc_elem *elem, size_t size, unsigned align,
 static inline void
 join_elem(struct malloc_elem *elem1, struct malloc_elem *elem2)
 {
-       struct malloc_elem *next = RTE_PTR_ADD(elem2, elem2->size);
+       struct malloc_elem *next = elem2->next;
        elem1->size += elem2->size;
-       next->prev = elem1;
+       if (next)
+               next->prev = elem1;
+       else
+               elem1->heap->last = elem1;
+       elem1->next = next;
+}
+
+struct malloc_elem *
+malloc_elem_join_adjacent_free(struct malloc_elem *elem)
+{
+       /*
+        * check if next element exists, is adjacent and is free, if so join
+        * with it, need to remove from free list.
+        */
+       if (elem->next != NULL && elem->next->state == ELEM_FREE &&
+                       next_elem_is_adjacent(elem)) {
+               void *erase;
+               size_t erase_len;
+
+               /* we will want to erase the trailer and header */
+               erase = RTE_PTR_SUB(elem->next, MALLOC_ELEM_TRAILER_LEN);
+               erase_len = MALLOC_ELEM_OVERHEAD + elem->next->pad;
+
+               /* remove from free list, join to this one */
+               malloc_elem_free_list_remove(elem->next);
+               join_elem(elem, elem->next);
+
+               /* erase header, trailer and pad */
+               memset(erase, 0, erase_len);
+       }
+
+       /*
+        * check if prev element exists, is adjacent and is free, if so join
+        * with it, need to remove from free list.
+        */
+       if (elem->prev != NULL && elem->prev->state == ELEM_FREE &&
+                       prev_elem_is_adjacent(elem)) {
+               struct malloc_elem *new_elem;
+               void *erase;
+               size_t erase_len;
+
+               /* we will want to erase trailer and header */
+               erase = RTE_PTR_SUB(elem, MALLOC_ELEM_TRAILER_LEN);
+               erase_len = MALLOC_ELEM_OVERHEAD + elem->pad;
+
+               /* remove from free list, join to this one */
+               malloc_elem_free_list_remove(elem->prev);
+
+               new_elem = elem->prev;
+               join_elem(new_elem, elem);
+
+               /* erase header, trailer and pad */
+               memset(erase, 0, erase_len);
+
+               elem = new_elem;
+       }
+
+       return elem;
 }
 
 /*
@@ -240,43 +513,74 @@ join_elem(struct malloc_elem *elem1, struct malloc_elem *elem2)
  * blocks either immediately before or immediately after newly freed block
  * are also free, the blocks are merged together.
  */
-int
+struct malloc_elem *
 malloc_elem_free(struct malloc_elem *elem)
 {
-       if (!malloc_elem_cookies_ok(elem) || elem->state != ELEM_BUSY)
-               return -1;
+       void *ptr;
+       size_t data_len;
 
-       rte_spinlock_lock(&(elem->heap->lock));
-       size_t sz = elem->size - sizeof(*elem) - MALLOC_ELEM_TRAILER_LEN;
-       uint8_t *ptr = (uint8_t *)&elem[1];
-       struct malloc_elem *next = RTE_PTR_ADD(elem, elem->size);
-       if (next->state == ELEM_FREE){
-               /* remove from free list, join to this one */
-               elem_free_list_remove(next);
-               join_elem(elem, next);
-               sz += (sizeof(*elem) + MALLOC_ELEM_TRAILER_LEN);
-       }
+       ptr = RTE_PTR_ADD(elem, MALLOC_ELEM_HEADER_LEN);
+       data_len = elem->size - MALLOC_ELEM_OVERHEAD;
+
+       elem = malloc_elem_join_adjacent_free(elem);
 
-       /* check if previous element is free, if so join with it and return,
-        * need to re-insert in free list, as that element's size is changing
-        */
-       if (elem->prev != NULL && elem->prev->state == ELEM_FREE) {
-               elem_free_list_remove(elem->prev);
-               join_elem(elem->prev, elem);
-               sz += (sizeof(*elem) + MALLOC_ELEM_TRAILER_LEN);
-               ptr -= (sizeof(*elem) + MALLOC_ELEM_TRAILER_LEN);
-               elem = elem->prev;
-       }
        malloc_elem_free_list_insert(elem);
 
+       elem->pad = 0;
+
        /* decrease heap's count of allocated elements */
        elem->heap->alloc_count--;
 
-       memset(ptr, 0, sz);
+       memset(ptr, 0, data_len);
 
-       rte_spinlock_unlock(&(elem->heap->lock));
+       return elem;
+}
 
-       return 0;
+/* assume all checks were already done */
+void
+malloc_elem_hide_region(struct malloc_elem *elem, void *start, size_t len)
+{
+       struct malloc_elem *hide_start, *hide_end, *prev, *next;
+       size_t len_before, len_after;
+
+       hide_start = start;
+       hide_end = RTE_PTR_ADD(start, len);
+
+       prev = elem->prev;
+       next = elem->next;
+
+       /* we cannot do anything with non-adjacent elements */
+       if (next && next_elem_is_adjacent(elem)) {
+               len_after = RTE_PTR_DIFF(next, hide_end);
+               if (len_after >= MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
+                       /* split after */
+                       split_elem(elem, hide_end);
+
+                       malloc_elem_free_list_insert(hide_end);
+               } else if (len_after > 0) {
+                       RTE_LOG(ERR, EAL, "Unaligned element, heap is probably corrupt\n");
+                       return;
+               }
+       }
+
+       /* we cannot do anything with non-adjacent elements */
+       if (prev && prev_elem_is_adjacent(elem)) {
+               len_before = RTE_PTR_DIFF(hide_start, elem);
+               if (len_before >= MALLOC_ELEM_OVERHEAD + MIN_DATA_SIZE) {
+                       /* split before */
+                       split_elem(elem, hide_start);
+
+                       prev = elem;
+                       elem = hide_start;
+
+                       malloc_elem_free_list_insert(prev);
+               } else if (len_before > 0) {
+                       RTE_LOG(ERR, EAL, "Unaligned element, heap is probably corrupt\n");
+                       return;
+               }
+       }
+
+       remove_elem(elem);
 }
 
 /*
@@ -287,22 +591,23 @@ int
 malloc_elem_resize(struct malloc_elem *elem, size_t size)
 {
        const size_t new_size = size + elem->pad + MALLOC_ELEM_OVERHEAD;
+
        /* if we request a smaller size, then always return ok */
        if (elem->size >= new_size)
                return 0;
 
-       struct malloc_elem *next = RTE_PTR_ADD(elem, elem->size);
-       rte_spinlock_lock(&elem->heap->lock);
-       if (next ->state != ELEM_FREE)
-               goto err_return;
-       if (elem->size + next->size < new_size)
-               goto err_return;
+       /* check if there is a next element, it's free and adjacent */
+       if (!elem->next || elem->next->state != ELEM_FREE ||
+                       !next_elem_is_adjacent(elem))
+               return -1;
+       if (elem->size + elem->next->size < new_size)
+               return -1;
 
        /* we now know the element fits, so remove from free list,
         * join the two
         */
-       elem_free_list_remove(next);
-       join_elem(elem, next);
+       malloc_elem_free_list_remove(elem->next);
+       join_elem(elem, elem->next);
 
        if (elem->size - new_size >= MIN_DATA_SIZE + MALLOC_ELEM_OVERHEAD) {
                /* now we have a big block together. Lets cut it down a bit, by splitting */
@@ -311,10 +616,28 @@ malloc_elem_resize(struct malloc_elem *elem, size_t size)
                split_elem(elem, split_pt);
                malloc_elem_free_list_insert(split_pt);
        }
-       rte_spinlock_unlock(&elem->heap->lock);
        return 0;
+}
 
-err_return:
-       rte_spinlock_unlock(&elem->heap->lock);
-       return -1;
+static inline const char *
+elem_state_to_str(enum elem_state state)
+{
+       switch (state) {
+       case ELEM_PAD:
+               return "PAD";
+       case ELEM_BUSY:
+               return "BUSY";
+       case ELEM_FREE:
+               return "FREE";
+       }
+       return "ERROR";
+}
+
+void
+malloc_elem_dump(const struct malloc_elem *elem, FILE *f)
+{
+       fprintf(f, "Malloc element at %p (%s)\n", elem,
+                       elem_state_to_str(elem->state));
+       fprintf(f, "  len: 0x%zx pad: 0x%" PRIx32 "\n", elem->size, elem->pad);
+       fprintf(f, "  prev: %p next: %p\n", elem->prev, elem->next);
 }