45ea0adaabab71dadbad570a4971c6b9755d65a8
[dpdk.git] / lib / librte_eal / linuxapp / eal / eal_memalloc.c
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2017-2018 Intel Corporation
3  */
4
5 #define _FILE_OFFSET_BITS 64
6 #include <errno.h>
7 #include <stdarg.h>
8 #include <stdbool.h>
9 #include <stdlib.h>
10 #include <stdio.h>
11 #include <stdint.h>
12 #include <inttypes.h>
13 #include <string.h>
14 #include <sys/mman.h>
15 #include <sys/types.h>
16 #include <sys/stat.h>
17 #include <sys/queue.h>
18 #include <sys/file.h>
19 #include <unistd.h>
20 #include <limits.h>
21 #include <fcntl.h>
22 #include <sys/ioctl.h>
23 #include <sys/time.h>
24 #include <signal.h>
25 #include <setjmp.h>
26 #ifdef RTE_EAL_NUMA_AWARE_HUGEPAGES
27 #include <numa.h>
28 #include <numaif.h>
29 #endif
30
31 #include <rte_common.h>
32 #include <rte_log.h>
33 #include <rte_eal_memconfig.h>
34 #include <rte_eal.h>
35 #include <rte_memory.h>
36 #include <rte_spinlock.h>
37
38 #include "eal_filesystem.h"
39 #include "eal_internal_cfg.h"
40 #include "eal_memalloc.h"
41
42 static sigjmp_buf huge_jmpenv;
43
44 static void __rte_unused huge_sigbus_handler(int signo __rte_unused)
45 {
46         siglongjmp(huge_jmpenv, 1);
47 }
48
49 /* Put setjmp into a wrap method to avoid compiling error. Any non-volatile,
50  * non-static local variable in the stack frame calling sigsetjmp might be
51  * clobbered by a call to longjmp.
52  */
53 static int __rte_unused huge_wrap_sigsetjmp(void)
54 {
55         return sigsetjmp(huge_jmpenv, 1);
56 }
57
58 static struct sigaction huge_action_old;
59 static int huge_need_recover;
60
61 static void __rte_unused
62 huge_register_sigbus(void)
63 {
64         sigset_t mask;
65         struct sigaction action;
66
67         sigemptyset(&mask);
68         sigaddset(&mask, SIGBUS);
69         action.sa_flags = 0;
70         action.sa_mask = mask;
71         action.sa_handler = huge_sigbus_handler;
72
73         huge_need_recover = !sigaction(SIGBUS, &action, &huge_action_old);
74 }
75
76 static void __rte_unused
77 huge_recover_sigbus(void)
78 {
79         if (huge_need_recover) {
80                 sigaction(SIGBUS, &huge_action_old, NULL);
81                 huge_need_recover = 0;
82         }
83 }
84
85 #ifdef RTE_EAL_NUMA_AWARE_HUGEPAGES
86 static bool
87 check_numa(void)
88 {
89         bool ret = true;
90         /* Check if kernel supports NUMA. */
91         if (numa_available() != 0) {
92                 RTE_LOG(DEBUG, EAL, "NUMA is not supported.\n");
93                 ret = false;
94         }
95         return ret;
96 }
97
98 static void
99 prepare_numa(int *oldpolicy, struct bitmask *oldmask, int socket_id)
100 {
101         RTE_LOG(DEBUG, EAL, "Trying to obtain current memory policy.\n");
102         if (get_mempolicy(oldpolicy, oldmask->maskp,
103                           oldmask->size + 1, 0, 0) < 0) {
104                 RTE_LOG(ERR, EAL,
105                         "Failed to get current mempolicy: %s. "
106                         "Assuming MPOL_DEFAULT.\n", strerror(errno));
107                 oldpolicy = MPOL_DEFAULT;
108         }
109         RTE_LOG(DEBUG, EAL,
110                 "Setting policy MPOL_PREFERRED for socket %d\n",
111                 socket_id);
112         numa_set_preferred(socket_id);
113 }
114
115 static void
116 resotre_numa(int *oldpolicy, struct bitmask *oldmask)
117 {
118         RTE_LOG(DEBUG, EAL,
119                 "Restoring previous memory policy: %d\n", *oldpolicy);
120         if (oldpolicy == MPOL_DEFAULT) {
121                 numa_set_localalloc();
122         } else if (set_mempolicy(*oldpolicy, oldmask->maskp,
123                                  oldmask->size + 1) < 0) {
124                 RTE_LOG(ERR, EAL, "Failed to restore mempolicy: %s\n",
125                         strerror(errno));
126                 numa_set_localalloc();
127         }
128         numa_free_cpumask(oldmask);
129 }
130 #endif
131
132 static int
133 get_seg_fd(char *path, int buflen, struct hugepage_info *hi,
134                 unsigned int list_idx, unsigned int seg_idx)
135 {
136         int fd;
137         eal_get_hugefile_path(path, buflen, hi->hugedir,
138                         list_idx * RTE_MAX_MEMSEG_PER_LIST + seg_idx);
139         fd = open(path, O_CREAT | O_RDWR, 0600);
140         if (fd < 0) {
141                 RTE_LOG(DEBUG, EAL, "%s(): open failed: %s\n", __func__,
142                                 strerror(errno));
143                 return -1;
144         }
145         return fd;
146 }
147
148 /* returns 1 on successful lock, 0 on unsuccessful lock, -1 on error */
149 static int lock(int fd, uint64_t offset, uint64_t len, int type)
150 {
151         struct flock lck;
152         int ret;
153
154         memset(&lck, 0, sizeof(lck));
155
156         lck.l_type = type;
157         lck.l_whence = SEEK_SET;
158         lck.l_start = offset;
159         lck.l_len = len;
160
161         ret = fcntl(fd, F_SETLK, &lck);
162
163         if (ret && (errno == EAGAIN || errno == EACCES)) {
164                 /* locked by another process, not an error */
165                 return 0;
166         } else if (ret) {
167                 RTE_LOG(ERR, EAL, "%s(): error calling fcntl(): %s\n",
168                         __func__, strerror(errno));
169                 /* we've encountered an unexpected error */
170                 return -1;
171         }
172         return 1;
173 }
174
175 static int
176 alloc_seg(struct rte_memseg *ms, void *addr, int socket_id,
177                 struct hugepage_info *hi, unsigned int list_idx,
178                 unsigned int seg_idx)
179 {
180 #ifdef RTE_EAL_NUMA_AWARE_HUGEPAGES
181         int cur_socket_id = 0;
182 #endif
183         uint64_t map_offset;
184         char path[PATH_MAX];
185         int ret = 0;
186         int fd;
187         size_t alloc_sz;
188
189         fd = get_seg_fd(path, sizeof(path), hi, list_idx, seg_idx);
190         if (fd < 0)
191                 return -1;
192
193         alloc_sz = hi->hugepage_sz;
194
195         map_offset = 0;
196         if (ftruncate(fd, alloc_sz) < 0) {
197                 RTE_LOG(DEBUG, EAL, "%s(): ftruncate() failed: %s\n",
198                         __func__, strerror(errno));
199                 goto resized;
200         }
201         /* we've allocated a page - take out a read lock. we're using fcntl()
202          * locks rather than flock() here because doing that gives us one huge
203          * advantage - fcntl() locks are per-process, not per-file descriptor,
204          * which means that we don't have to keep the original fd's around to
205          * keep a lock on the file.
206          *
207          * this is useful, because when it comes to unmapping pages, we will
208          * have to take out a write lock (to figure out if another process still
209          * has this page mapped), and to do itwith flock() we'll have to use
210          * original fd, as lock is associated with that particular fd. with
211          * fcntl(), this is not necessary - we can open a new fd and use fcntl()
212          * on that.
213          */
214         ret = lock(fd, map_offset, alloc_sz, F_RDLCK);
215
216         /* this should not fail */
217         if (ret != 1) {
218                 RTE_LOG(ERR, EAL, "%s(): error locking file: %s\n",
219                         __func__,
220                         strerror(errno));
221                 goto resized;
222         }
223
224         /*
225          * map the segment, and populate page tables, the kernel fills this
226          * segment with zeros if it's a new page.
227          */
228         void *va = mmap(addr, alloc_sz, PROT_READ | PROT_WRITE,
229                         MAP_SHARED | MAP_POPULATE | MAP_FIXED, fd, map_offset);
230         close(fd);
231
232         if (va == MAP_FAILED) {
233                 RTE_LOG(DEBUG, EAL, "%s(): mmap() failed: %s\n", __func__,
234                         strerror(errno));
235                 goto resized;
236         }
237         if (va != addr) {
238                 RTE_LOG(DEBUG, EAL, "%s(): wrong mmap() address\n", __func__);
239                 goto mapped;
240         }
241
242         rte_iova_t iova = rte_mem_virt2iova(addr);
243         if (iova == RTE_BAD_PHYS_ADDR) {
244                 RTE_LOG(DEBUG, EAL, "%s(): can't get IOVA addr\n",
245                         __func__);
246                 goto mapped;
247         }
248
249 #ifdef RTE_EAL_NUMA_AWARE_HUGEPAGES
250         move_pages(getpid(), 1, &addr, NULL, &cur_socket_id, 0);
251
252         if (cur_socket_id != socket_id) {
253                 RTE_LOG(DEBUG, EAL,
254                                 "%s(): allocation happened on wrong socket (wanted %d, got %d)\n",
255                         __func__, socket_id, cur_socket_id);
256                 goto mapped;
257         }
258 #endif
259
260         /* In linux, hugetlb limitations, like cgroup, are
261          * enforced at fault time instead of mmap(), even
262          * with the option of MAP_POPULATE. Kernel will send
263          * a SIGBUS signal. To avoid to be killed, save stack
264          * environment here, if SIGBUS happens, we can jump
265          * back here.
266          */
267         if (huge_wrap_sigsetjmp()) {
268                 RTE_LOG(DEBUG, EAL, "SIGBUS: Cannot mmap more hugepages of size %uMB\n",
269                         (unsigned int)(alloc_sz >> 20));
270                 goto mapped;
271         }
272         *(int *)addr = *(int *)addr;
273
274         ms->addr = addr;
275         ms->hugepage_sz = alloc_sz;
276         ms->len = alloc_sz;
277         ms->nchannel = rte_memory_get_nchannel();
278         ms->nrank = rte_memory_get_nrank();
279         ms->iova = iova;
280         ms->socket_id = socket_id;
281
282         return 0;
283
284 mapped:
285         munmap(addr, alloc_sz);
286 resized:
287         close(fd);
288         unlink(path);
289         return -1;
290 }
291
292 struct alloc_walk_param {
293         struct hugepage_info *hi;
294         struct rte_memseg **ms;
295         size_t page_sz;
296         unsigned int segs_allocated;
297         unsigned int n_segs;
298         int socket;
299         bool exact;
300 };
301 static int
302 alloc_seg_walk(const struct rte_memseg_list *msl, void *arg)
303 {
304         struct rte_mem_config *mcfg = rte_eal_get_configuration()->mem_config;
305         struct alloc_walk_param *wa = arg;
306         struct rte_memseg_list *cur_msl;
307         size_t page_sz;
308         int cur_idx;
309         unsigned int msl_idx, need, i;
310
311         if (msl->page_sz != wa->page_sz)
312                 return 0;
313         if (msl->socket_id != wa->socket)
314                 return 0;
315
316         page_sz = (size_t)msl->page_sz;
317
318         msl_idx = msl - mcfg->memsegs;
319         cur_msl = &mcfg->memsegs[msl_idx];
320
321         need = wa->n_segs;
322
323         /* try finding space in memseg list */
324         cur_idx = rte_fbarray_find_next_n_free(&cur_msl->memseg_arr, 0, need);
325         if (cur_idx < 0)
326                 return 0;
327
328         for (i = 0; i < need; i++, cur_idx++) {
329                 struct rte_memseg *cur;
330                 void *map_addr;
331
332                 cur = rte_fbarray_get(&cur_msl->memseg_arr, cur_idx);
333                 map_addr = RTE_PTR_ADD(cur_msl->base_va,
334                                 cur_idx * page_sz);
335
336                 if (alloc_seg(cur, map_addr, wa->socket, wa->hi,
337                                 msl_idx, cur_idx)) {
338                         RTE_LOG(DEBUG, EAL, "attempted to allocate %i segments, but only %i were allocated\n",
339                                 need, i);
340
341                         /* if exact number wasn't requested, stop */
342                         if (!wa->exact)
343                                 goto out;
344                         return -1;
345                 }
346                 if (wa->ms)
347                         wa->ms[i] = cur;
348
349                 rte_fbarray_set_used(&cur_msl->memseg_arr, cur_idx);
350         }
351 out:
352         wa->segs_allocated = i;
353         return 1;
354
355 }
356
357 int
358 eal_memalloc_alloc_seg_bulk(struct rte_memseg **ms, int n_segs, size_t page_sz,
359                 int socket, bool exact)
360 {
361         int i, ret = -1;
362 #ifdef RTE_EAL_NUMA_AWARE_HUGEPAGES
363         bool have_numa = false;
364         int oldpolicy;
365         struct bitmask *oldmask;
366 #endif
367         struct alloc_walk_param wa;
368         struct hugepage_info *hi = NULL;
369
370         memset(&wa, 0, sizeof(wa));
371
372         /* dynamic allocation not supported in legacy mode */
373         if (internal_config.legacy_mem)
374                 return -1;
375
376         for (i = 0; i < (int) RTE_DIM(internal_config.hugepage_info); i++) {
377                 if (page_sz ==
378                                 internal_config.hugepage_info[i].hugepage_sz) {
379                         hi = &internal_config.hugepage_info[i];
380                         break;
381                 }
382         }
383         if (!hi) {
384                 RTE_LOG(ERR, EAL, "%s(): can't find relevant hugepage_info entry\n",
385                         __func__);
386                 return -1;
387         }
388
389 #ifdef RTE_EAL_NUMA_AWARE_HUGEPAGES
390         if (check_numa()) {
391                 oldmask = numa_allocate_nodemask();
392                 prepare_numa(&oldpolicy, oldmask, socket);
393                 have_numa = true;
394         }
395 #endif
396
397         wa.exact = exact;
398         wa.hi = hi;
399         wa.ms = ms;
400         wa.n_segs = n_segs;
401         wa.page_sz = page_sz;
402         wa.socket = socket;
403         wa.segs_allocated = 0;
404
405         ret = rte_memseg_list_walk(alloc_seg_walk, &wa);
406         if (ret == 0) {
407                 RTE_LOG(ERR, EAL, "%s(): couldn't find suitable memseg_list\n",
408                         __func__);
409                 ret = -1;
410         } else if (ret > 0) {
411                 ret = (int)wa.segs_allocated;
412         }
413
414 #ifdef RTE_EAL_NUMA_AWARE_HUGEPAGES
415         if (have_numa)
416                 resotre_numa(&oldpolicy, oldmask);
417 #endif
418         return ret;
419 }
420
421 struct rte_memseg *
422 eal_memalloc_alloc_seg(size_t page_sz, int socket)
423 {
424         struct rte_memseg *ms;
425         if (eal_memalloc_alloc_seg_bulk(&ms, 1, page_sz, socket, true) < 0)
426                 return NULL;
427         /* return pointer to newly allocated memseg */
428         return ms;
429 }