42efc007ceba6b9f3da06b84037179fe86c5cf42
[vpp.git] / src / vppinfra / linux / mem.c
1 /*
2  * Copyright (c) 2017 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 <unistd.h>
21 #include <sys/mount.h>
22 #include <sys/mman.h>
23 #include <fcntl.h>
24 #include <linux/mempolicy.h>
25 #include <linux/memfd.h>
26
27 #include <vppinfra/clib.h>
28 #include <vppinfra/mem.h>
29 #include <vppinfra/time.h>
30 #include <vppinfra/format.h>
31 #include <vppinfra/clib_error.h>
32 #include <vppinfra/linux/syscall.h>
33 #include <vppinfra/linux/sysfs.h>
34
35 #ifndef F_LINUX_SPECIFIC_BASE
36 #define F_LINUX_SPECIFIC_BASE 1024
37 #endif
38
39 #ifndef F_ADD_SEALS
40 #define F_ADD_SEALS (F_LINUX_SPECIFIC_BASE + 9)
41 #define F_GET_SEALS (F_LINUX_SPECIFIC_BASE + 10)
42
43 #define F_SEAL_SEAL     0x0001  /* prevent further seals from being set */
44 #define F_SEAL_SHRINK   0x0002  /* prevent file from shrinking */
45 #define F_SEAL_GROW     0x0004  /* prevent file from growing */
46 #define F_SEAL_WRITE    0x0008  /* prevent writes */
47 #endif
48
49 #ifndef MFD_HUGETLB
50 #define MFD_HUGETLB 0x0004U
51 #endif
52
53 uword
54 clib_mem_get_page_size (void)
55 {
56   return getpagesize ();
57 }
58
59 uword
60 clib_mem_get_default_hugepage_size (void)
61 {
62   unformat_input_t input;
63   static u32 size = 0;
64   int fd;
65
66   if (size)
67     goto done;
68
69   /*
70    * If the kernel doesn't support hugepages, /proc/meminfo won't
71    * say anything about it. Use the regular page size as a default.
72    */
73   size = clib_mem_get_page_size () / 1024;
74
75   if ((fd = open ("/proc/meminfo", 0)) == -1)
76     return 0;
77
78   unformat_init_clib_file (&input, fd);
79
80   while (unformat_check_input (&input) != UNFORMAT_END_OF_INPUT)
81     {
82       if (unformat (&input, "Hugepagesize:%_%u kB", &size))
83         ;
84       else
85         unformat_skip_line (&input);
86     }
87   unformat_free (&input);
88   close (fd);
89 done:
90   return 1024ULL * size;
91 }
92
93 static clib_mem_page_sz_t
94 legacy_get_log2_default_hugepage_size (void)
95 {
96   clib_mem_page_sz_t log2_page_size = CLIB_MEM_PAGE_SZ_UNKNOWN;
97   FILE *fp;
98   char tmp[33] = { };
99
100   if ((fp = fopen ("/proc/meminfo", "r")) == NULL)
101     return CLIB_MEM_PAGE_SZ_UNKNOWN;
102
103   while (fscanf (fp, "%32s", tmp) > 0)
104     if (strncmp ("Hugepagesize:", tmp, 13) == 0)
105       {
106         u32 size;
107         if (fscanf (fp, "%u", &size) > 0)
108           log2_page_size = 10 + min_log2 (size);
109         break;
110       }
111
112   fclose (fp);
113   return log2_page_size;
114 }
115
116 void
117 clib_mem_main_init ()
118 {
119   clib_mem_main_t *mm = &clib_mem_main;
120   uword page_size;
121   void *va;
122   int fd;
123
124   if (mm->log2_page_sz != CLIB_MEM_PAGE_SZ_UNKNOWN)
125     return;
126
127   /* system page size */
128   page_size = sysconf (_SC_PAGESIZE);
129   mm->log2_page_sz = min_log2 (page_size);
130
131   /* default system hugeppage size */
132   if ((fd = memfd_create ("test", MFD_HUGETLB)) != -1)
133     {
134       mm->log2_default_hugepage_sz = clib_mem_get_fd_log2_page_size (fd);
135       close (fd);
136     }
137   else                          /* likely kernel older than 4.14 */
138     mm->log2_default_hugepage_sz = legacy_get_log2_default_hugepage_size ();
139
140   /* numa nodes */
141   va = mmap (0, page_size, PROT_READ | PROT_WRITE, MAP_PRIVATE |
142              MAP_ANONYMOUS, -1, 0);
143   if (va == MAP_FAILED)
144     return;
145
146   if (mlock (va, page_size))
147     goto done;
148
149   for (int i = 0; i < CLIB_MAX_NUMAS; i++)
150     {
151       int status;
152       if (move_pages (0, 1, &va, &i, &status, 0) == 0)
153         mm->numa_node_bitmap |= 1ULL << i;
154     }
155
156 done:
157   munmap (va, page_size);
158 }
159
160 u64
161 clib_mem_get_fd_page_size (int fd)
162 {
163   struct stat st = { 0 };
164   if (fstat (fd, &st) == -1)
165     return 0;
166   return st.st_blksize;
167 }
168
169 int
170 clib_mem_get_fd_log2_page_size (int fd)
171 {
172   return min_log2 (clib_mem_get_fd_page_size (fd));
173 }
174
175 void
176 clib_mem_vm_randomize_va (uword * requested_va,
177                           clib_mem_page_sz_t log2_page_size)
178 {
179   u8 bit_mask = 15;
180
181   if (log2_page_size <= 12)
182     bit_mask = 15;
183   else if (log2_page_size > 12 && log2_page_size <= 16)
184     bit_mask = 3;
185   else
186     bit_mask = 0;
187
188   *requested_va +=
189     (clib_cpu_time_now () & bit_mask) * (1ull << log2_page_size);
190 }
191
192 clib_error_t *
193 clib_mem_create_fd (char *name, int *fdp)
194 {
195   int fd;
196
197   ASSERT (name);
198
199   if ((fd = memfd_create (name, MFD_ALLOW_SEALING)) == -1)
200     return clib_error_return_unix (0, "memfd_create");
201
202   if ((fcntl (fd, F_ADD_SEALS, F_SEAL_SHRINK)) == -1)
203     {
204       close (fd);
205       return clib_error_return_unix (0, "fcntl (F_ADD_SEALS)");
206     }
207
208   *fdp = fd;
209   return 0;
210 }
211
212 clib_error_t *
213 clib_mem_create_hugetlb_fd (char *name, int *fdp)
214 {
215   clib_error_t *err = 0;
216   int fd = -1;
217   static int memfd_hugetlb_supported = 1;
218   char *mount_dir;
219   char template[] = "/tmp/hugepage_mount.XXXXXX";
220   u8 *filename;
221
222   ASSERT (name);
223
224   if (memfd_hugetlb_supported)
225     {
226       if ((fd = memfd_create (name, MFD_HUGETLB)) != -1)
227         goto done;
228
229       /* avoid further tries if memfd MFD_HUGETLB is not supported */
230       if (errno == EINVAL && strnlen (name, 256) <= 249)
231         memfd_hugetlb_supported = 0;
232     }
233
234   mount_dir = mkdtemp (template);
235   if (mount_dir == 0)
236     return clib_error_return_unix (0, "mkdtemp \'%s\'", template);
237
238   if (mount ("none", (char *) mount_dir, "hugetlbfs", 0, NULL))
239     {
240       rmdir ((char *) mount_dir);
241       err = clib_error_return_unix (0, "mount hugetlb directory '%s'",
242                                     mount_dir);
243     }
244
245   filename = format (0, "%s/%s%c", mount_dir, name, 0);
246   fd = open ((char *) filename, O_CREAT | O_RDWR, 0755);
247   umount2 ((char *) mount_dir, MNT_DETACH);
248   rmdir ((char *) mount_dir);
249
250   if (fd == -1)
251     err = clib_error_return_unix (0, "open");
252
253 done:
254   if (fd != -1)
255     fdp[0] = fd;
256   return err;
257 }
258
259 clib_error_t *
260 clib_mem_vm_ext_alloc (clib_mem_vm_alloc_t * a)
261 {
262   int fd = -1;
263   clib_error_t *err = 0;
264   void *addr = 0;
265   u8 *filename = 0;
266   int mmap_flags = 0;
267   int log2_page_size;
268   int n_pages;
269   int old_mpol = -1;
270   long unsigned int old_mask[16] = { 0 };
271
272   /* save old numa mem policy if needed */
273   if (a->flags & (CLIB_MEM_VM_F_NUMA_PREFER | CLIB_MEM_VM_F_NUMA_FORCE))
274     {
275       int rv;
276       rv = get_mempolicy (&old_mpol, old_mask, sizeof (old_mask) * 8 + 1,
277                           0, 0);
278
279       if (rv == -1)
280         {
281           if (a->numa_node != 0 && (a->flags & CLIB_MEM_VM_F_NUMA_FORCE) != 0)
282             {
283               err = clib_error_return_unix (0, "get_mempolicy");
284               goto error;
285             }
286           else
287             old_mpol = -1;
288         }
289     }
290
291   if (a->flags & CLIB_MEM_VM_F_LOCKED)
292     mmap_flags |= MAP_LOCKED;
293
294   /* if we are creating shared segment, we need file descriptor */
295   if (a->flags & CLIB_MEM_VM_F_SHARED)
296     {
297       mmap_flags |= MAP_SHARED;
298       /* if hugepages are needed we need to create mount point */
299       if (a->flags & CLIB_MEM_VM_F_HUGETLB)
300         {
301           if ((err = clib_mem_create_hugetlb_fd (a->name, &fd)))
302             goto error;
303
304           mmap_flags |= MAP_LOCKED;
305         }
306       else
307         {
308           if ((err = clib_mem_create_fd (a->name, &fd)))
309             goto error;
310         }
311
312       log2_page_size = clib_mem_get_fd_log2_page_size (fd);
313       if (log2_page_size == 0)
314         {
315           err = clib_error_return_unix (0, "cannot determine page size");
316           goto error;
317         }
318
319       if (a->requested_va)
320         {
321           clib_mem_vm_randomize_va (&a->requested_va, log2_page_size);
322           mmap_flags |= MAP_FIXED;
323         }
324     }
325   else                          /* not CLIB_MEM_VM_F_SHARED */
326     {
327       mmap_flags |= MAP_PRIVATE | MAP_ANONYMOUS;
328       if (a->flags & CLIB_MEM_VM_F_HUGETLB)
329         {
330           mmap_flags |= MAP_HUGETLB;
331           log2_page_size = 21;
332         }
333       else
334         {
335           log2_page_size = min_log2 (sysconf (_SC_PAGESIZE));
336         }
337     }
338
339   n_pages = ((a->size - 1) >> log2_page_size) + 1;
340
341   if (a->flags & CLIB_MEM_VM_F_HUGETLB_PREALLOC)
342     {
343       err = clib_sysfs_prealloc_hugepages (a->numa_node, log2_page_size,
344                                            n_pages);
345       if (err)
346         goto error;
347
348     }
349
350   if (fd != -1)
351     if ((ftruncate (fd, (u64) n_pages * (1 << log2_page_size))) == -1)
352       {
353         err = clib_error_return_unix (0, "ftruncate");
354         goto error;
355       }
356
357   if (old_mpol != -1)
358     {
359       int rv;
360       long unsigned int mask[16] = { 0 };
361       mask[0] = 1 << a->numa_node;
362       rv = set_mempolicy (MPOL_BIND, mask, sizeof (mask) * 8 + 1);
363       if (rv == -1 && a->numa_node != 0 &&
364           (a->flags & CLIB_MEM_VM_F_NUMA_FORCE) != 0)
365         {
366           err = clib_error_return_unix (0, "set_mempolicy");
367           goto error;
368         }
369     }
370
371   addr = mmap (uword_to_pointer (a->requested_va, void *), a->size,
372                (PROT_READ | PROT_WRITE), mmap_flags, fd, 0);
373   if (addr == MAP_FAILED)
374     {
375       err = clib_error_return_unix (0, "mmap");
376       goto error;
377     }
378
379   /* re-apply old numa memory policy */
380   if (old_mpol != -1 &&
381       set_mempolicy (old_mpol, old_mask, sizeof (old_mask) * 8 + 1) == -1)
382     {
383       err = clib_error_return_unix (0, "set_mempolicy");
384       goto error;
385     }
386
387   a->log2_page_size = log2_page_size;
388   a->n_pages = n_pages;
389   a->addr = addr;
390   a->fd = fd;
391   CLIB_MEM_UNPOISON (addr, a->size);
392   goto done;
393
394 error:
395   if (fd != -1)
396     close (fd);
397
398 done:
399   vec_free (filename);
400   return err;
401 }
402
403 void
404 clib_mem_vm_ext_free (clib_mem_vm_alloc_t * a)
405 {
406   if (a != 0)
407     {
408       clib_mem_vm_free (a->addr, 1ull << a->log2_page_size);
409       if (a->fd != -1)
410         close (a->fd);
411     }
412 }
413
414 uword
415 clib_mem_vm_reserve (uword start, uword size, clib_mem_page_sz_t log2_page_sz)
416 {
417   uword off, pagesize = 1ULL << log2_page_sz;
418   int mmap_flags = MAP_PRIVATE | MAP_ANONYMOUS;
419   u8 *p;
420
421   if (start)
422     mmap_flags |= MAP_FIXED;
423
424   size = round_pow2 (size, pagesize);
425
426   p = uword_to_pointer (start, void *);
427   p = mmap (p, size + pagesize, PROT_NONE, mmap_flags, -1, 0);
428
429   if (p == MAP_FAILED)
430     return ~0;
431
432   off = round_pow2 ((uword) p, pagesize) - (uword) p;
433
434   /* trim start and end of reservation to be page aligned */
435   if (off)
436     {
437       munmap (p, off);
438       p += off;
439     }
440
441   munmap (p + size, pagesize - off);
442
443   return (uword) p;
444 }
445
446 u64 *
447 clib_mem_vm_get_paddr (void *mem, int log2_page_size, int n_pages)
448 {
449   int pagesize = sysconf (_SC_PAGESIZE);
450   int fd;
451   int i;
452   u64 *r = 0;
453
454   if ((fd = open ((char *) "/proc/self/pagemap", O_RDONLY)) == -1)
455     return 0;
456
457   for (i = 0; i < n_pages; i++)
458     {
459       u64 seek, pagemap = 0;
460       uword vaddr = pointer_to_uword (mem) + (((u64) i) << log2_page_size);
461       seek = ((u64) vaddr / pagesize) * sizeof (u64);
462       if (lseek (fd, seek, SEEK_SET) != seek)
463         goto done;
464
465       if (read (fd, &pagemap, sizeof (pagemap)) != (sizeof (pagemap)))
466         goto done;
467
468       if ((pagemap & (1ULL << 63)) == 0)
469         goto done;
470
471       pagemap &= pow2_mask (55);
472       vec_add1 (r, pagemap * pagesize);
473     }
474
475 done:
476   close (fd);
477   if (vec_len (r) != n_pages)
478     {
479       vec_free (r);
480       return 0;
481     }
482   return r;
483 }
484
485 clib_error_t *
486 clib_mem_vm_ext_map (clib_mem_vm_map_t * a)
487 {
488   long unsigned int old_mask[16] = { 0 };
489   int mmap_flags = MAP_SHARED;
490   clib_error_t *err = 0;
491   int old_mpol = -1;
492   void *addr;
493   int rv;
494
495   if (a->numa_node)
496     {
497       rv = get_mempolicy (&old_mpol, old_mask, sizeof (old_mask) * 8 + 1, 0,
498                           0);
499
500       if (rv == -1)
501         {
502           err = clib_error_return_unix (0, "get_mempolicy");
503           goto done;
504         }
505     }
506
507   if (a->requested_va)
508     mmap_flags |= MAP_FIXED;
509
510   if (old_mpol != -1)
511     {
512       long unsigned int mask[16] = { 0 };
513       mask[0] = 1 << a->numa_node;
514       rv = set_mempolicy (MPOL_BIND, mask, sizeof (mask) * 8 + 1);
515       if (rv == -1)
516         {
517           err = clib_error_return_unix (0, "set_mempolicy");
518           goto done;
519         }
520     }
521
522   addr = (void *) mmap (uword_to_pointer (a->requested_va, void *), a->size,
523                         PROT_READ | PROT_WRITE, mmap_flags, a->fd, 0);
524
525   if (addr == MAP_FAILED)
526     return clib_error_return_unix (0, "mmap");
527
528   /* re-apply old numa memory policy */
529   if (old_mpol != -1 &&
530       set_mempolicy (old_mpol, old_mask, sizeof (old_mask) * 8 + 1) == -1)
531     {
532       err = clib_error_return_unix (0, "set_mempolicy");
533       goto done;
534     }
535
536   a->addr = addr;
537   CLIB_MEM_UNPOISON (addr, a->size);
538
539 done:
540   return err;
541 }
542
543 /*
544  * fd.io coding-style-patch-verification: ON
545  *
546  * Local Variables:
547  * eval: (c-set-style "gnu")
548  * End:
549  */