a8b47d74c9b78c4c3aa93113c8e524a229eae749
[vpp.git] / src / vppinfra / pmalloc.c
1 /*
2  * Copyright (c) 2018 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #define _GNU_SOURCE
17 #include <stdlib.h>
18 #include <sys/types.h>
19 #include <sys/stat.h>
20 #include <fcntl.h>
21 #include <linux/mempolicy.h>
22 #include <linux/memfd.h>
23
24 #include <vppinfra/format.h>
25 #include <vppinfra/linux/syscall.h>
26 #include <vppinfra/linux/sysfs.h>
27 #include <vppinfra/mem.h>
28 #include <vppinfra/hash.h>
29 #include <vppinfra/pmalloc.h>
30
31 #if __SIZEOF_POINTER__ >= 8
32 #define DEFAULT_RESERVED_MB 16384
33 #else
34 #define DEFAULT_RESERVED_MB 256
35 #endif
36
37 static inline clib_pmalloc_chunk_t *
38 get_chunk (clib_pmalloc_page_t * pp, u32 index)
39 {
40   return pool_elt_at_index (pp->chunks, index);
41 }
42
43 static inline int
44 pmalloc_validate_numa_node (u32 * numa_node)
45 {
46   if (*numa_node == CLIB_PMALLOC_NUMA_LOCAL)
47     {
48       u32 cpu;
49       if (getcpu (&cpu, numa_node, 0) != 0)
50         return 1;
51     }
52   return 0;
53 }
54
55 int
56 clib_pmalloc_init (clib_pmalloc_main_t * pm, uword size)
57 {
58   uword off, pagesize;
59
60   ASSERT (pm->error == 0);
61
62   pagesize = clib_mem_get_default_hugepage_size ();
63   pm->log2_page_sz = min_log2 (pagesize);
64
65   size = size ? size : ((u64) DEFAULT_RESERVED_MB) << 20;
66   size = round_pow2 (size, pagesize);
67
68   pm->max_pages = size >> pm->log2_page_sz;
69
70   /* reserve VA space for future growth */
71   pm->base = mmap (0, size + pagesize, PROT_NONE,
72                    MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
73
74   if (pm->base == MAP_FAILED)
75     {
76       pm->error = clib_error_return_unix (0, "failed to reserve %u pages");
77       return -1;
78     }
79
80   off = round_pow2 (pointer_to_uword (pm->base), pagesize) -
81     pointer_to_uword (pm->base);
82
83   /* trim start and end of reservation to be page aligned */
84   if (off)
85     {
86       munmap (pm->base, off);
87       pm->base += off;
88     }
89
90   munmap (pm->base + (pm->max_pages * pagesize), pagesize - off);
91   return 0;
92 }
93
94 static inline void *
95 alloc_chunk_from_page (clib_pmalloc_main_t * pm, clib_pmalloc_page_t * pp,
96                        u32 n_blocks, u32 block_align, u32 numa_node)
97 {
98   clib_pmalloc_chunk_t *c;
99   void *va;
100   u32 off;
101   u32 alloc_chunk_index;
102
103   if (pp->chunks == 0)
104     {
105       pool_get (pp->chunks, c);
106       pp->n_free_chunks = 1;
107       pp->first_chunk_index = c - pp->chunks;
108       c->prev = c->next = ~0;
109       c->size = pp->n_free_blocks;
110     }
111
112   alloc_chunk_index = pp->first_chunk_index;
113
114 next_chunk:
115   c = pool_elt_at_index (pp->chunks, alloc_chunk_index);
116   off = (block_align - (c->start & (block_align - 1))) & (block_align - 1);
117
118   if (c->used || n_blocks + off > c->size)
119     {
120       if (c->next == ~0)
121         return 0;
122       alloc_chunk_index = c->next;
123       goto next_chunk;
124     }
125
126   /* if alignment is needed create new empty chunk */
127   if (off)
128     {
129       u32 offset_chunk_index;
130       clib_pmalloc_chunk_t *co;
131       pool_get (pp->chunks, c);
132       pp->n_free_chunks++;
133       offset_chunk_index = alloc_chunk_index;
134       alloc_chunk_index = c - pp->chunks;
135
136       co = pool_elt_at_index (pp->chunks, offset_chunk_index);
137       c->size = co->size - off;
138       c->next = co->next;
139       c->start = co->start + off;
140       c->prev = offset_chunk_index;
141       co->size = off;
142       co->next = alloc_chunk_index;
143     }
144
145   c->used = 1;
146   if (c->size > n_blocks)
147     {
148       u32 tail_chunk_index;
149       clib_pmalloc_chunk_t *ct;
150       pool_get (pp->chunks, ct);
151       pp->n_free_chunks++;
152       tail_chunk_index = ct - pp->chunks;
153       c = pool_elt_at_index (pp->chunks, alloc_chunk_index);
154       ct->size = c->size - n_blocks;
155       ct->next = c->next;
156       ct->prev = alloc_chunk_index;
157       ct->start = c->start + n_blocks;
158
159       c->size = n_blocks;
160       c->next = tail_chunk_index;
161       if (ct->next != ~0)
162         pool_elt_at_index (pp->chunks, ct->next)->prev = tail_chunk_index;
163     }
164   else if (c->next != ~0)
165     pool_elt_at_index (pp->chunks, c->next)->prev = alloc_chunk_index;
166
167   c = get_chunk (pp, alloc_chunk_index);
168   va = pm->base + ((pp - pm->pages) << pm->log2_page_sz) +
169     (c->start << PMALLOC_LOG2_BLOCK_SZ);
170   hash_set (pm->chunk_index_by_va, pointer_to_uword (va), alloc_chunk_index);
171   pp->n_free_blocks -= n_blocks;
172   pp->n_free_chunks--;
173   return va;
174 }
175
176 static inline clib_pmalloc_page_t *
177 pmalloc_map_pages (clib_pmalloc_main_t * pm, clib_pmalloc_arena_t * a,
178                    u32 numa_node, u32 n_pages)
179 {
180   clib_pmalloc_page_t *pp = 0;
181   u64 seek, pa, sys_page_size;
182   int pagemap_fd, status, rv, i, mmap_flags;
183   void *va;
184   int old_mpol = -1;
185   long unsigned int mask[16] = { 0 };
186   long unsigned int old_mask[16] = { 0 };
187
188   clib_error_free (pm->error);
189
190   if (pm->max_pages <= vec_len (pm->pages))
191     {
192       pm->error = clib_error_return (0, "maximum number of pages reached");
193       return 0;
194     }
195
196   pm->error = clib_sysfs_prealloc_hugepages (numa_node, pm->log2_page_sz,
197                                              n_pages);
198
199   if (pm->error)
200     return 0;
201
202   rv = get_mempolicy (&old_mpol, old_mask, sizeof (old_mask) * 8 + 1, 0, 0);
203   /* failure to get mempolicy means we can only proceed with numa 0 maps */
204   if (rv == -1 && numa_node != 0)
205     {
206       pm->error = clib_error_return_unix (0, "failed to get mempolicy");
207       return 0;
208     }
209
210   mask[0] = 1 << numa_node;
211   rv = set_mempolicy (MPOL_BIND, mask, sizeof (mask) * 8 + 1);
212   if (rv == -1 && numa_node != 0)
213     {
214       pm->error = clib_error_return_unix (0, "failed to set mempolicy for "
215                                           "numa node %u", numa_node);
216       return 0;
217     }
218
219   mmap_flags = MAP_FIXED | MAP_HUGETLB | MAP_LOCKED | MAP_ANONYMOUS;
220   if (a->flags & CLIB_PMALLOC_ARENA_F_SHARED_MEM)
221     {
222       mmap_flags |= MAP_SHARED;
223       pm->error = clib_mem_create_hugetlb_fd ((char *) a->name, &a->fd);
224       if (a->fd == -1)
225         goto error;
226     }
227   else
228     {
229       mmap_flags |= MAP_PRIVATE;
230       a->fd = -1;
231     }
232
233   va = pm->base + (vec_len (pm->pages) << pm->log2_page_sz);
234   if (mmap (va, n_pages << pm->log2_page_sz, PROT_READ | PROT_WRITE,
235             mmap_flags, a->fd, 0) == MAP_FAILED)
236     {
237       pm->error = clib_error_return_unix (0, "failed to mmap %u pages at %p "
238                                           "fd %d numa %d flags 0x%x", n_pages,
239                                           va, a->fd, numa_node, mmap_flags);
240       goto error;
241     }
242
243   rv = set_mempolicy (old_mpol, old_mask, sizeof (old_mask) * 8 + 1);
244   if (rv == -1 && numa_node != 0)
245     {
246       pm->error = clib_error_return_unix (0, "failed to restore mempolicy");
247       goto error;
248     }
249
250   /* we tolerate move_pages failure only if request os for numa node 0
251      to support non-numa kernels */
252   rv = move_pages (0, 1, &va, 0, &status, 0);
253   if ((rv == 0 && status != numa_node) || (rv != 0 && numa_node != 0))
254     {
255       pm->error = rv == -1 ?
256         clib_error_return_unix (0, "page allocated on wrong node, numa node "
257                                 "%u status %d", numa_node, status) :
258         clib_error_return (0, "page allocated on wrong node, numa node "
259                            "%u status %d", numa_node, status);
260
261       /* unmap & reesrve */
262       munmap (va, n_pages << pm->log2_page_sz);
263       mmap (va, n_pages << pm->log2_page_sz, PROT_NONE,
264             MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
265       goto error;
266     }
267
268   clib_memset (va, 0, n_pages << pm->log2_page_sz);
269   sys_page_size = sysconf (_SC_PAGESIZE);
270   pagemap_fd = open ((char *) "/proc/self/pagemap", O_RDONLY);
271
272   for (i = 0; i < n_pages; i++)
273     {
274       uword page_va = pointer_to_uword ((u8 *) va + (i << pm->log2_page_sz));
275       vec_add2 (pm->pages, pp, 1);
276       pp->n_free_blocks = 1 << (pm->log2_page_sz - PMALLOC_LOG2_BLOCK_SZ);
277       pp->index = pp - pm->pages;
278       pp->arena_index = a->index;
279
280       vec_add1 (a->page_indices, pp->index);
281       a->n_pages++;
282
283       seek = (page_va / sys_page_size) * sizeof (pa);
284       if (pagemap_fd != -1 &&
285           lseek (pagemap_fd, seek, SEEK_SET) == seek &&
286           read (pagemap_fd, &pa, sizeof (pa)) == (sizeof (pa)) &&
287           pa & (1ULL << 63) /* page present bit */ )
288         {
289           pp->pa = (pa & pow2_mask (55)) * sys_page_size;
290         }
291       vec_add1_aligned (pm->va_pa_diffs, pp->pa ? page_va - pp->pa : 0,
292                         CLIB_CACHE_LINE_BYTES);
293     }
294
295   if (pagemap_fd != -1)
296     close (pagemap_fd);
297
298   /* return pointer to 1st page */
299   return pp - (n_pages - 1);
300
301 error:
302   if (a->fd != -1)
303     close (a->fd);
304   return 0;
305 }
306
307 void *
308 clib_pmalloc_create_shared_arena (clib_pmalloc_main_t * pm, char *name,
309                                   uword size, u32 numa_node)
310 {
311   clib_pmalloc_arena_t *a;
312   clib_pmalloc_page_t *pp;
313   u32 n_pages = round_pow2 (size, 1ULL << pm->log2_page_sz) >>
314     pm->log2_page_sz;
315
316   if (n_pages + vec_len (pm->pages) > pm->max_pages)
317     return 0;
318
319   if (pmalloc_validate_numa_node (&numa_node))
320     return 0;
321
322   pool_get (pm->arenas, a);
323   a->index = a - pm->arenas;
324   a->name = format (0, "%s%c", name, 0);
325   a->numa_node = numa_node;
326   a->flags = CLIB_PMALLOC_ARENA_F_SHARED_MEM;
327   a->log2_page_sz = pm->log2_page_sz;
328
329   if ((pp = pmalloc_map_pages (pm, a, numa_node, n_pages)) == 0)
330     {
331       vec_free (a->name);
332       memset (a, 0, sizeof (*a));
333       pool_put (pm->arenas, a);
334       return 0;
335     }
336
337   return pm->base + (pp->index << pm->log2_page_sz);
338 }
339
340 static inline void *
341 clib_pmalloc_alloc_inline (clib_pmalloc_main_t * pm, clib_pmalloc_arena_t * a,
342                            uword size, uword align, u32 numa_node)
343 {
344   clib_pmalloc_page_t *pp;
345   u32 n_blocks, block_align, *page_index;
346
347   ASSERT (is_pow2 (align));
348
349   if (pmalloc_validate_numa_node (&numa_node))
350     return 0;
351
352   if (a == 0)
353     {
354       vec_validate_init_empty (pm->default_arena_for_numa_node,
355                                numa_node, ~0);
356       if (pm->default_arena_for_numa_node[numa_node] == ~0)
357         {
358           pool_get (pm->arenas, a);
359           pm->default_arena_for_numa_node[numa_node] = a - pm->arenas;
360           a->name = format (0, "default-numa-%u%c", numa_node, 0);
361           a->numa_node = numa_node;
362         }
363       else
364         a = pool_elt_at_index (pm->arenas,
365                                pm->default_arena_for_numa_node[numa_node]);
366     }
367
368   n_blocks = round_pow2 (size, PMALLOC_BLOCK_SZ) / PMALLOC_BLOCK_SZ;
369   block_align = align >> PMALLOC_LOG2_BLOCK_SZ;
370
371   vec_foreach (page_index, a->page_indices)
372   {
373     pp = vec_elt_at_index (pm->pages, *page_index);
374     void *rv = alloc_chunk_from_page (pm, pp, n_blocks, block_align,
375                                       numa_node);
376
377     if (rv)
378       return rv;
379   }
380
381   if ((a->flags & CLIB_PMALLOC_ARENA_F_SHARED_MEM) == 0 &&
382       (pp = pmalloc_map_pages (pm, a, numa_node, 1)))
383     return alloc_chunk_from_page (pm, pp, n_blocks, block_align, numa_node);
384
385   return 0;
386 }
387
388 void *
389 clib_pmalloc_alloc_aligned_on_numa (clib_pmalloc_main_t * pm, uword size,
390                                     uword align, u32 numa_node)
391 {
392   return clib_pmalloc_alloc_inline (pm, 0, size, align, numa_node);
393 }
394
395 void *
396 clib_pmalloc_alloc_aligned (clib_pmalloc_main_t * pm, uword size, uword align)
397 {
398   return clib_pmalloc_alloc_inline (pm, 0, size, align,
399                                     CLIB_PMALLOC_NUMA_LOCAL);
400 }
401
402 void *
403 clib_pmalloc_alloc_from_arena (clib_pmalloc_main_t * pm, void *arena_va,
404                                uword size, uword align)
405 {
406   clib_pmalloc_arena_t *a = clib_pmalloc_get_arena (pm, arena_va);
407   return clib_pmalloc_alloc_inline (pm, a, size, align, 0);
408 }
409
410 void
411 clib_pmalloc_free (clib_pmalloc_main_t * pm, void *va)
412 {
413   clib_pmalloc_page_t *pp;
414   clib_pmalloc_chunk_t *c;
415   uword *p;
416   u32 chunk_index, page_index;
417
418   p = hash_get (pm->chunk_index_by_va, pointer_to_uword (va));
419
420   if (p == 0)
421     os_panic ();
422
423   chunk_index = p[0];
424   page_index = clib_pmalloc_get_page_index (pm, va);
425   hash_unset (pm->chunk_index_by_va, pointer_to_uword (va));
426
427   pp = vec_elt_at_index (pm->pages, page_index);
428   c = pool_elt_at_index (pp->chunks, chunk_index);
429   c->used = 0;
430   pp->n_free_blocks += c->size;
431   pp->n_free_chunks++;
432
433   /* merge with next if free */
434   if (c->next != ~0 && get_chunk (pp, c->next)->used == 0)
435     {
436       clib_pmalloc_chunk_t *next = get_chunk (pp, c->next);
437       c->size += next->size;
438       c->next = next->next;
439       if (next->next != ~0)
440         get_chunk (pp, next->next)->prev = chunk_index;
441       memset (next, 0, sizeof (*next));
442       pool_put (pp->chunks, next);
443       pp->n_free_chunks--;
444     }
445
446   /* merge with prev if free */
447   if (c->prev != ~0 && get_chunk (pp, c->prev)->used == 0)
448     {
449       clib_pmalloc_chunk_t *prev = get_chunk (pp, c->prev);
450       prev->size += c->size;
451       prev->next = c->next;
452       if (c->next != ~0)
453         get_chunk (pp, c->next)->prev = c->prev;
454       memset (c, 0, sizeof (*c));
455       pool_put (pp->chunks, c);
456       pp->n_free_chunks--;
457     }
458 }
459
460 static u8 *
461 format_pmalloc_page (u8 * s, va_list * va)
462 {
463   clib_pmalloc_page_t *pp = va_arg (*va, clib_pmalloc_page_t *);
464   int verbose = va_arg (*va, int);
465   u32 indent = format_get_indent (s);
466
467   s = format (s, "page %u: phys-addr %p ", pp->index, pp->pa);
468
469   if (pp->chunks == 0)
470     return s;
471
472   s = format (s, "free %u chunks %u free-chunks %d ",
473               (pp->n_free_blocks) << PMALLOC_LOG2_BLOCK_SZ,
474               pool_elts (pp->chunks), pp->n_free_chunks);
475
476   if (verbose >= 2)
477     {
478       clib_pmalloc_chunk_t *c;
479       c = pool_elt_at_index (pp->chunks, pp->first_chunk_index);
480       s = format (s, "\n%U%12s%12s%8s%8s%8s%8s",
481                   format_white_space, indent + 2,
482                   "chunk offset", "size", "used", "index", "prev", "next");
483       while (1)
484         {
485           s = format (s, "\n%U%12u%12u%8s%8d%8d%8d",
486                       format_white_space, indent + 2,
487                       c->start << PMALLOC_LOG2_BLOCK_SZ,
488                       c->size << PMALLOC_LOG2_BLOCK_SZ,
489                       c->used ? "yes" : "no",
490                       c - pp->chunks, c->prev, c->next);
491           if (c->next == ~0)
492             break;
493           c = pool_elt_at_index (pp->chunks, c->next);
494         }
495     }
496   return s;
497 }
498
499 u8 *
500 format_pmalloc (u8 * s, va_list * va)
501 {
502   clib_pmalloc_main_t *pm = va_arg (*va, clib_pmalloc_main_t *);
503   int verbose = va_arg (*va, int);
504   u32 indent = format_get_indent (s);
505
506   clib_pmalloc_page_t *pp;
507   clib_pmalloc_arena_t *a;
508
509   s = format (s, "used-pages %u reserved-pages %u pagesize %uKB",
510               vec_len (pm->pages), pm->max_pages,
511               1 << (pm->log2_page_sz - 10));
512
513   if (verbose >= 2)
514     s = format (s, " va-start %p", pm->base);
515
516   if (pm->error)
517     s = format (s, "\n%Ulast-error: %U", format_white_space, indent + 2,
518                 format_clib_error, pm->error);
519
520
521   /* *INDENT-OFF* */
522   pool_foreach (a, pm->arenas,
523     {
524       u32 *page_index;
525       s = format (s, "\n%Uarena '%s' pages %u numa-node %u",
526                   format_white_space, indent + 2,
527                   a->name, vec_len (a->page_indices), a->numa_node);
528       if (a->fd != -1)
529         s = format (s, " shared fd %d", a->fd);
530       if (verbose >= 1)
531         vec_foreach (page_index, a->page_indices)
532           {
533             pp = vec_elt_at_index (pm->pages, *page_index);
534             s = format (s, "\n%U%U", format_white_space, indent + 4,
535                         format_pmalloc_page, pp, verbose);
536           }
537     });
538   /* *INDENT-ON* */
539
540   return s;
541 }
542
543 /*
544  * fd.io coding-style-patch-verification: ON
545  *
546  * Local Variables:
547  * eval: (c-set-style "gnu")
548  * End:
549  */