svm: fifo segment cleanup
[vpp.git] / src / svm / fifo_segment.c
1 /*
2  * Copyright (c) 2016-2019 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 #include <svm/fifo_segment.h>
17
18 static inline fifo_segment_slice_t *
19 fsh_slice_get (fifo_segment_header_t * fsh, u32 slice_index)
20 {
21   return &fsh->slices[slice_index];
22 }
23
24 static char *fifo_segment_mem_status_strings[] = {
25 #define _(sym,str) str,
26   foreach_segment_mem_status
27 #undef _
28 };
29
30 /**
31  * Fifo segment free space
32  *
33  * Queries the underlying memory manager, dlmalloc, for free space. Since this
34  * ends up walking the internal data structures, it should not be called
35  * indiscriminately.
36  *
37  * @param fs            fifo segment
38  * @return              number of free bytes
39  */
40 static uword
41 fsh_free_space (fifo_segment_header_t * fsh)
42 {
43   return clib_mem_get_heap_free_space (fsh->ssvm_sh->heap);
44 }
45
46 static inline void
47 fsh_free_bytes_sub (fifo_segment_header_t * fsh, int size)
48 {
49   clib_atomic_fetch_sub_rel (&fsh->n_free_bytes, size);
50 }
51
52 static inline uword
53 fsh_n_free_bytes (fifo_segment_header_t * fsh)
54 {
55   uword n_free = clib_atomic_load_relax_n (&fsh->n_free_bytes);
56   return n_free > fsh->n_reserved_bytes ? n_free - fsh->n_reserved_bytes : 0;
57 }
58
59 static inline void
60 fsh_update_free_bytes (fifo_segment_header_t * fsh)
61 {
62   clib_atomic_store_rel_n (&fsh->n_free_bytes, fsh_free_space (fsh));
63 }
64
65 static inline void
66 fsh_cached_bytes_add (fifo_segment_header_t * fsh, int size)
67 {
68   clib_atomic_fetch_add_rel (&fsh->n_cached_bytes, size);
69 }
70
71 static inline void
72 fsh_cached_bytes_sub (fifo_segment_header_t * fsh, int size)
73 {
74   clib_atomic_fetch_sub_rel (&fsh->n_cached_bytes, size);
75 }
76
77 static inline uword
78 fsh_n_cached_bytes (fifo_segment_header_t * fsh)
79 {
80   uword n_cached = clib_atomic_load_relax_n (&fsh->n_cached_bytes);
81   return n_cached;
82 }
83
84 static inline void
85 fsh_active_fifos_update (fifo_segment_header_t * fsh, int inc)
86 {
87   clib_atomic_fetch_add_rel (&fsh->n_active_fifos, inc);
88 }
89
90 static inline u32
91 fsh_n_active_fifos (fifo_segment_header_t * fsh)
92 {
93   return clib_atomic_load_relax_n (&fsh->n_active_fifos);
94 }
95
96 static inline uword
97 fsh_virtual_mem (fifo_segment_header_t * fsh)
98 {
99   fifo_segment_slice_t *fss;
100   uword total_vm = 0;
101   int i;
102
103   for (i = 0; i < fsh->n_slices; i++)
104     {
105       fss = fsh_slice_get (fsh, i);
106       total_vm += clib_atomic_load_relax_n (&fss->virtual_mem);
107     }
108   return total_vm;
109 }
110
111 void
112 fsh_virtual_mem_update (fifo_segment_header_t * fsh, u32 slice_index,
113                         int n_bytes)
114 {
115   fifo_segment_slice_t *fss = fsh_slice_get (fsh, slice_index);
116   fss->virtual_mem += n_bytes;
117 }
118
119 static void
120 fsh_check_mem (fifo_segment_header_t * fsh)
121 {
122   uword thresh;
123
124   if (fsh->flags & FIFO_SEGMENT_F_MEM_LIMIT)
125     return;
126
127   thresh = clib_max (0.01 * fsh->ssvm_sh->ssvm_size,
128                      2 * fsh->n_reserved_bytes);
129   if (fsh->n_free_bytes > thresh)
130     return;
131
132   fsh->flags |= FIFO_SEGMENT_F_MEM_LIMIT;
133   fsh_update_free_bytes (fsh);
134 }
135
136 static inline int
137 fss_chunk_fl_index_is_valid (fifo_segment_slice_t * fss, u32 fl_index)
138 {
139   return (fl_index < vec_len (fss->free_chunks));
140 }
141
142 static void
143 fss_chunk_free_list_push (fifo_segment_slice_t * fss, u32 fl_index,
144                           svm_fifo_chunk_t * c)
145 {
146   c->next = fss->free_chunks[fl_index];
147   fss->free_chunks[fl_index] = c;
148 }
149
150 static void
151 fss_chunk_free_list_push_list (fifo_segment_slice_t * fss, u32 fl_index,
152                                svm_fifo_chunk_t * head,
153                                svm_fifo_chunk_t * tail)
154 {
155   tail->next = fss->free_chunks[fl_index];
156   fss->free_chunks[fl_index] = head;
157 }
158
159 static svm_fifo_chunk_t *
160 fss_chunk_free_list_pop (fifo_segment_slice_t * fss, u32 fl_index)
161 {
162   svm_fifo_chunk_t *c;
163
164   ASSERT (fss_chunk_fl_index_is_valid (fss, fl_index));
165
166   if (!fss->free_chunks[fl_index])
167     return 0;
168
169   c = fss->free_chunks[fl_index];
170   fss->free_chunks[fl_index] = c->next;
171
172   return c;
173 }
174
175 static inline void
176 fss_fifo_add_active_list (fifo_segment_slice_t * fss, svm_fifo_t * f)
177 {
178   if (fss->fifos)
179     {
180       fss->fifos->prev = f;
181       f->next = fss->fifos;
182     }
183   fss->fifos = f;
184 }
185
186 static inline void
187 fss_fifo_del_active_list (fifo_segment_slice_t * fss, svm_fifo_t * f)
188 {
189   if (f->flags & SVM_FIFO_F_LL_TRACKED)
190     {
191       if (f->prev)
192         f->prev->next = f->next;
193       else
194         fss->fifos = f->next;
195       if (f->next)
196         f->next->prev = f->prev;
197     }
198 }
199
200 /**
201  * Initialize fifo segment shared header
202  */
203 int
204 fifo_segment_init (fifo_segment_t * fs)
205 {
206   fifo_segment_header_t *fsh;
207   fifo_segment_slice_t *fss;
208   ssvm_shared_header_t *sh;
209   u32 max_chunk_sz;
210   uword max_fifo;
211   void *oldheap;
212   int i;
213
214   sh = fs->ssvm.sh;
215   oldheap = ssvm_push_heap (sh);
216
217   /*
218    * Manually align the fifo segment header to sizeof(uword) = 8 bytes.
219    * Long story made short: the "process-private" fifo segment
220    * is allocated from the main heap, not mmapped. dlmalloc
221    * only guarantees 4-byte alignment, and on aarch64
222    * the fsh can end up 4-byte but not 8-byte aligned.
223    * That eventually causes the atomic op in fifo_segment_update_free_bytes
224    * to backfire.
225    */
226   fsh = clib_mem_alloc_aligned (sizeof (*fsh), sizeof (uword));
227   clib_memset (fsh, 0, sizeof (*fsh));
228   fs->h = sh->opaque[0] = fsh;
229   fs->n_slices = clib_max (fs->n_slices, 1);
230
231   fsh->ssvm_sh = fs->ssvm.sh;
232   fsh->n_slices = fs->n_slices;
233   max_fifo = clib_min ((fsh_free_space (fsh) - 4096) / 2,
234                        FIFO_SEGMENT_MAX_FIFO_SIZE);
235   fsh->max_log2_chunk_size = max_log2 (max_fifo);
236
237   fsh->slices = clib_mem_alloc (sizeof (*fss) * fs->n_slices);
238   clib_memset (fsh->slices, 0, sizeof (*fss) * fs->n_slices);
239   max_chunk_sz = fsh->max_log2_chunk_size - FIFO_SEGMENT_MIN_LOG2_FIFO_SIZE;
240
241   for (i = 0; i < fs->n_slices; i++)
242     {
243       fss = fsh_slice_get (fsh, i);
244       vec_validate_init_empty (fss->free_chunks, max_chunk_sz, 0);
245       vec_validate_init_empty (fss->num_chunks, max_chunk_sz, 0);
246       clib_spinlock_init (&fss->chunk_lock);
247     }
248
249   ssvm_pop_heap (oldheap);
250
251   fsh->n_free_bytes = fsh_free_space (fsh);
252   fsh->n_cached_bytes = 0;
253   fsh->n_reserved_bytes = clib_min (0.01 * fsh->n_free_bytes, 256 << 10);
254   sh->ready = 1;
255   return (0);
256 }
257
258 /**
259  * Create a fifo segment and initialize as master
260  */
261 int
262 fifo_segment_create (fifo_segment_main_t * sm, fifo_segment_create_args_t * a)
263 {
264   fifo_segment_t *fs;
265   uword baseva;
266   int rv;
267
268   /* Allocate a fresh segment */
269   pool_get_zero (sm->segments, fs);
270
271   baseva = a->segment_type == SSVM_SEGMENT_PRIVATE ? ~0ULL : sm->next_baseva;
272   fs->ssvm.ssvm_size = a->segment_size;
273   fs->ssvm.is_server = 1;
274   fs->ssvm.my_pid = getpid ();
275   fs->ssvm.name = format (0, "%s%c", a->segment_name, 0);
276   fs->ssvm.requested_va = baseva;
277
278   if ((rv = ssvm_server_init (&fs->ssvm, a->segment_type)))
279     {
280       pool_put (sm->segments, fs);
281       return (rv);
282     }
283
284   /* Note: requested_va updated due to seg base addr randomization */
285   sm->next_baseva = fs->ssvm.sh->ssvm_va + fs->ssvm.ssvm_size;
286
287   fifo_segment_init (fs);
288   vec_add1 (a->new_segment_indices, fs - sm->segments);
289   return (0);
290 }
291
292 /**
293  * Attach as slave to a fifo segment
294  */
295 int
296 fifo_segment_attach (fifo_segment_main_t * sm, fifo_segment_create_args_t * a)
297 {
298   fifo_segment_t *fs;
299   int rv;
300
301   pool_get_zero (sm->segments, fs);
302
303   fs->ssvm.ssvm_size = a->segment_size;
304   fs->ssvm.my_pid = getpid ();
305   fs->ssvm.name = format (0, "%s%c", a->segment_name, 0);
306   fs->ssvm.requested_va = sm->next_baseva;
307   if (a->segment_type == SSVM_SEGMENT_MEMFD)
308     fs->ssvm.fd = a->memfd_fd;
309   else
310     fs->ssvm.attach_timeout = sm->timeout_in_seconds;
311
312   if ((rv = ssvm_client_init (&fs->ssvm, a->segment_type)))
313     {
314       _vec_len (fs) = vec_len (fs) - 1;
315       return (rv);
316     }
317
318   /* Fish the segment header */
319   fs->h = fs->ssvm.sh->opaque[0];
320
321   vec_add1 (a->new_segment_indices, fs - sm->segments);
322   return (0);
323 }
324
325 void
326 fifo_segment_delete (fifo_segment_main_t * sm, fifo_segment_t * s)
327 {
328   ssvm_delete (&s->ssvm);
329   clib_memset (s, 0xfe, sizeof (*s));
330   pool_put (sm->segments, s);
331 }
332
333 u32
334 fifo_segment_index (fifo_segment_main_t * sm, fifo_segment_t * s)
335 {
336   return s - sm->segments;
337 }
338
339 fifo_segment_t *
340 fifo_segment_get_segment (fifo_segment_main_t * sm, u32 segment_index)
341 {
342   return pool_elt_at_index (sm->segments, segment_index);
343 }
344
345 void
346 fifo_segment_info (fifo_segment_t * seg, char **address, size_t * size)
347 {
348   *address = (char *) seg->ssvm.sh->ssvm_va;
349   *size = seg->ssvm.ssvm_size;
350 }
351
352 void
353 fifo_segment_main_init (fifo_segment_main_t * sm, u64 baseva,
354                         u32 timeout_in_seconds)
355 {
356   sm->next_baseva = baseva;
357   sm->timeout_in_seconds = timeout_in_seconds;
358 }
359
360 static inline u32
361 fs_freelist_for_size (u32 size)
362 {
363   if (PREDICT_FALSE (size < FIFO_SEGMENT_MIN_FIFO_SIZE))
364     return 0;
365   return max_log2 (size) - FIFO_SEGMENT_MIN_LOG2_FIFO_SIZE;
366 }
367
368 static inline u32
369 fs_freelist_index_to_size (u32 fl_index)
370 {
371   return 1 << (fl_index + FIFO_SEGMENT_MIN_LOG2_FIFO_SIZE);
372 }
373
374 static inline int
375 fs_chunk_size_is_valid (fifo_segment_header_t * fsh, u32 size)
376 {
377   /*
378    * 4K minimum. It's not likely that anything good will happen
379    * with a smaller FIFO.
380    */
381   return size >= FIFO_SEGMENT_MIN_FIFO_SIZE
382     && size <= (1ULL << fsh->max_log2_chunk_size);
383 }
384
385 svm_fifo_chunk_t *
386 fs_try_alloc_multi_chunk (fifo_segment_header_t * fsh,
387                           fifo_segment_slice_t * fss, u32 data_bytes)
388 {
389   u32 fl_index, fl_size, n_alloc = 0, req_bytes = data_bytes;
390   svm_fifo_chunk_t *c, *first = 0, *next;
391
392   fl_index = fs_freelist_for_size (req_bytes);
393   if (fl_index > 0)
394     fl_index -= 1;
395
396   fl_size = fs_freelist_index_to_size (fl_index);
397
398   while (req_bytes)
399     {
400       c = fss_chunk_free_list_pop (fss, fl_index);
401       if (c)
402         {
403           c->next = first;
404           first = c;
405           n_alloc += fl_size;
406           req_bytes -= clib_min (fl_size, req_bytes);
407         }
408       else
409         {
410           /* Failed to allocate with smaller chunks */
411           if (fl_index == 0)
412             {
413               /* Free all chunks if any allocated */
414               c = first;
415               while (c)
416                 {
417                   fl_index = fs_freelist_for_size (c->length);
418                   next = c->next;
419                   fss_chunk_free_list_push (fss, fl_index, c);
420                   c = next;
421                 }
422               n_alloc = 0;
423               first = 0;
424               /* As last attempt, try allocating a chunk larger than
425                * the requested size, if possible */
426               fl_index = fs_freelist_for_size (data_bytes) + 1;
427               if (!fss_chunk_fl_index_is_valid (fss, fl_index))
428                 return 0;
429               first = fss_chunk_free_list_pop (fss, fl_index);
430               if (first)
431                 {
432                   first->next = 0;
433                   n_alloc = fs_freelist_index_to_size (fl_index);
434                   goto done;
435                 }
436               return 0;
437             }
438           fl_index -= 1;
439           fl_size = fl_size >> 1;
440         }
441     }
442
443 done:
444   fss->n_fl_chunk_bytes -= n_alloc;
445   fsh_cached_bytes_sub (fsh, n_alloc);
446   return first;
447 }
448
449 static int
450 fsh_try_alloc_fifo_hdr_batch (fifo_segment_header_t * fsh,
451                               fifo_segment_slice_t * fss, u32 batch_size)
452 {
453   svm_fifo_t *f;
454   void *oldheap;
455   uword size;
456   u8 *fmem;
457   int i;
458
459   size = (uword) sizeof (*f) * batch_size;
460
461   oldheap = ssvm_push_heap (fsh->ssvm_sh);
462   fmem = clib_mem_alloc_aligned_at_offset (size, CLIB_CACHE_LINE_BYTES,
463                                            0 /* align_offset */ ,
464                                            0 /* os_out_of_memory */ );
465   ssvm_pop_heap (oldheap);
466
467   /* Out of space.. */
468   if (fmem == 0)
469     return -1;
470
471   /* Carve fifo hdr space */
472   for (i = 0; i < batch_size; i++)
473     {
474       f = (svm_fifo_t *) fmem;
475       memset (f, 0, sizeof (*f));
476       f->next = fss->free_fifos;
477       fss->free_fifos = f;
478       fmem += sizeof (*f);
479     }
480
481   return 0;
482 }
483
484 static int
485 fsh_try_alloc_chunk_batch (fifo_segment_header_t * fsh,
486                            fifo_segment_slice_t * fss,
487                            u32 fl_index, u32 batch_size)
488 {
489   u32 rounded_data_size;
490   svm_fifo_chunk_t *c, *head = 0, *tail;
491   void *oldheap;
492   uword size;
493   u8 *cmem;
494   int i;
495
496   rounded_data_size = fs_freelist_index_to_size (fl_index);
497   size = (uword) (sizeof (*c) + rounded_data_size) * batch_size;
498
499   oldheap = ssvm_push_heap (fsh->ssvm_sh);
500   cmem = clib_mem_alloc_aligned_at_offset (size, CLIB_CACHE_LINE_BYTES,
501                                            0 /* align_offset */ ,
502                                            0 /* os_out_of_memory */ );
503   ssvm_pop_heap (oldheap);
504
505   /* Out of space.. */
506   if (cmem == 0)
507     return -1;
508
509   /* Carve fifo + chunk space */
510   tail = c = (svm_fifo_chunk_t *) cmem;
511   for (i = 0; i < batch_size; i++)
512     {
513       c->start_byte = 0;
514       c->length = rounded_data_size;
515       c->enq_rb_index = RBTREE_TNIL_INDEX;
516       c->deq_rb_index = RBTREE_TNIL_INDEX;
517       c->next = head;
518       head = c;
519       cmem += sizeof (*c) + rounded_data_size;
520       c = (svm_fifo_chunk_t *) cmem;
521     }
522
523   fss_chunk_free_list_push_list (fss, fl_index, head, tail);
524   fss->num_chunks[fl_index] += batch_size;
525   fss->n_fl_chunk_bytes += batch_size * rounded_data_size;
526   fsh_cached_bytes_add (fsh, batch_size * rounded_data_size);
527   fsh_free_bytes_sub (fsh, size);
528
529   return 0;
530 }
531
532 static int
533 fs_try_alloc_fifo_batch (fifo_segment_header_t * fsh,
534                          fifo_segment_slice_t * fss,
535                          u32 fl_index, u32 batch_size)
536 {
537   if (fsh_try_alloc_fifo_hdr_batch (fsh, fss, batch_size))
538     return 0;
539   return fsh_try_alloc_chunk_batch (fsh, fss, fl_index, batch_size);
540 }
541
542 static svm_fifo_t *
543 fsh_try_alloc_fifo_hdr (fifo_segment_header_t * fsh,
544                         fifo_segment_slice_t * fss)
545 {
546   svm_fifo_t *f;
547
548   if (!fss->free_fifos)
549     {
550       if (fsh_try_alloc_fifo_hdr_batch (fsh, fss,
551                                         FIFO_SEGMENT_ALLOC_BATCH_SIZE))
552         return 0;
553     }
554
555   f = fss->free_fifos;
556   fss->free_fifos = f->next;
557   memset (f, 0, sizeof (*f));
558   return f;
559 }
560
561 static svm_fifo_chunk_t *
562 fsh_try_alloc_chunk (fifo_segment_header_t * fsh,
563                      fifo_segment_slice_t * fss, u32 data_bytes)
564 {
565   svm_fifo_chunk_t *c;
566   u32 fl_index;
567
568   fl_index = fs_freelist_for_size (data_bytes);
569
570 free_list:
571   c = fss_chunk_free_list_pop (fss, fl_index);
572   if (c)
573     {
574       c->next = 0;
575       fss->n_fl_chunk_bytes -= fs_freelist_index_to_size (fl_index);
576       fsh_cached_bytes_sub (fsh, fs_freelist_index_to_size (fl_index));
577     }
578   else
579     {
580       u32 chunk_size, batch = FIFO_SEGMENT_ALLOC_BATCH_SIZE;
581       uword n_free;
582
583       chunk_size = fs_freelist_index_to_size (fl_index);
584       n_free = fsh_n_free_bytes (fsh);
585
586       if (chunk_size <= n_free)
587         {
588           batch = chunk_size * batch <= n_free ? batch : 1;
589           if (!fsh_try_alloc_chunk_batch (fsh, fss, fl_index, batch))
590             goto free_list;
591         }
592       /* Failed to allocate larger chunk, try to allocate multi-chunk
593        * that is close to what was actually requested */
594       if (data_bytes <= fss->n_fl_chunk_bytes)
595         {
596           c = fs_try_alloc_multi_chunk (fsh, fss, data_bytes);
597           if (c)
598             goto done;
599           batch = n_free / FIFO_SEGMENT_MIN_FIFO_SIZE;
600           if (!batch || fsh_try_alloc_chunk_batch (fsh, fss, 0, batch))
601             {
602               fsh_check_mem (fsh);
603               goto done;
604             }
605         }
606       if (data_bytes <= fss->n_fl_chunk_bytes + n_free)
607         {
608           u32 min_size = FIFO_SEGMENT_MIN_FIFO_SIZE;
609
610           batch = (data_bytes - fss->n_fl_chunk_bytes) / min_size;
611           batch = clib_min (batch + 1, n_free / min_size);
612           if (fsh_try_alloc_chunk_batch (fsh, fss, 0, batch))
613             {
614               fsh_check_mem (fsh);
615               goto done;
616             }
617           c = fs_try_alloc_multi_chunk (fsh, fss, data_bytes);
618         }
619     }
620
621 done:
622
623   return c;
624 }
625
626 /**
627  * Try to allocate new fifo
628  *
629  * Tries the following steps in order:
630  * - grab fifo and chunk from freelists
631  * - batch fifo and chunk allocation
632  * - single fifo allocation
633  * - grab multiple fifo chunks from freelists
634  */
635 static svm_fifo_t *
636 fs_try_alloc_fifo (fifo_segment_header_t * fsh, fifo_segment_slice_t * fss,
637                    u32 data_bytes)
638 {
639   u32 fl_index;
640   svm_fifo_chunk_t *c;
641   svm_fifo_t *f = 0;
642   u32 min_size;
643
644   min_size = clib_max ((fsh->pct_first_alloc * data_bytes) / 100, 4096);
645   fl_index = fs_freelist_for_size (min_size);
646
647   if (!fss_chunk_fl_index_is_valid (fss, fl_index))
648     return 0;
649
650   clib_spinlock_lock (&fss->chunk_lock);
651
652   f = fsh_try_alloc_fifo_hdr (fsh, fss);
653   if (!f)
654     goto done;
655
656   c = fsh_try_alloc_chunk (fsh, fss, min_size);
657   if (c)
658     {
659       f->start_chunk = c;
660       while (c->next)
661         c = c->next;
662       f->end_chunk = c;
663     }
664   else
665     {
666       f->next = fss->free_fifos;
667       fss->free_fifos = f;
668       f = 0;
669     }
670
671 done:
672   clib_spinlock_unlock (&fss->chunk_lock);
673
674   if (f)
675     {
676       f->size = data_bytes;
677       f->fs_hdr = fsh;
678     }
679   return f;
680 }
681
682 svm_fifo_chunk_t *
683 fsh_alloc_chunk (fifo_segment_header_t * fsh, u32 slice_index, u32 chunk_size)
684 {
685   fifo_segment_slice_t *fss;
686   svm_fifo_chunk_t *c;
687
688   fss = fsh_slice_get (fsh, slice_index);
689
690   clib_spinlock_lock (&fss->chunk_lock);
691   c = fsh_try_alloc_chunk (fsh, fss, chunk_size);
692   clib_spinlock_unlock (&fss->chunk_lock);
693
694   return c;
695 }
696
697 static void
698 fsh_slice_collect_chunks (fifo_segment_header_t * fsh,
699                           fifo_segment_slice_t * fss, svm_fifo_chunk_t * c)
700 {
701   u32 n_collect = 0, fl_index;
702   svm_fifo_chunk_t *next;
703
704   clib_spinlock_lock (&fss->chunk_lock);
705
706   while (c)
707     {
708       CLIB_MEM_UNPOISON (c, sizeof (*c));
709       next = c->next;
710       fl_index = fs_freelist_for_size (c->length);
711       c->enq_rb_index = RBTREE_TNIL_INDEX;
712       c->deq_rb_index = RBTREE_TNIL_INDEX;
713       fss_chunk_free_list_push (fss, fl_index, c);
714       n_collect += fs_freelist_index_to_size (fl_index);
715       c = next;
716     }
717
718   fss->n_fl_chunk_bytes += n_collect;
719   fsh_cached_bytes_add (fsh, n_collect);
720
721   clib_spinlock_unlock (&fss->chunk_lock);
722 }
723
724 void
725 fsh_collect_chunks (fifo_segment_header_t * fsh, u32 slice_index,
726                     svm_fifo_chunk_t * c)
727 {
728   fifo_segment_slice_t *fss;
729   fss = fsh_slice_get (fsh, slice_index);
730   fsh_slice_collect_chunks (fsh, fss, c);
731 }
732
733 /**
734  * Allocate fifo in fifo segment
735  */
736 svm_fifo_t *
737 fifo_segment_alloc_fifo_w_slice (fifo_segment_t * fs, u32 slice_index,
738                                  u32 data_bytes, fifo_segment_ftype_t ftype)
739 {
740   fifo_segment_header_t *fsh = fs->h;
741   fifo_segment_slice_t *fss;
742   svm_fifo_t *f = 0;
743
744   ASSERT (slice_index < fs->n_slices);
745
746   if (PREDICT_FALSE (data_bytes > 1 << fsh->max_log2_chunk_size))
747     return 0;
748
749   fss = fsh_slice_get (fsh, slice_index);
750   f = fs_try_alloc_fifo (fsh, fss, data_bytes);
751   if (!f)
752     goto done;
753
754   f->slice_index = slice_index;
755
756   svm_fifo_init (f, data_bytes);
757
758   /* If rx fifo type add to active fifos list. When cleaning up segment,
759    * we need a list of active sessions that should be disconnected. Since
760    * both rx and tx fifos keep pointers to the session, it's enough to track
761    * only one. */
762   if (ftype == FIFO_SEGMENT_RX_FIFO)
763     {
764       fss_fifo_add_active_list (fss, f);
765       f->flags |= SVM_FIFO_F_LL_TRACKED;
766     }
767
768   fsh_active_fifos_update (fsh, 1);
769   fss->virtual_mem += svm_fifo_size (f);
770
771 done:
772   return (f);
773 }
774
775 /**
776  * Free fifo allocated in fifo segment
777  */
778 void
779 fifo_segment_free_fifo (fifo_segment_t * fs, svm_fifo_t * f)
780 {
781   fifo_segment_header_t *fsh = fs->h;
782   fifo_segment_slice_t *fss;
783
784   ASSERT (f->refcnt > 0);
785
786   if (--f->refcnt > 0)
787     return;
788
789   fss = fsh_slice_get (fsh, f->slice_index);
790
791   /* Remove from active list. Only rx fifos are tracked */
792   if (f->flags & SVM_FIFO_F_LL_TRACKED)
793     {
794       fss_fifo_del_active_list (fss, f);
795       f->flags &= ~SVM_FIFO_F_LL_TRACKED;
796     }
797
798   /* Free fifo chunks */
799   fsh_slice_collect_chunks (fsh, fss, f->start_chunk);
800
801   f->start_chunk = f->end_chunk = 0;
802   f->head_chunk = f->tail_chunk = f->ooo_enq = f->ooo_deq = 0;
803
804   /* not allocated on segment heap */
805   svm_fifo_free_chunk_lookup (f);
806   svm_fifo_free_ooo_data (f);
807
808   if (CLIB_DEBUG)
809     {
810       f->master_session_index = ~0;
811       f->master_thread_index = ~0;
812     }
813
814   fss->virtual_mem -= svm_fifo_size (f);
815
816   /* Add to free list */
817   f->next = fss->free_fifos;
818   f->prev = 0;
819   fss->free_fifos = f;
820
821   fsh_active_fifos_update (fsh, -1);
822 }
823
824 void
825 fifo_segment_detach_fifo (fifo_segment_t * fs, svm_fifo_t * f)
826 {
827   fifo_segment_slice_t *fss;
828   svm_fifo_chunk_t *c;
829   u32 fl_index;
830
831   ASSERT (f->refcnt == 1);
832
833   fss = fsh_slice_get (fs->h, f->slice_index);
834   fss->virtual_mem -= svm_fifo_size (f);
835   if (f->flags & SVM_FIFO_F_LL_TRACKED)
836     fss_fifo_del_active_list (fss, f);
837
838   c = f->start_chunk;
839   while (c)
840     {
841       fl_index = fs_freelist_for_size (c->length);
842       clib_atomic_fetch_sub_rel (&fss->num_chunks[fl_index], 1);
843       c = c->next;
844     }
845 }
846
847 void
848 fifo_segment_attach_fifo (fifo_segment_t * fs, svm_fifo_t * f,
849                           u32 slice_index)
850 {
851   fifo_segment_slice_t *fss;
852   svm_fifo_chunk_t *c;
853   u32 fl_index;
854
855   f->slice_index = slice_index;
856   fss = fsh_slice_get (fs->h, f->slice_index);
857   fss->virtual_mem += svm_fifo_size (f);
858   if (f->flags & SVM_FIFO_F_LL_TRACKED)
859     fss_fifo_add_active_list (fss, f);
860
861   c = f->start_chunk;
862   while (c)
863     {
864       fl_index = fs_freelist_for_size (c->length);
865       clib_atomic_fetch_add_rel (&fss->num_chunks[fl_index], 1);
866       c = c->next;
867     }
868 }
869
870 int
871 fifo_segment_prealloc_fifo_hdrs (fifo_segment_t * fs, u32 slice_index,
872                                  u32 batch_size)
873 {
874   fifo_segment_header_t *fsh = fs->h;
875   fifo_segment_slice_t *fss;
876   svm_fifo_t *f;
877   void *oldheap;
878   uword size;
879   u8 *fmem;
880   int i;
881
882   fss = fsh_slice_get (fsh, slice_index);
883   size = (uword) (sizeof (*f)) * batch_size;
884
885   oldheap = ssvm_push_heap (fsh->ssvm_sh);
886   fmem = clib_mem_alloc_aligned_at_offset (size, CLIB_CACHE_LINE_BYTES,
887                                            0 /* align_offset */ ,
888                                            0 /* os_out_of_memory */ );
889   ssvm_pop_heap (oldheap);
890
891   /* Out of space.. */
892   if (fmem == 0)
893     return -1;
894
895   /* Carve fifo + chunk space */
896   for (i = 0; i < batch_size; i++)
897     {
898       f = (svm_fifo_t *) fmem;
899       memset (f, 0, sizeof (*f));
900       f->next = fss->free_fifos;
901       fss->free_fifos = f;
902       fmem += sizeof (*f);
903     }
904
905   fsh_free_bytes_sub (fsh, size);
906
907   return 0;
908 }
909
910 int
911 fifo_segment_prealloc_fifo_chunks (fifo_segment_t * fs, u32 slice_index,
912                                    u32 chunk_size, u32 batch_size)
913 {
914   fifo_segment_header_t *fsh = fs->h;
915   fifo_segment_slice_t *fss;
916   u32 fl_index;
917
918   if (!fs_chunk_size_is_valid (fsh, chunk_size))
919     {
920       clib_warning ("chunk size out of range %d", chunk_size);
921       return -1;
922     }
923
924   fl_index = fs_freelist_for_size (chunk_size);
925   fss = fsh_slice_get (fsh, slice_index);
926
927   return fsh_try_alloc_chunk_batch (fsh, fss, fl_index, batch_size);
928 }
929
930 /**
931  * Pre-allocates fifo pairs in fifo segment
932  */
933 void
934 fifo_segment_preallocate_fifo_pairs (fifo_segment_t * fs,
935                                      u32 rx_fifo_size, u32 tx_fifo_size,
936                                      u32 * n_fifo_pairs)
937 {
938   u32 rx_rounded_data_size, tx_rounded_data_size, pair_size, pairs_to_alloc;
939   u32 hdrs, pairs_per_slice, alloc_now;
940   fifo_segment_header_t *fsh = fs->h;
941   int rx_fl_index, tx_fl_index, i;
942   fifo_segment_slice_t *fss;
943   uword space_available;
944
945   /* Parameter check */
946   if (rx_fifo_size == 0 || tx_fifo_size == 0 || *n_fifo_pairs == 0)
947     return;
948
949   if (!fs_chunk_size_is_valid (fsh, rx_fifo_size))
950     {
951       clib_warning ("rx fifo_size out of range %d", rx_fifo_size);
952       return;
953     }
954
955   if (!fs_chunk_size_is_valid (fsh, tx_fifo_size))
956     {
957       clib_warning ("tx fifo_size out of range %d", tx_fifo_size);
958       return;
959     }
960
961   rx_rounded_data_size = (1 << (max_log2 (rx_fifo_size)));
962   rx_fl_index = fs_freelist_for_size (rx_fifo_size);
963   tx_rounded_data_size = (1 << (max_log2 (tx_fifo_size)));
964   tx_fl_index = fs_freelist_for_size (tx_fifo_size);
965
966   hdrs = sizeof (svm_fifo_t) + sizeof (svm_fifo_chunk_t);
967
968   /* Calculate space requirements */
969   pair_size = 2 * hdrs + rx_rounded_data_size + tx_rounded_data_size;
970   space_available = fsh_free_space (fsh);
971   pairs_to_alloc = space_available / pair_size;
972   pairs_to_alloc = clib_min (pairs_to_alloc, *n_fifo_pairs);
973   pairs_per_slice = pairs_to_alloc / fs->n_slices;
974   pairs_per_slice += pairs_to_alloc % fs->n_slices ? 1 : 0;
975
976   if (!pairs_per_slice)
977     return;
978
979   for (i = 0; i < fs->n_slices; i++)
980     {
981       fss = fsh_slice_get (fsh, i);
982       alloc_now = clib_min (pairs_per_slice, *n_fifo_pairs);
983       if (fs_try_alloc_fifo_batch (fsh, fss, rx_fl_index, alloc_now))
984         clib_warning ("rx prealloc failed: pairs %u", alloc_now);
985       if (fs_try_alloc_fifo_batch (fsh, fss, tx_fl_index, alloc_now))
986         clib_warning ("tx prealloc failed: pairs %u", alloc_now);
987
988       /* Account for the pairs allocated */
989       *n_fifo_pairs -= alloc_now;
990     }
991 }
992
993 /**
994  * Get number of active fifos
995  */
996 u32
997 fifo_segment_num_fifos (fifo_segment_t * fs)
998 {
999   return fsh_n_active_fifos (fs->h);
1000 }
1001
1002 static u32
1003 fs_slice_num_free_fifos (fifo_segment_slice_t * fss)
1004 {
1005   svm_fifo_t *f;
1006   u32 count = 0;
1007
1008   f = fss->free_fifos;
1009   if (f == 0)
1010     return 0;
1011
1012   while (f)
1013     {
1014       f = f->next;
1015       count++;
1016     }
1017   return count;
1018 }
1019
1020 u32
1021 fifo_segment_num_free_fifos (fifo_segment_t * fs)
1022 {
1023   fifo_segment_header_t *fsh = fs->h;
1024   fifo_segment_slice_t *fss;
1025   int slice_index;
1026   u32 count = 0;
1027
1028   for (slice_index = 0; slice_index < fs->n_slices; slice_index++)
1029     {
1030       fss = fsh_slice_get (fsh, slice_index);
1031       count += fs_slice_num_free_fifos (fss);
1032     }
1033   return count;
1034 }
1035
1036 static u32
1037 fs_slice_num_free_chunks (fifo_segment_slice_t * fss, u32 size)
1038 {
1039   u32 count = 0, rounded_size, fl_index;
1040   svm_fifo_chunk_t *c;
1041   int i;
1042
1043   /* Count all free chunks? */
1044   if (size == ~0)
1045     {
1046       for (i = 0; i < vec_len (fss->free_chunks); i++)
1047         {
1048           c = fss->free_chunks[i];
1049           if (c == 0)
1050             continue;
1051
1052           while (c)
1053             {
1054               c = c->next;
1055               count++;
1056             }
1057         }
1058       return count;
1059     }
1060
1061   rounded_size = (1 << (max_log2 (size)));
1062   fl_index = fs_freelist_for_size (rounded_size);
1063
1064   if (fl_index >= vec_len (fss->free_chunks))
1065     return 0;
1066
1067   c = fss->free_chunks[fl_index];
1068   if (c == 0)
1069     return 0;
1070
1071   while (c)
1072     {
1073       c = c->next;
1074       count++;
1075     }
1076   return count;
1077 }
1078
1079 u32
1080 fifo_segment_num_free_chunks (fifo_segment_t * fs, u32 size)
1081 {
1082   fifo_segment_header_t *fsh = fs->h;
1083   fifo_segment_slice_t *fss;
1084   int slice_index;
1085   u32 count = 0;
1086
1087   for (slice_index = 0; slice_index < fs->n_slices; slice_index++)
1088     {
1089       fss = fsh_slice_get (fsh, slice_index);
1090       count += fs_slice_num_free_chunks (fss, size);
1091     }
1092   return count;
1093 }
1094
1095 void
1096 fifo_segment_update_free_bytes (fifo_segment_t * fs)
1097 {
1098   fsh_update_free_bytes (fs->h);
1099 }
1100
1101 uword
1102 fifo_segment_size (fifo_segment_t * fs)
1103 {
1104   return fs->ssvm.ssvm_size;
1105 }
1106
1107 u8
1108 fsh_has_reached_mem_limit (fifo_segment_header_t * fsh)
1109 {
1110   return (fsh->flags & FIFO_SEGMENT_F_MEM_LIMIT) ? 1 : 0;
1111 }
1112
1113 void
1114 fsh_reset_mem_limit (fifo_segment_header_t * fsh)
1115 {
1116   fsh->flags &= ~FIFO_SEGMENT_F_MEM_LIMIT;
1117 }
1118
1119 uword
1120 fifo_segment_free_bytes (fifo_segment_t * fs)
1121 {
1122   return fsh_n_free_bytes (fs->h);
1123 }
1124
1125 uword
1126 fifo_segment_cached_bytes (fifo_segment_t * fs)
1127 {
1128   return fsh_n_cached_bytes (fs->h);
1129 }
1130
1131 uword
1132 fifo_segment_available_bytes (fifo_segment_t * fs)
1133 {
1134   return fsh_n_free_bytes (fs->h) + fsh_n_cached_bytes (fs->h);
1135 }
1136
1137 uword
1138 fifo_segment_fl_chunk_bytes (fifo_segment_t * fs)
1139 {
1140   fifo_segment_header_t *fsh = fs->h;
1141   fifo_segment_slice_t *fss;
1142   uword n_bytes = 0;
1143   int slice_index;
1144
1145   for (slice_index = 0; slice_index < fs->n_slices; slice_index++)
1146     {
1147       fss = fsh_slice_get (fsh, slice_index);
1148       n_bytes += fss->n_fl_chunk_bytes;
1149     }
1150
1151   return n_bytes;
1152 }
1153
1154 u8
1155 fifo_segment_has_fifos (fifo_segment_t * fs)
1156 {
1157   return (fsh_n_active_fifos (fs->h) != 0);
1158 }
1159
1160 svm_fifo_t *
1161 fifo_segment_get_slice_fifo_list (fifo_segment_t * fs, u32 slice_index)
1162 {
1163   fifo_segment_header_t *fsh = fs->h;
1164   fifo_segment_slice_t *fss;
1165
1166   fss = fsh_slice_get (fsh, slice_index);
1167   return fss->fifos;
1168 }
1169
1170 u8
1171 fifo_segment_get_mem_usage (fifo_segment_t * fs)
1172 {
1173   uword size, in_use;
1174
1175   size = fifo_segment_size (fs);
1176   in_use =
1177     size - fifo_segment_free_bytes (fs) - fifo_segment_cached_bytes (fs);
1178   return (in_use * 100) / size;
1179 }
1180
1181 fifo_segment_mem_status_t
1182 fifo_segment_determine_status (fifo_segment_header_t * fsh, u8 usage)
1183 {
1184   if (!fsh->high_watermark || !fsh->low_watermark)
1185     return MEMORY_PRESSURE_NO_PRESSURE;
1186
1187   /* once the no-memory is detected, the status continues
1188    * until memory usage gets below the high watermark
1189    */
1190   if (fsh_has_reached_mem_limit (fsh))
1191     {
1192       if (usage >= fsh->high_watermark)
1193         return MEMORY_PRESSURE_NO_MEMORY;
1194       else
1195         fsh_reset_mem_limit (fsh);
1196     }
1197
1198   if (usage >= fsh->high_watermark)
1199     return MEMORY_PRESSURE_HIGH_PRESSURE;
1200
1201   else if (usage >= fsh->low_watermark)
1202     return MEMORY_PRESSURE_LOW_PRESSURE;
1203
1204   return MEMORY_PRESSURE_NO_PRESSURE;
1205 }
1206
1207 fifo_segment_mem_status_t
1208 fifo_segment_get_mem_status (fifo_segment_t * fs)
1209 {
1210   fifo_segment_header_t *fsh = fs->h;
1211   u8 usage = fifo_segment_get_mem_usage (fs);
1212
1213   return fifo_segment_determine_status (fsh, usage);
1214 }
1215
1216 u8 *
1217 format_fifo_segment_type (u8 * s, va_list * args)
1218 {
1219   fifo_segment_t *sp;
1220   sp = va_arg (*args, fifo_segment_t *);
1221   ssvm_segment_type_t st = ssvm_type (&sp->ssvm);
1222
1223   if (st == SSVM_SEGMENT_PRIVATE)
1224     s = format (s, "%s", "private");
1225   else if (st == SSVM_SEGMENT_MEMFD)
1226     s = format (s, "%s", "memfd");
1227   else if (st == SSVM_SEGMENT_SHM)
1228     s = format (s, "%s", "shm");
1229   else
1230     s = format (s, "%s", "unknown");
1231   return s;
1232 }
1233
1234 /**
1235  * Segment format function
1236  */
1237 u8 *
1238 format_fifo_segment (u8 * s, va_list * args)
1239 {
1240   u32 count, indent, active_fifos, free_fifos;
1241   fifo_segment_t *fs = va_arg (*args, fifo_segment_t *);
1242   int verbose __attribute__ ((unused)) = va_arg (*args, int);
1243   uword est_chunk_bytes, est_free_seg_bytes, free_chunks;
1244   uword chunk_bytes = 0, free_seg_bytes, chunk_size;
1245   uword tracked_cached_bytes;
1246   uword fifo_hdr = 0, reserved;
1247   fifo_segment_header_t *fsh;
1248   fifo_segment_slice_t *fss;
1249   svm_fifo_chunk_t *c;
1250   u32 slice_index;
1251   char *address;
1252   size_t size;
1253   int i;
1254   uword allocated, in_use, virt;
1255   f64 usage;
1256   fifo_segment_mem_status_t mem_st;
1257
1258   indent = format_get_indent (s) + 2;
1259
1260   if (fs == 0)
1261     {
1262       s = format (s, "%-20s%10s%15s%15s%15s%15s", "Name", "Type",
1263                   "HeapSize (M)", "ActiveFifos", "FreeFifos", "Address");
1264       return s;
1265     }
1266
1267   fifo_segment_info (fs, &address, &size);
1268   active_fifos = fifo_segment_num_fifos (fs);
1269   free_fifos = fifo_segment_num_free_fifos (fs);
1270
1271   s = format (s, "%-20v%10U%15llu%15u%15u%15llx", ssvm_name (&fs->ssvm),
1272               format_fifo_segment_type, fs, size >> 20ULL, active_fifos,
1273               free_fifos, address);
1274
1275   if (!verbose)
1276     return s;
1277
1278   fsh = fs->h;
1279
1280   free_chunks = fifo_segment_num_free_chunks (fs, ~0);
1281   if (free_chunks)
1282     s =
1283       format (s, "\n\n%UFree/Allocated chunks by size:\n", format_white_space,
1284               indent + 2);
1285   else
1286     s = format (s, "\n");
1287
1288   for (slice_index = 0; slice_index < fs->n_slices; slice_index++)
1289     {
1290       fss = fsh_slice_get (fsh, slice_index);
1291       for (i = 0; i < vec_len (fss->free_chunks); i++)
1292         {
1293           c = fss->free_chunks[i];
1294           if (c == 0 && fss->num_chunks[i] == 0)
1295             continue;
1296           count = 0;
1297           while (c)
1298             {
1299               c = c->next;
1300               count++;
1301             }
1302
1303           chunk_size = fs_freelist_index_to_size (i);
1304           s = format (s, "%U%-5u kB: %u/%u\n", format_white_space, indent + 2,
1305                       chunk_size >> 10, count, fss->num_chunks[i]);
1306
1307           chunk_bytes += count * chunk_size;
1308         }
1309     }
1310
1311   fifo_hdr = free_fifos * sizeof (svm_fifo_t);
1312   est_chunk_bytes = fifo_segment_fl_chunk_bytes (fs);
1313   est_free_seg_bytes = fifo_segment_free_bytes (fs);
1314   fifo_segment_update_free_bytes (fs);
1315   free_seg_bytes = fifo_segment_free_bytes (fs);
1316   tracked_cached_bytes = fifo_segment_cached_bytes (fs);
1317   allocated = fifo_segment_size (fs);
1318   in_use = fifo_segment_size (fs) - est_free_seg_bytes - tracked_cached_bytes;
1319   usage = (100.0 * in_use) / allocated;
1320   mem_st = fifo_segment_get_mem_status (fs);
1321   virt = fsh_virtual_mem (fsh);
1322   reserved = fsh->n_reserved_bytes;
1323
1324   s = format (s, "\n%Useg free bytes: %U (%lu) estimated: %U (%lu) reserved:"
1325               " %U (%lu)\n", format_white_space, indent + 2,
1326               format_memory_size, free_seg_bytes, free_seg_bytes,
1327               format_memory_size, est_free_seg_bytes, est_free_seg_bytes,
1328               format_memory_size, reserved, reserved);
1329   s = format (s, "%Uchunk free bytes: %U (%lu) estimated: %U (%lu) tracked:"
1330               " %U (%lu)\n", format_white_space, indent + 2,
1331               format_memory_size, chunk_bytes, chunk_bytes,
1332               format_memory_size, est_chunk_bytes, est_chunk_bytes,
1333               format_memory_size, tracked_cached_bytes, tracked_cached_bytes);
1334   s = format (s, "%Ufifo active: %u hdr free bytes: %U (%u) \n",
1335               format_white_space, indent + 2, fsh->n_active_fifos,
1336               format_memory_size, fifo_hdr, fifo_hdr);
1337   s = format (s, "%Usegment usage: %.2f%% (%U / %U) virt: %U status: %s\n",
1338               format_white_space, indent + 2, usage, format_memory_size,
1339               in_use, format_memory_size, allocated, format_memory_size, virt,
1340               fifo_segment_mem_status_strings[mem_st]);
1341   s = format (s, "\n");
1342
1343   return s;
1344 }
1345
1346 /*
1347  * fd.io coding-style-patch-verification: ON
1348  *
1349  * Local Variables:
1350  * eval: (c-set-style "gnu")
1351  * End:
1352  */