svm/session: improve svm fifo allocation
[vpp.git] / src / svm / svm_fifo_segment.c
1 /*
2  * Copyright (c) 2016 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/svm_fifo_segment.h>
17
18 svm_fifo_segment_main_t svm_fifo_segment_main;
19
20 static void
21 allocate_new_fifo_chunk (svm_fifo_segment_header_t * fsh,
22                          u32 data_size_in_bytes, int chunk_size)
23 {
24   int freelist_index;
25   u32 size;
26   u8 *fifo_space;
27   u32 rounded_data_size;
28   svm_fifo_t *f;
29   int i;
30
31   rounded_data_size = (1 << (max_log2 (data_size_in_bytes)));
32   freelist_index = max_log2 (rounded_data_size)
33     - max_log2 (FIFO_SEGMENT_MIN_FIFO_SIZE);
34
35   /* Calculate space requirement $$$ round-up data_size_in_bytes */
36   size = (sizeof (*f) + rounded_data_size) * chunk_size;
37
38   /* Allocate fifo space. May fail. */
39   fifo_space = clib_mem_alloc_aligned_at_offset
40     (size, CLIB_CACHE_LINE_BYTES, 0 /* align_offset */ ,
41      0 /* os_out_of_memory */ );
42
43   /* Out of space.. */
44   if (fifo_space == 0)
45     return;
46
47   /* Carve fifo space */
48   f = (svm_fifo_t *) fifo_space;
49   for (i = 0; i < chunk_size; i++)
50     {
51       f->freelist_index = freelist_index;
52       f->next = fsh->free_fifos[freelist_index];
53       fsh->free_fifos[freelist_index] = f;
54       fifo_space += sizeof (*f) + rounded_data_size;
55       f = (svm_fifo_t *) fifo_space;
56     }
57 }
58
59 /**
60  * Pre-allocates fifo pairs in fifo segment
61  *
62  * The number of fifos pre-allocated is the minimum of the requested number
63  * of pairs and the maximum number that fit within the segment. If the maximum
64  * is hit, the number of fifo pairs requested is updated by subtracting the
65  * number of fifos that have been successfully allocated.
66  */
67 void
68 svm_fifo_segment_preallocate_fifo_pairs (svm_fifo_segment_private_t * s,
69                                          u32 rx_fifo_size, u32 tx_fifo_size,
70                                          u32 * n_fifo_pairs)
71 {
72   u32 rx_rounded_data_size, tx_rounded_data_size, pair_size;
73   u32 rx_fifos_size, tx_fifos_size, pairs_to_allocate;
74   int rx_freelist_index, tx_freelist_index;
75   ssvm_shared_header_t *sh = s->ssvm.sh;
76   svm_fifo_segment_header_t *fsh = s->h;
77   u8 *rx_fifo_space, *tx_fifo_space;
78   uword space_available;
79   void *oldheap;
80   svm_fifo_t *f;
81   int i;
82
83   /* Parameter check */
84   if (rx_fifo_size == 0 || tx_fifo_size == 0 || *n_fifo_pairs == 0)
85     return;
86
87   if (rx_fifo_size < FIFO_SEGMENT_MIN_FIFO_SIZE ||
88       rx_fifo_size > FIFO_SEGMENT_MAX_FIFO_SIZE)
89     {
90       clib_warning ("rx fifo_size out of range %d", rx_fifo_size);
91       return;
92     }
93
94   if (tx_fifo_size < FIFO_SEGMENT_MIN_FIFO_SIZE ||
95       tx_fifo_size > FIFO_SEGMENT_MAX_FIFO_SIZE)
96     {
97       clib_warning ("tx fifo_size out of range %d", rx_fifo_size);
98       return;
99     }
100
101   rx_rounded_data_size = (1 << (max_log2 (rx_fifo_size)));
102   rx_freelist_index = max_log2 (rx_fifo_size)
103     - max_log2 (FIFO_SEGMENT_MIN_FIFO_SIZE);
104   tx_rounded_data_size = (1 << (max_log2 (tx_fifo_size)));
105   tx_freelist_index = max_log2 (tx_fifo_size)
106     - max_log2 (FIFO_SEGMENT_MIN_FIFO_SIZE);
107
108   /* Calculate space requirements */
109   pair_size = 2 * sizeof (*f) + rx_rounded_data_size + tx_rounded_data_size;
110   space_available = s->ssvm.ssvm_size - mheap_bytes (sh->heap);
111   pairs_to_allocate = clib_min (space_available / pair_size, *n_fifo_pairs);
112   rx_fifos_size = (sizeof (*f) + rx_rounded_data_size) * pairs_to_allocate;
113   tx_fifos_size = (sizeof (*f) + tx_rounded_data_size) * pairs_to_allocate;
114
115   vec_validate_init_empty (fsh->free_fifos,
116                            clib_max (rx_freelist_index, tx_freelist_index),
117                            0);
118
119   oldheap = ssvm_push_heap (sh);
120   /* Allocate rx fifo space. May fail. */
121   rx_fifo_space = clib_mem_alloc_aligned_at_offset
122     (rx_fifos_size, CLIB_CACHE_LINE_BYTES, 0 /* align_offset */ ,
123      0 /* os_out_of_memory */ );
124
125   /* Same for TX */
126   tx_fifo_space = clib_mem_alloc_aligned_at_offset
127     (tx_fifos_size, CLIB_CACHE_LINE_BYTES, 0 /* align_offset */ ,
128      0 /* os_out_of_memory */ );
129
130   /* Make sure it worked. Clean up if it didn't... */
131   if (rx_fifo_space == 0 || tx_fifo_space == 0)
132     {
133       if (rx_fifo_space)
134         clib_mem_free (rx_fifo_space);
135       else
136         clib_warning ("rx fifo preallocation failure: size %d npairs %d",
137                       rx_fifo_size, *n_fifo_pairs);
138
139       if (tx_fifo_space)
140         clib_mem_free (tx_fifo_space);
141       else
142         clib_warning ("tx fifo preallocation failure: size %d nfifos %d",
143                       tx_fifo_size, *n_fifo_pairs);
144       ssvm_pop_heap (oldheap);
145       return;
146     }
147
148   /* Carve rx fifo space */
149   f = (svm_fifo_t *) rx_fifo_space;
150   for (i = 0; i < pairs_to_allocate; i++)
151     {
152       f->freelist_index = rx_freelist_index;
153       f->next = fsh->free_fifos[rx_freelist_index];
154       fsh->free_fifos[rx_freelist_index] = f;
155       rx_fifo_space += sizeof (*f) + rx_rounded_data_size;
156       f = (svm_fifo_t *) rx_fifo_space;
157     }
158   /* Carve tx fifo space */
159   f = (svm_fifo_t *) tx_fifo_space;
160   for (i = 0; i < pairs_to_allocate; i++)
161     {
162       f->freelist_index = tx_freelist_index;
163       f->next = fsh->free_fifos[tx_freelist_index];
164       fsh->free_fifos[tx_freelist_index] = f;
165       tx_fifo_space += sizeof (*f) + tx_rounded_data_size;
166       f = (svm_fifo_t *) tx_fifo_space;
167     }
168
169   /* Account for the pairs allocated */
170   *n_fifo_pairs -= pairs_to_allocate;
171   ssvm_pop_heap (oldheap);
172 }
173
174 /**
175  * Initialize svm fifo segment shared header
176  */
177 int
178 svm_fifo_segment_init (svm_fifo_segment_private_t * s)
179 {
180   svm_fifo_segment_header_t *fsh;
181   ssvm_shared_header_t *sh;
182   void *oldheap;
183
184   sh = s->ssvm.sh;
185   oldheap = ssvm_push_heap (sh);
186
187   fsh = clib_mem_alloc (sizeof (*fsh));
188   memset (fsh, 0, sizeof (*fsh));
189   s->h = sh->opaque[0] = fsh;
190
191   ssvm_pop_heap (oldheap);
192
193   sh->ready = 1;
194   return (0);
195 }
196
197 /**
198  * Create an svm fifo segment and initialize as master
199  */
200 int
201 svm_fifo_segment_create (svm_fifo_segment_create_args_t * a)
202 {
203   svm_fifo_segment_main_t *sm = &svm_fifo_segment_main;
204   svm_fifo_segment_private_t *s;
205   int rv;
206
207   /* Allocate a fresh segment */
208   pool_get (sm->segments, s);
209   memset (s, 0, sizeof (*s));
210
211   s->ssvm.ssvm_size = a->segment_size;
212   s->ssvm.i_am_master = 1;
213   s->ssvm.my_pid = getpid ();
214   s->ssvm.name = format (0, "%s%c", a->segment_name, 0);
215   s->ssvm.requested_va = sm->next_baseva;
216
217   if ((rv = ssvm_master_init (&s->ssvm, a->segment_type)))
218     {
219       pool_put (sm->segments, s);
220       return (rv);
221     }
222
223   /* Note: requested_va updated due to seg base addr randomization */
224   sm->next_baseva = s->ssvm.sh->ssvm_va + a->segment_size;
225
226   svm_fifo_segment_init (s);
227   vec_add1 (a->new_segment_indices, s - sm->segments);
228   return (0);
229 }
230
231 /**
232  * Create an svm fifo segment in process-private memory
233  */
234 int
235 svm_fifo_segment_create_process_private (svm_fifo_segment_create_args_t * a)
236 {
237   svm_fifo_segment_main_t *sm = &svm_fifo_segment_main;
238   svm_fifo_segment_private_t *s;
239   ssvm_shared_header_t *sh;
240   mheap_t *heap_header;
241   u32 rnd_size = 0;
242   u8 *heap;
243   u32 pagesize = clib_mem_get_page_size ();
244
245   pool_get (sm->segments, s);
246   memset (s, 0, sizeof (*s));
247
248   rnd_size = (a->segment_size + (pagesize - 1)) & ~pagesize;
249
250   heap = mheap_alloc (0, rnd_size);
251   if (heap == 0)
252     {
253       clib_unix_warning ("mheap alloc");
254       pool_put (sm->segments, s);
255       return -1;
256     }
257   heap_header = mheap_header (heap);
258   heap_header->flags |= MHEAP_FLAG_THREAD_SAFE;
259
260   s->ssvm.ssvm_size = rnd_size;
261   s->ssvm.i_am_master = 1;
262   s->ssvm.my_pid = getpid ();
263   s->ssvm.name = format (0, "%s%c", a->segment_name, 0);
264   s->ssvm.requested_va = ~0;
265
266   /* Allocate a [sic] shared memory header, in process memory... */
267   sh = clib_mem_alloc_aligned (sizeof (*sh), CLIB_CACHE_LINE_BYTES);
268   s->ssvm.sh = sh;
269
270   memset (sh, 0, sizeof (*sh));
271   sh->heap = heap;
272
273   svm_fifo_segment_init (s);
274   vec_add1 (a->new_segment_indices, s - sm->segments);
275
276   return (0);
277 }
278
279 /**
280  * Attach as slave to an svm fifo segment
281  */
282 int
283 svm_fifo_segment_attach (svm_fifo_segment_create_args_t * a)
284 {
285   svm_fifo_segment_main_t *sm = &svm_fifo_segment_main;
286   svm_fifo_segment_private_t *s;
287   int rv;
288
289   /* Allocate a fresh segment */
290   pool_get (sm->segments, s);
291   memset (s, 0, sizeof (*s));
292
293   s->ssvm.ssvm_size = a->segment_size;
294   s->ssvm.my_pid = getpid ();
295   s->ssvm.name = format (0, "%s%c", a->segment_name, 0);
296   s->ssvm.requested_va = sm->next_baseva;
297   if (a->segment_type == SSVM_SEGMENT_MEMFD)
298     s->ssvm.fd = a->memfd_fd;
299   else
300     s->ssvm.attach_timeout = sm->timeout_in_seconds;
301
302   if ((rv = ssvm_slave_init (&s->ssvm, a->segment_type)))
303     {
304       _vec_len (s) = vec_len (s) - 1;
305       return (rv);
306     }
307
308   /* Fish the segment header */
309   s->h = s->ssvm.sh->opaque[0];
310
311   vec_add1 (a->new_segment_indices, s - sm->segments);
312   return (0);
313 }
314
315 void
316 svm_fifo_segment_delete (svm_fifo_segment_private_t * s)
317 {
318   svm_fifo_segment_main_t *sm = &svm_fifo_segment_main;
319
320   ssvm_delete (&s->ssvm);
321   memset (s, 0xfe, sizeof (*s));
322   pool_put (sm->segments, s);
323 }
324
325 /**
326  * Allocate fifo in svm segment
327  */
328 svm_fifo_t *
329 svm_fifo_segment_alloc_fifo (svm_fifo_segment_private_t * s,
330                              u32 data_size_in_bytes,
331                              svm_fifo_segment_freelist_t list_index)
332 {
333   ssvm_shared_header_t *sh;
334   svm_fifo_segment_header_t *fsh;
335   svm_fifo_t *f = 0;
336   void *oldheap;
337   int freelist_index;
338
339   /*
340    * 4K minimum. It's not likely that anything good will happen
341    * with a smaller FIFO.
342    */
343   if (data_size_in_bytes < FIFO_SEGMENT_MIN_FIFO_SIZE ||
344       data_size_in_bytes > FIFO_SEGMENT_MAX_FIFO_SIZE)
345     {
346       clib_warning ("fifo size out of range %d", data_size_in_bytes);
347       return 0;
348     }
349
350   freelist_index = max_log2 (data_size_in_bytes)
351     - max_log2 (FIFO_SEGMENT_MIN_FIFO_SIZE);
352
353   sh = s->ssvm.sh;
354   ssvm_lock_non_recursive (sh, 1);
355   fsh = (svm_fifo_segment_header_t *) sh->opaque[0];
356
357   switch (list_index)
358     {
359     case FIFO_SEGMENT_RX_FREELIST:
360     case FIFO_SEGMENT_TX_FREELIST:
361       vec_validate_init_empty (fsh->free_fifos, freelist_index, 0);
362       f = fsh->free_fifos[freelist_index];
363       if (PREDICT_FALSE (!f))
364         {
365           /* Preallocated and no fifo left. Don't even try */
366           if (fsh->flags & FIFO_SEGMENT_F_IS_PREALLOCATED)
367             goto done;
368
369           oldheap = ssvm_push_heap (sh);
370           allocate_new_fifo_chunk (fsh, data_size_in_bytes,
371                                    FIFO_SEGMENT_ALLOC_CHUNK_SIZE);
372           ssvm_pop_heap (oldheap);
373           f = fsh->free_fifos[freelist_index];
374         }
375       if (PREDICT_TRUE (f != 0))
376         {
377           fsh->free_fifos[freelist_index] = f->next;
378           /* (re)initialize the fifo, as in svm_fifo_create */
379           memset (f, 0, sizeof (*f));
380           f->nitems = data_size_in_bytes;
381           f->ooos_list_head = OOO_SEGMENT_INVALID_INDEX;
382           f->refcnt = 1;
383           f->freelist_index = freelist_index;
384           goto found;
385         }
386       break;
387     case FIFO_SEGMENT_FREELIST_NONE:
388       break;
389
390     default:
391       clib_warning ("ignore bogus freelist %d", list_index);
392       break;
393     }
394
395   /* Catch all that allocates just one fifo. Note: this can fail,
396    * in which case: create another segment */
397   oldheap = ssvm_push_heap (sh);
398   f = svm_fifo_create (data_size_in_bytes);
399   ssvm_pop_heap (oldheap);
400   if (PREDICT_FALSE (f == 0))
401     goto done;
402   f->freelist_index = freelist_index;
403
404 found:
405   /* If rx_freelist add to active fifos list. When cleaning up segment,
406    * we need a list of active sessions that should be disconnected. Since
407    * both rx and tx fifos keep pointers to the session, it's enough to track
408    * only one. */
409   if (list_index == FIFO_SEGMENT_RX_FREELIST)
410     {
411       if (fsh->fifos)
412         {
413           fsh->fifos->prev = f;
414           f->next = fsh->fifos;
415         }
416       fsh->fifos = f;
417     }
418   fsh->n_active_fifos++;
419
420 done:
421   ssvm_unlock_non_recursive (sh);
422   return (f);
423 }
424
425 void
426 svm_fifo_segment_free_fifo (svm_fifo_segment_private_t * s, svm_fifo_t * f,
427                             svm_fifo_segment_freelist_t list_index)
428 {
429   ssvm_shared_header_t *sh;
430   svm_fifo_segment_header_t *fsh;
431   void *oldheap;
432   int freelist_index;
433
434   ASSERT (f->refcnt > 0);
435
436   if (--f->refcnt > 0)
437     return;
438
439   sh = s->ssvm.sh;
440   fsh = (svm_fifo_segment_header_t *) sh->opaque[0];
441
442   freelist_index = f->freelist_index;
443
444   ASSERT (freelist_index < vec_len (fsh->free_fifos));
445
446   ssvm_lock_non_recursive (sh, 2);
447   oldheap = ssvm_push_heap (sh);
448
449   switch (list_index)
450     {
451     case FIFO_SEGMENT_RX_FREELIST:
452       /* Remove from active list */
453       if (f->prev)
454         f->prev->next = f->next;
455       else
456         fsh->fifos = f->next;
457       if (f->next)
458         f->next->prev = f->prev;
459       /* Fall through: we add only rx fifos to active pool */
460     case FIFO_SEGMENT_TX_FREELIST:
461       /* Add to free list */
462       f->next = fsh->free_fifos[freelist_index];
463       f->prev = 0;
464       fsh->free_fifos[freelist_index] = f;
465       break;
466     case FIFO_SEGMENT_FREELIST_NONE:
467       break;
468
469     default:
470       clib_warning ("ignore bogus freelist %d", list_index);
471       break;
472     }
473
474   if (CLIB_DEBUG)
475     {
476       f->master_session_index = ~0;
477       f->master_thread_index = ~0;
478     }
479
480   fsh->n_active_fifos--;
481   ssvm_pop_heap (oldheap);
482   ssvm_unlock_non_recursive (sh);
483 }
484
485 void
486 svm_fifo_segment_main_init (u64 baseva, u32 timeout_in_seconds)
487 {
488   svm_fifo_segment_main_t *sm = &svm_fifo_segment_main;
489
490   sm->next_baseva = baseva;
491   sm->timeout_in_seconds = timeout_in_seconds;
492 }
493
494 u32
495 svm_fifo_segment_index (svm_fifo_segment_private_t * s)
496 {
497   return s - svm_fifo_segment_main.segments;
498 }
499
500 /**
501  * Retrieve svm segments pool. Used only for debug purposes.
502  */
503 svm_fifo_segment_private_t *
504 svm_fifo_segment_segments_pool (void)
505 {
506   svm_fifo_segment_main_t *sm = &svm_fifo_segment_main;
507   return sm->segments;
508 }
509
510 /**
511  * Get number of active fifos
512  */
513 u32
514 svm_fifo_segment_num_fifos (svm_fifo_segment_private_t * fifo_segment)
515 {
516   return fifo_segment->h->n_active_fifos;
517 }
518
519 u32
520 svm_fifo_segment_num_free_fifos (svm_fifo_segment_private_t * fifo_segment,
521                                  u32 fifo_size_in_bytes)
522 {
523   ssvm_shared_header_t *sh;
524   svm_fifo_segment_header_t *fsh;
525   svm_fifo_t *f;
526   int i;
527   u32 count = 0, rounded_data_size, freelist_index;
528
529   sh = fifo_segment->ssvm.sh;
530   fsh = (svm_fifo_segment_header_t *) sh->opaque[0];
531
532   /* Count all free fifos? */
533   if (fifo_size_in_bytes == ~0)
534     {
535       for (i = 0; i < vec_len (fsh->free_fifos); i++)
536         {
537           f = fsh->free_fifos[i];
538           if (f == 0)
539             continue;
540
541           while (f)
542             {
543               f = f->next;
544               count++;
545             }
546         }
547       return count;
548     }
549
550   rounded_data_size = (1 << (max_log2 (fifo_size_in_bytes)));
551   freelist_index = max_log2 (rounded_data_size)
552     - max_log2 (FIFO_SEGMENT_MIN_FIFO_SIZE);
553
554   if (freelist_index > vec_len (fsh->free_fifos))
555     return 0;
556
557   f = fsh->free_fifos[freelist_index];
558   if (f == 0)
559     return 0;
560
561   while (f)
562     {
563       f = f->next;
564       count++;
565     }
566   return count;
567 }
568
569 void
570 svm_fifo_segment_info (svm_fifo_segment_private_t * seg, uword * address,
571                        u64 * size)
572 {
573   if (ssvm_type (&seg->ssvm) == SSVM_SEGMENT_PRIVATE)
574     {
575       mheap_t *heap_header;
576
577       *address = pointer_to_uword (seg->ssvm.sh->heap);
578       heap_header = mheap_header (seg->ssvm.sh->heap);
579       *size = heap_header->max_size;
580     }
581   else
582     {
583       *address = seg->ssvm.sh->ssvm_va;
584       *size = seg->ssvm.ssvm_size;
585     }
586 }
587
588 mheap_t *
589 svm_fifo_segment_heap (svm_fifo_segment_private_t * seg)
590 {
591   return seg->ssvm.sh->heap;
592 }
593
594 u8 *
595 format_svm_fifo_segment_type (u8 * s, va_list * args)
596 {
597   svm_fifo_segment_private_t *sp;
598   sp = va_arg (*args, svm_fifo_segment_private_t *);
599   ssvm_segment_type_t st = ssvm_type (&sp->ssvm);
600
601   if (st == SSVM_SEGMENT_PRIVATE)
602     s = format (s, "%s", "private-heap");
603   else if (st == SSVM_SEGMENT_MEMFD)
604     s = format (s, "%s", "memfd");
605   else if (st == SSVM_SEGMENT_SHM)
606     s = format (s, "%s", "shm");
607   else
608     s = format (s, "%s", "unknown");
609   return s;
610 }
611
612 /**
613  * Segment format function
614  */
615 u8 *
616 format_svm_fifo_segment (u8 * s, va_list * args)
617 {
618   svm_fifo_segment_private_t *sp
619     = va_arg (*args, svm_fifo_segment_private_t *);
620   int verbose = va_arg (*args, int);
621   svm_fifo_segment_header_t *fsh = sp->h;
622   u32 count, indent;
623   svm_fifo_t *f;
624   int i;
625
626   indent = format_get_indent (s) + 2;
627   s = format (s, "%U segment heap: %U\n", format_white_space, indent,
628               format_mheap, svm_fifo_segment_heap (sp), verbose);
629   s = format (s, "%U segment has %u active fifos\n",
630               format_white_space, indent, svm_fifo_segment_num_fifos (sp));
631
632   for (i = 0; i < vec_len (fsh->free_fifos); i++)
633     {
634       f = fsh->free_fifos[i];
635       if (f == 0)
636         continue;
637       count = 0;
638       while (f)
639         {
640           f = f->next;
641           count++;
642         }
643
644       s = format (s, "%U%-5u Kb: %u free",
645                   format_white_space, indent + 2,
646                   1 << (i + max_log2 (FIFO_SEGMENT_MIN_FIFO_SIZE) - 10),
647                   count);
648     }
649   return s;
650 }
651
652 /*
653  * fd.io coding-style-patch-verification: ON
654  *
655  * Local Variables:
656  * eval: (c-set-style "gnu")
657  * End:
658  */