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