2017-12-19 15:49:03 +00:00
|
|
|
/* SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
* Copyright(c) 2010-2014 Intel Corporation
|
2015-07-15 17:32:20 +01:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/queue.h>
|
|
|
|
|
|
|
|
#include <rte_memcpy.h>
|
|
|
|
#include <rte_memory.h>
|
|
|
|
#include <rte_eal.h>
|
|
|
|
#include <rte_eal_memconfig.h>
|
|
|
|
#include <rte_branch_prediction.h>
|
|
|
|
#include <rte_debug.h>
|
|
|
|
#include <rte_launch.h>
|
|
|
|
#include <rte_per_lcore.h>
|
|
|
|
#include <rte_lcore.h>
|
|
|
|
#include <rte_common.h>
|
|
|
|
#include <rte_spinlock.h>
|
|
|
|
|
|
|
|
#include <rte_malloc.h>
|
|
|
|
#include "malloc_elem.h"
|
|
|
|
#include "malloc_heap.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* Free the memory space back to heap */
|
|
|
|
void rte_free(void *addr)
|
|
|
|
{
|
|
|
|
if (addr == NULL) return;
|
2018-04-11 13:29:37 +01:00
|
|
|
if (malloc_heap_free(malloc_elem_from_data(addr)) < 0)
|
2018-04-11 13:29:44 +01:00
|
|
|
RTE_LOG(ERR, EAL, "Error: Invalid memory\n");
|
2015-07-15 17:32:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate memory on specified heap.
|
|
|
|
*/
|
|
|
|
void *
|
2018-04-11 13:29:45 +01:00
|
|
|
rte_malloc_socket(const char *type, size_t size, unsigned int align,
|
|
|
|
int socket_arg)
|
2015-07-15 17:32:20 +01:00
|
|
|
{
|
|
|
|
/* return NULL if size is 0 or alignment is not power-of-2 */
|
|
|
|
if (size == 0 || (align && !rte_is_power_of_2(align)))
|
|
|
|
return NULL;
|
|
|
|
|
2015-07-15 17:32:21 +01:00
|
|
|
if (!rte_eal_has_hugepages())
|
|
|
|
socket_arg = SOCKET_ID_ANY;
|
|
|
|
|
2015-07-15 17:32:20 +01:00
|
|
|
/* Check socket parameter */
|
malloc: enable memory hotplug support
This set of changes enables rte_malloc to allocate and free memory
as needed. Currently, it is disabled because legacy mem mode is
enabled unconditionally.
The way it works is, first malloc checks if there is enough memory
already allocated to satisfy user's request. If there isn't, we try
and allocate more memory. The reverse happens with free - we free
an element, check its size (including free element merging due to
adjacency) and see if it's bigger than hugepage size and that its
start and end span a hugepage or more. Then we remove the area from
malloc heap (adjusting element lengths where appropriate), and
deallocate the page.
For legacy mode, runtime alloc/free of pages is disabled.
It is worth noting that memseg lists are being sorted by page size,
and that we try our best to satisfy user's request. That is, if
the user requests an element from a 2MB page memory, we will check
if we can satisfy that request from existing memory, if not we try
and allocate more 2MB pages. If that fails and user also specified
a "size is hint" flag, we then check other page sizes and try to
allocate from there. If that fails too, then, depending on flags,
we may try allocating from other sockets. In other words, we try
our best to give the user what they asked for, but going to other
sockets is last resort - first we try to allocate more memory on
the same socket.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
Tested-by: Santosh Shukla <santosh.shukla@caviumnetworks.com>
Tested-by: Hemant Agrawal <hemant.agrawal@nxp.com>
Tested-by: Gowrishankar Muthukrishnan <gowrishankar.m@linux.vnet.ibm.com>
2018-04-11 13:30:35 +01:00
|
|
|
if (socket_arg >= RTE_MAX_NUMA_NODES)
|
2015-07-15 17:32:20 +01:00
|
|
|
return NULL;
|
|
|
|
|
malloc: enable memory hotplug support
This set of changes enables rte_malloc to allocate and free memory
as needed. Currently, it is disabled because legacy mem mode is
enabled unconditionally.
The way it works is, first malloc checks if there is enough memory
already allocated to satisfy user's request. If there isn't, we try
and allocate more memory. The reverse happens with free - we free
an element, check its size (including free element merging due to
adjacency) and see if it's bigger than hugepage size and that its
start and end span a hugepage or more. Then we remove the area from
malloc heap (adjusting element lengths where appropriate), and
deallocate the page.
For legacy mode, runtime alloc/free of pages is disabled.
It is worth noting that memseg lists are being sorted by page size,
and that we try our best to satisfy user's request. That is, if
the user requests an element from a 2MB page memory, we will check
if we can satisfy that request from existing memory, if not we try
and allocate more 2MB pages. If that fails and user also specified
a "size is hint" flag, we then check other page sizes and try to
allocate from there. If that fails too, then, depending on flags,
we may try allocating from other sockets. In other words, we try
our best to give the user what they asked for, but going to other
sockets is last resort - first we try to allocate more memory on
the same socket.
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
Tested-by: Santosh Shukla <santosh.shukla@caviumnetworks.com>
Tested-by: Hemant Agrawal <hemant.agrawal@nxp.com>
Tested-by: Gowrishankar Muthukrishnan <gowrishankar.m@linux.vnet.ibm.com>
2018-04-11 13:30:35 +01:00
|
|
|
return malloc_heap_alloc(type, size, socket_arg, 0,
|
|
|
|
align == 0 ? 1 : align, 0, false);
|
2015-07-15 17:32:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate memory on default heap.
|
|
|
|
*/
|
|
|
|
void *
|
|
|
|
rte_malloc(const char *type, size_t size, unsigned align)
|
|
|
|
{
|
|
|
|
return rte_malloc_socket(type, size, align, SOCKET_ID_ANY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate zero'd memory on specified heap.
|
|
|
|
*/
|
|
|
|
void *
|
|
|
|
rte_zmalloc_socket(const char *type, size_t size, unsigned align, int socket)
|
|
|
|
{
|
2016-07-05 12:01:16 +01:00
|
|
|
return rte_malloc_socket(type, size, align, socket);
|
2015-07-15 17:32:20 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate zero'd memory on default heap.
|
|
|
|
*/
|
|
|
|
void *
|
|
|
|
rte_zmalloc(const char *type, size_t size, unsigned align)
|
|
|
|
{
|
|
|
|
return rte_zmalloc_socket(type, size, align, SOCKET_ID_ANY);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate zero'd memory on specified heap.
|
|
|
|
*/
|
|
|
|
void *
|
|
|
|
rte_calloc_socket(const char *type, size_t num, size_t size, unsigned align, int socket)
|
|
|
|
{
|
|
|
|
return rte_zmalloc_socket(type, num * size, align, socket);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Allocate zero'd memory on default heap.
|
|
|
|
*/
|
|
|
|
void *
|
|
|
|
rte_calloc(const char *type, size_t num, size_t size, unsigned align)
|
|
|
|
{
|
|
|
|
return rte_zmalloc(type, num * size, align);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Resize allocated memory.
|
|
|
|
*/
|
|
|
|
void *
|
|
|
|
rte_realloc(void *ptr, size_t size, unsigned align)
|
|
|
|
{
|
|
|
|
if (ptr == NULL)
|
|
|
|
return rte_malloc(NULL, size, align);
|
|
|
|
|
|
|
|
struct malloc_elem *elem = malloc_elem_from_data(ptr);
|
2018-04-11 13:29:44 +01:00
|
|
|
if (elem == NULL) {
|
|
|
|
RTE_LOG(ERR, EAL, "Error: memory corruption detected\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
2015-07-15 17:32:20 +01:00
|
|
|
|
|
|
|
size = RTE_CACHE_LINE_ROUNDUP(size), align = RTE_CACHE_LINE_ROUNDUP(align);
|
|
|
|
/* check alignment matches first, and if ok, see if we can resize block */
|
|
|
|
if (RTE_PTR_ALIGN(ptr,align) == ptr &&
|
2018-04-11 13:29:37 +01:00
|
|
|
malloc_heap_resize(elem, size) == 0)
|
2015-07-15 17:32:20 +01:00
|
|
|
return ptr;
|
|
|
|
|
|
|
|
/* either alignment is off, or we have no room to expand,
|
|
|
|
* so move data. */
|
|
|
|
void *new_ptr = rte_malloc(NULL, size, align);
|
|
|
|
if (new_ptr == NULL)
|
|
|
|
return NULL;
|
|
|
|
const unsigned old_size = elem->size - MALLOC_ELEM_OVERHEAD;
|
|
|
|
rte_memcpy(new_ptr, ptr, old_size < size ? old_size : size);
|
|
|
|
rte_free(ptr);
|
|
|
|
|
|
|
|
return new_ptr;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
rte_malloc_validate(const void *ptr, size_t *size)
|
|
|
|
{
|
|
|
|
const struct malloc_elem *elem = malloc_elem_from_data(ptr);
|
|
|
|
if (!malloc_elem_cookies_ok(elem))
|
|
|
|
return -1;
|
|
|
|
if (size != NULL)
|
|
|
|
*size = elem->size - elem->pad - MALLOC_ELEM_OVERHEAD;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Function to retrieve data for heap on given socket
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
rte_malloc_get_socket_stats(int socket,
|
|
|
|
struct rte_malloc_socket_stats *socket_stats)
|
|
|
|
{
|
|
|
|
struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
|
|
|
|
|
|
|
|
if (socket >= RTE_MAX_NUMA_NODES || socket < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return malloc_heap_get_stats(&mcfg->malloc_heaps[socket], socket_stats);
|
|
|
|
}
|
|
|
|
|
2018-04-11 13:29:39 +01:00
|
|
|
/*
|
|
|
|
* Function to dump contents of all heaps
|
|
|
|
*/
|
|
|
|
void __rte_experimental
|
|
|
|
rte_malloc_dump_heaps(FILE *f)
|
|
|
|
{
|
|
|
|
struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
|
|
|
|
unsigned int idx;
|
|
|
|
|
|
|
|
for (idx = 0; idx < rte_socket_count(); idx++) {
|
|
|
|
unsigned int socket = rte_socket_id_by_idx(idx);
|
|
|
|
fprintf(f, "Heap on socket %i:\n", socket);
|
|
|
|
malloc_heap_dump(&mcfg->malloc_heaps[socket], f);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-07-15 17:32:20 +01:00
|
|
|
/*
|
|
|
|
* Print stats on memory type. If type is NULL, info on all types is printed
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
rte_malloc_dump_stats(FILE *f, __rte_unused const char *type)
|
|
|
|
{
|
|
|
|
unsigned int socket;
|
|
|
|
struct rte_malloc_socket_stats sock_stats;
|
|
|
|
/* Iterate through all initialised heaps */
|
|
|
|
for (socket=0; socket< RTE_MAX_NUMA_NODES; socket++) {
|
|
|
|
if ((rte_malloc_get_socket_stats(socket, &sock_stats) < 0))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
fprintf(f, "Socket:%u\n", socket);
|
|
|
|
fprintf(f, "\tHeap_size:%zu,\n", sock_stats.heap_totalsz_bytes);
|
|
|
|
fprintf(f, "\tFree_size:%zu,\n", sock_stats.heap_freesz_bytes);
|
|
|
|
fprintf(f, "\tAlloc_size:%zu,\n", sock_stats.heap_allocsz_bytes);
|
|
|
|
fprintf(f, "\tGreatest_free_size:%zu,\n",
|
|
|
|
sock_stats.greatest_free_size);
|
|
|
|
fprintf(f, "\tAlloc_count:%u,\n",sock_stats.alloc_count);
|
|
|
|
fprintf(f, "\tFree_count:%u,\n", sock_stats.free_count);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* TODO: Set limit to memory that can be allocated to memory type
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
rte_malloc_set_limit(__rte_unused const char *type,
|
|
|
|
__rte_unused size_t max)
|
|
|
|
{
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
2017-11-04 02:22:21 +01:00
|
|
|
* Return the IO address of a virtual address obtained through rte_malloc
|
2015-07-15 17:32:20 +01:00
|
|
|
*/
|
2017-11-04 02:22:21 +01:00
|
|
|
rte_iova_t
|
|
|
|
rte_malloc_virt2iova(const void *addr)
|
2015-07-15 17:32:20 +01:00
|
|
|
{
|
mem: replace memseg with memseg lists
Before, we were aggregating multiple pages into one memseg, so the
number of memsegs was small. Now, each page gets its own memseg,
so the list of memsegs is huge. To accommodate the new memseg list
size and to keep the under-the-hood workings sane, the memseg list
is now not just a single list, but multiple lists. To be precise,
each hugepage size available on the system gets one or more memseg
lists, per socket.
In order to support dynamic memory allocation, we reserve all
memory in advance (unless we're in 32-bit legacy mode, in which
case we do not preallocate memory). As in, we do an anonymous
mmap() of the entire maximum size of memory per hugepage size, per
socket (which is limited to either RTE_MAX_MEMSEG_PER_TYPE pages or
RTE_MAX_MEM_MB_PER_TYPE megabytes worth of memory, whichever is the
smaller one), split over multiple lists (which are limited to
either RTE_MAX_MEMSEG_PER_LIST memsegs or RTE_MAX_MEM_MB_PER_LIST
megabytes per list, whichever is the smaller one). There is also
a global limit of CONFIG_RTE_MAX_MEM_MB megabytes, which is mainly
used for 32-bit targets to limit amounts of preallocated memory,
but can be used to place an upper limit on total amount of VA
memory that can be allocated by DPDK application.
So, for each hugepage size, we get (by default) up to 128G worth
of memory, per socket, split into chunks of up to 32G in size.
The address space is claimed at the start, in eal_common_memory.c.
The actual page allocation code is in eal_memalloc.c (Linux-only),
and largely consists of copied EAL memory init code.
Pages in the list are also indexed by address. That is, in order
to figure out where the page belongs, one can simply look at base
address for a memseg list. Similarly, figuring out IOVA address
of a memzone is a matter of finding the right memseg list, getting
offset and dividing by page size to get the appropriate memseg.
This commit also removes rte_eal_dump_physmem_layout() call,
according to deprecation notice [1], and removes that deprecation
notice as well.
On 32-bit targets due to limited VA space, DPDK will no longer
spread memory to different sockets like before. Instead, it will
(by default) allocate all of the memory on socket where master
lcore is. To override this behavior, --socket-mem must be used.
The rest of the changes are really ripple effects from the memseg
change - heap changes, compile fixes, and rewrites to support
fbarray-backed memseg lists. Due to earlier switch to _walk()
functions, most of the changes are simple fixes, however some
of the _walk() calls were switched to memseg list walk, where
it made sense to do so.
Additionally, we are also switching locks from flock() to fcntl().
Down the line, we will be introducing single-file segments option,
and we cannot use flock() locks to lock parts of the file. Therefore,
we will use fcntl() locks for legacy mem as well, in case someone is
unfortunate enough to accidentally start legacy mem primary process
alongside an already working non-legacy mem-based primary process.
[1] http://dpdk.org/dev/patchwork/patch/34002/
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
Tested-by: Santosh Shukla <santosh.shukla@caviumnetworks.com>
Tested-by: Hemant Agrawal <hemant.agrawal@nxp.com>
Tested-by: Gowrishankar Muthukrishnan <gowrishankar.m@linux.vnet.ibm.com>
2018-04-11 13:30:24 +01:00
|
|
|
const struct rte_memseg *ms;
|
|
|
|
struct malloc_elem *elem = malloc_elem_from_data(addr);
|
|
|
|
|
2015-07-15 17:32:20 +01:00
|
|
|
if (elem == NULL)
|
2017-11-04 02:22:21 +01:00
|
|
|
return RTE_BAD_IOVA;
|
2017-10-06 16:33:46 +05:30
|
|
|
|
|
|
|
if (rte_eal_iova_mode() == RTE_IOVA_VA)
|
mem: replace memseg with memseg lists
Before, we were aggregating multiple pages into one memseg, so the
number of memsegs was small. Now, each page gets its own memseg,
so the list of memsegs is huge. To accommodate the new memseg list
size and to keep the under-the-hood workings sane, the memseg list
is now not just a single list, but multiple lists. To be precise,
each hugepage size available on the system gets one or more memseg
lists, per socket.
In order to support dynamic memory allocation, we reserve all
memory in advance (unless we're in 32-bit legacy mode, in which
case we do not preallocate memory). As in, we do an anonymous
mmap() of the entire maximum size of memory per hugepage size, per
socket (which is limited to either RTE_MAX_MEMSEG_PER_TYPE pages or
RTE_MAX_MEM_MB_PER_TYPE megabytes worth of memory, whichever is the
smaller one), split over multiple lists (which are limited to
either RTE_MAX_MEMSEG_PER_LIST memsegs or RTE_MAX_MEM_MB_PER_LIST
megabytes per list, whichever is the smaller one). There is also
a global limit of CONFIG_RTE_MAX_MEM_MB megabytes, which is mainly
used for 32-bit targets to limit amounts of preallocated memory,
but can be used to place an upper limit on total amount of VA
memory that can be allocated by DPDK application.
So, for each hugepage size, we get (by default) up to 128G worth
of memory, per socket, split into chunks of up to 32G in size.
The address space is claimed at the start, in eal_common_memory.c.
The actual page allocation code is in eal_memalloc.c (Linux-only),
and largely consists of copied EAL memory init code.
Pages in the list are also indexed by address. That is, in order
to figure out where the page belongs, one can simply look at base
address for a memseg list. Similarly, figuring out IOVA address
of a memzone is a matter of finding the right memseg list, getting
offset and dividing by page size to get the appropriate memseg.
This commit also removes rte_eal_dump_physmem_layout() call,
according to deprecation notice [1], and removes that deprecation
notice as well.
On 32-bit targets due to limited VA space, DPDK will no longer
spread memory to different sockets like before. Instead, it will
(by default) allocate all of the memory on socket where master
lcore is. To override this behavior, --socket-mem must be used.
The rest of the changes are really ripple effects from the memseg
change - heap changes, compile fixes, and rewrites to support
fbarray-backed memseg lists. Due to earlier switch to _walk()
functions, most of the changes are simple fixes, however some
of the _walk() calls were switched to memseg list walk, where
it made sense to do so.
Additionally, we are also switching locks from flock() to fcntl().
Down the line, we will be introducing single-file segments option,
and we cannot use flock() locks to lock parts of the file. Therefore,
we will use fcntl() locks for legacy mem as well, in case someone is
unfortunate enough to accidentally start legacy mem primary process
alongside an already working non-legacy mem-based primary process.
[1] http://dpdk.org/dev/patchwork/patch/34002/
Signed-off-by: Anatoly Burakov <anatoly.burakov@intel.com>
Tested-by: Santosh Shukla <santosh.shukla@caviumnetworks.com>
Tested-by: Hemant Agrawal <hemant.agrawal@nxp.com>
Tested-by: Gowrishankar Muthukrishnan <gowrishankar.m@linux.vnet.ibm.com>
2018-04-11 13:30:24 +01:00
|
|
|
return (uintptr_t) addr;
|
|
|
|
|
|
|
|
ms = rte_mem_virt2memseg(addr, elem->msl);
|
|
|
|
if (ms == NULL)
|
|
|
|
return RTE_BAD_IOVA;
|
|
|
|
|
|
|
|
if (ms->iova == RTE_BAD_IOVA)
|
|
|
|
return RTE_BAD_IOVA;
|
|
|
|
|
|
|
|
return ms->iova + RTE_PTR_DIFF(addr, ms->addr);
|
2015-07-15 17:32:20 +01:00
|
|
|
}
|