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