session: add support for memfd segments
[vpp.git] / src / vnet / session / segment_manager.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 #include <vnet/session/segment_manager.h>
17 #include <vnet/session/session.h>
18 #include <vnet/session/application.h>
19
20 /**
21  * Counter used to build segment names
22  */
23 u32 segment_name_counter = 0;
24
25 /**
26  * Pool of segment managers
27  */
28 segment_manager_t *segment_managers = 0;
29
30 /*
31  * Pool of segment manager properties
32  */
33 static segment_manager_properties_t *segment_manager_properties_pool;
34
35 /**
36  * Process private segment index
37  */
38 u32 *private_segment_indices;
39
40 /**
41  * Default fifo and segment size. TODO config.
42  */
43 u32 default_fifo_size = 1 << 16;
44 u32 default_segment_size = 1 << 20;
45
46 segment_manager_properties_t *
47 segment_manager_properties_alloc (void)
48 {
49   segment_manager_properties_t *props;
50   pool_get (segment_manager_properties_pool, props);
51   memset (props, 0, sizeof (*props));
52   props->add_segment_size = default_segment_size;
53   props->rx_fifo_size = default_fifo_size;
54   props->tx_fifo_size = default_fifo_size;
55   return props;
56 }
57
58 void
59 segment_manager_properties_free (segment_manager_properties_t * props)
60 {
61   pool_put (segment_manager_properties_pool, props);
62   memset (props, 0xFB, sizeof (*props));
63 }
64
65 segment_manager_properties_t *
66 segment_manager_properties_get (u32 smp_index)
67 {
68   if (pool_is_free_index (segment_manager_properties_pool, smp_index))
69     return 0;
70   return pool_elt_at_index (segment_manager_properties_pool, smp_index);
71 }
72
73 u32
74 segment_manager_properties_index (segment_manager_properties_t * p)
75 {
76   return p - segment_manager_properties_pool;
77 }
78
79 svm_fifo_segment_private_t *
80 segment_manager_get_segment (u32 segment_index)
81 {
82   return svm_fifo_segment_get_segment (segment_index);
83 }
84
85 void
86 segment_manager_get_segment_info (u32 index, u8 ** name, u32 * size)
87 {
88   svm_fifo_segment_private_t *s;
89   s = svm_fifo_segment_get_segment (index);
90   *name = s->ssvm.name;
91   *size = s->ssvm.ssvm_size;
92 }
93
94 always_inline int
95 segment_manager_add_segment_i (segment_manager_t * sm, u32 segment_size,
96                                u32 protected_space)
97 {
98   svm_fifo_segment_create_args_t _ca = { 0 }, *ca = &_ca;
99   segment_manager_properties_t *props;
100
101   props = segment_manager_properties_get (sm->properties_index);
102
103   /* Not configured for addition of new segments and not first */
104   if (!props->add_segment && !segment_size)
105     {
106       clib_warning ("cannot allocate new segment");
107       return VNET_API_ERROR_INVALID_VALUE;
108     }
109
110   ca->segment_size = segment_size ? segment_size : props->add_segment_size;
111   ca->rx_fifo_size = props->rx_fifo_size;
112   ca->tx_fifo_size = props->tx_fifo_size;
113   ca->preallocated_fifo_pairs = props->preallocated_fifo_pairs;
114   ca->seg_protected_space = protected_space ? protected_space : 0;
115
116   if (props->segment_type != SSVM_N_SEGMENT_TYPES)
117     {
118       ca->segment_name = (char *) format (0, "%d-%d%c", getpid (),
119                                           segment_name_counter++, 0);
120       ca->segment_type = props->segment_type;
121       if (svm_fifo_segment_create (ca))
122         {
123           clib_warning ("svm_fifo_segment_create ('%s', %d) failed",
124                         ca->segment_name, ca->segment_size);
125           return VNET_API_ERROR_SVM_SEGMENT_CREATE_FAIL;
126         }
127       vec_free (ca->segment_name);
128     }
129   else
130     {
131       u32 rx_fifo_size, tx_fifo_size;
132       u32 rx_rounded_data_size, tx_rounded_data_size;
133       u32 approx_segment_count;
134       u64 approx_total_size;
135
136       ca->segment_name = "process-private-segment";
137       ca->private_segment_count = props->private_segment_count;
138
139       /* Calculate space requirements */
140       rx_rounded_data_size = (1 << (max_log2 (ca->rx_fifo_size)));
141       tx_rounded_data_size = (1 << (max_log2 (ca->tx_fifo_size)));
142
143       rx_fifo_size = sizeof (svm_fifo_t) + rx_rounded_data_size;
144       tx_fifo_size = sizeof (svm_fifo_t) + tx_rounded_data_size;
145
146       approx_total_size = (u64) ca->preallocated_fifo_pairs
147         * (rx_fifo_size + tx_fifo_size);
148       approx_segment_count = (approx_total_size + protected_space
149                               + (ca->segment_size -
150                                  1)) / (u64) ca->segment_size;
151
152       /* The user asked us to figure it out... */
153       if (ca->private_segment_count == 0
154           || approx_segment_count < ca->private_segment_count)
155         {
156           ca->private_segment_count = approx_segment_count;
157         }
158       /* Follow directions, but issue a warning */
159       else if (approx_segment_count < ca->private_segment_count)
160         {
161           clib_warning ("Honoring segment count %u, calculated count was %u",
162                         ca->private_segment_count, approx_segment_count);
163         }
164       else if (approx_segment_count > ca->private_segment_count)
165         {
166           clib_warning ("Segment count too low %u, calculated %u.",
167                         ca->private_segment_count, approx_segment_count);
168           return VNET_API_ERROR_INVALID_VALUE;
169         }
170
171       if (svm_fifo_segment_create_process_private (ca))
172         clib_warning ("Failed to create process private segment");
173
174       ASSERT (vec_len (ca->new_segment_indices));
175     }
176
177   vec_append (sm->segment_indices, ca->new_segment_indices);
178   vec_free (ca->new_segment_indices);
179   return 0;
180 }
181
182 int
183 segment_manager_add_segment (segment_manager_t * sm)
184 {
185   return segment_manager_add_segment_i (sm, 0, 0);
186 }
187
188 segment_manager_t *
189 segment_manager_new ()
190 {
191   segment_manager_t *sm;
192   pool_get (segment_managers, sm);
193   memset (sm, 0, sizeof (*sm));
194   return sm;
195 }
196
197 /**
198  * Initializes segment manager based on options provided.
199  * Returns error if svm segment allocation fails.
200  */
201 int
202 segment_manager_init (segment_manager_t * sm, u32 props_index,
203                       u32 first_seg_size, u32 evt_q_size)
204 {
205   u32 protected_space;
206   int rv;
207
208   sm->properties_index = props_index;
209
210   protected_space = max_pow2 (sizeof (svm_queue_t)
211                               + evt_q_size * sizeof (session_fifo_event_t));
212   protected_space = round_pow2_u64 (protected_space, CLIB_CACHE_LINE_BYTES);
213   first_seg_size = first_seg_size > 0 ? first_seg_size : default_segment_size;
214   rv = segment_manager_add_segment_i (sm, first_seg_size, protected_space);
215   if (rv)
216     {
217       clib_warning ("Failed to allocate segment");
218       return rv;
219     }
220
221   clib_spinlock_init (&sm->lockp);
222   return 0;
223 }
224
225 u8
226 segment_manager_has_fifos (segment_manager_t * sm)
227 {
228   svm_fifo_segment_private_t *segment;
229   int i;
230
231   for (i = 0; i < vec_len (sm->segment_indices); i++)
232     {
233       segment = svm_fifo_segment_get_segment (sm->segment_indices[i]);
234       if (CLIB_DEBUG && i && !svm_fifo_segment_has_fifos (segment)
235           && !(segment->h->flags & FIFO_SEGMENT_F_IS_PREALLOCATED))
236         clib_warning ("segment %d has no fifos!", sm->segment_indices[i]);
237       if (svm_fifo_segment_has_fifos (segment))
238         return 1;
239     }
240   return 0;
241 }
242
243 static u8
244 segment_manager_app_detached (segment_manager_t * sm)
245 {
246   return (sm->app_index == SEGMENT_MANAGER_INVALID_APP_INDEX);
247 }
248
249 void
250 segment_manager_app_detach (segment_manager_t * sm)
251 {
252   sm->app_index = SEGMENT_MANAGER_INVALID_APP_INDEX;
253 }
254
255 static void
256 segment_manager_del_segment (segment_manager_t * sm, u32 segment_index)
257 {
258   svm_fifo_segment_private_t *fifo_segment;
259   u32 svm_segment_index;
260   clib_spinlock_lock (&sm->lockp);
261   svm_segment_index = vec_elt (sm->segment_indices, segment_index);
262   fifo_segment = svm_fifo_segment_get_segment (svm_segment_index);
263   if (!fifo_segment
264       || ((fifo_segment->h->flags & FIFO_SEGMENT_F_IS_PREALLOCATED)
265           && !segment_manager_app_detached (sm)))
266     {
267       clib_spinlock_unlock (&sm->lockp);
268       return;
269     }
270   svm_fifo_segment_delete (fifo_segment);
271   vec_del1 (sm->segment_indices, segment_index);
272   clib_spinlock_unlock (&sm->lockp);
273 }
274
275 /**
276  * Initiate disconnects for all sessions 'owned' by a segment manager
277  */
278 void
279 segment_manager_del_sessions (segment_manager_t * sm)
280 {
281   int j;
282   svm_fifo_segment_private_t *fifo_segment;
283   svm_fifo_t *fifo;
284
285   ASSERT (vec_len (sm->segment_indices));
286
287   /* Across all fifo segments used by the server */
288   for (j = 0; j < vec_len (sm->segment_indices); j++)
289     {
290       fifo_segment = svm_fifo_segment_get_segment (sm->segment_indices[j]);
291       fifo = svm_fifo_segment_get_fifo_list (fifo_segment);
292
293       /*
294        * Remove any residual sessions from the session lookup table
295        * Don't bother deleting the individual fifos, we're going to
296        * throw away the fifo segment in a minute.
297        */
298       while (fifo)
299         {
300           u32 session_index, thread_index;
301           stream_session_t *session;
302
303           session_index = fifo->master_session_index;
304           thread_index = fifo->master_thread_index;
305           session = session_get (session_index, thread_index);
306
307           /* Instead of directly removing the session call disconnect */
308           if (session->session_state != SESSION_STATE_CLOSED)
309             {
310               session->session_state = SESSION_STATE_CLOSED;
311               session_send_session_evt_to_thread (session_handle (session),
312                                                   FIFO_EVENT_DISCONNECT,
313                                                   thread_index);
314             }
315           fifo = fifo->next;
316         }
317
318       /* Instead of removing the segment, test when cleaning up disconnected
319        * sessions if the segment can be removed.
320        */
321     }
322 }
323
324 /**
325  * Removes segment manager.
326  *
327  * Since the fifos allocated in the segment keep backpointers to the sessions
328  * prior to removing the segment, we call session disconnect. This
329  * subsequently propagates into transport.
330  */
331 void
332 segment_manager_del (segment_manager_t * sm)
333 {
334   int i;
335
336   ASSERT (!segment_manager_has_fifos (sm)
337           && segment_manager_app_detached (sm));
338
339   /* If we have empty preallocated segments that haven't been removed, remove
340    * them now. Apart from that, the first segment in the first segment manager
341    * is not removed when all fifos are removed. It can only be removed when
342    * the manager is explicitly deleted/detached by the app. */
343   for (i = vec_len (sm->segment_indices) - 1; i >= 0; i--)
344     {
345       if (CLIB_DEBUG)
346         {
347           svm_fifo_segment_private_t *segment;
348           segment = svm_fifo_segment_get_segment (sm->segment_indices[i]);
349           ASSERT (!svm_fifo_segment_has_fifos (segment));
350         }
351       segment_manager_del_segment (sm, i);
352     }
353   clib_spinlock_free (&sm->lockp);
354   if (CLIB_DEBUG)
355     memset (sm, 0xfe, sizeof (*sm));
356   pool_put (segment_managers, sm);
357 }
358
359 void
360 segment_manager_init_del (segment_manager_t * sm)
361 {
362   segment_manager_app_detach (sm);
363   if (segment_manager_has_fifos (sm))
364     segment_manager_del_sessions (sm);
365   else
366     {
367       ASSERT (!sm->first_is_protected || segment_manager_app_detached (sm));
368       segment_manager_del (sm);
369     }
370 }
371
372 int
373 segment_manager_alloc_session_fifos (segment_manager_t * sm,
374                                      svm_fifo_t ** rx_fifo,
375                                      svm_fifo_t ** tx_fifo,
376                                      u32 * fifo_segment_index)
377 {
378   svm_fifo_segment_private_t *fifo_segment;
379   segment_manager_properties_t *props;
380   u32 fifo_size, sm_index;
381   u8 added_a_segment = 0;
382   int i;
383
384   ASSERT (vec_len (sm->segment_indices));
385
386   /* Make sure we don't have multiple threads trying to allocate segments
387    * at the same time. */
388   clib_spinlock_lock (&sm->lockp);
389
390   /* Allocate svm fifos */
391   props = segment_manager_properties_get (sm->properties_index);
392 again:
393   for (i = 0; i < vec_len (sm->segment_indices); i++)
394     {
395       *fifo_segment_index = vec_elt (sm->segment_indices, i);
396       fifo_segment = svm_fifo_segment_get_segment (*fifo_segment_index);
397
398       fifo_size = props->rx_fifo_size;
399       fifo_size = (fifo_size == 0) ? default_fifo_size : fifo_size;
400       *rx_fifo = svm_fifo_segment_alloc_fifo (fifo_segment, fifo_size,
401                                               FIFO_SEGMENT_RX_FREELIST);
402
403       fifo_size = props->tx_fifo_size;
404       fifo_size = (fifo_size == 0) ? default_fifo_size : fifo_size;
405       *tx_fifo = svm_fifo_segment_alloc_fifo (fifo_segment, fifo_size,
406                                               FIFO_SEGMENT_TX_FREELIST);
407
408       if (*rx_fifo == 0)
409         {
410           /* This would be very odd, but handle it... */
411           if (*tx_fifo != 0)
412             {
413               svm_fifo_segment_free_fifo (fifo_segment, *tx_fifo,
414                                           FIFO_SEGMENT_TX_FREELIST);
415               *tx_fifo = 0;
416             }
417           continue;
418         }
419       if (*tx_fifo == 0)
420         {
421           if (*rx_fifo != 0)
422             {
423               svm_fifo_segment_free_fifo (fifo_segment, *rx_fifo,
424                                           FIFO_SEGMENT_RX_FREELIST);
425               *rx_fifo = 0;
426             }
427           continue;
428         }
429       break;
430     }
431
432   /* See if we're supposed to create another segment */
433   if (*rx_fifo == 0)
434     {
435       if (props->add_segment && !props->segment_type)
436         {
437           if (added_a_segment)
438             {
439               clib_warning ("added a segment, still can't allocate a fifo");
440               clib_spinlock_unlock (&sm->lockp);
441               return SESSION_ERROR_NEW_SEG_NO_SPACE;
442             }
443
444           if (segment_manager_add_segment (sm))
445             {
446               clib_spinlock_unlock (&sm->lockp);
447               return VNET_API_ERROR_URI_FIFO_CREATE_FAILED;
448             }
449
450           added_a_segment = 1;
451           goto again;
452         }
453       else
454         {
455           clib_warning ("No space to allocate fifos!");
456           clib_spinlock_unlock (&sm->lockp);
457           return SESSION_ERROR_NO_SPACE;
458         }
459     }
460
461   /* Backpointers to segment manager */
462   sm_index = segment_manager_index (sm);
463   (*tx_fifo)->segment_manager = sm_index;
464   (*rx_fifo)->segment_manager = sm_index;
465
466   clib_spinlock_unlock (&sm->lockp);
467
468   if (added_a_segment)
469     return application_add_segment_notify (sm->app_index,
470                                            *fifo_segment_index);
471
472   return 0;
473 }
474
475 void
476 segment_manager_dealloc_fifos (u32 svm_segment_index, svm_fifo_t * rx_fifo,
477                                svm_fifo_t * tx_fifo)
478 {
479   segment_manager_t *sm;
480   svm_fifo_segment_private_t *fifo_segment;
481   u32 i, segment_index = ~0;
482   u8 is_first;
483
484   sm = segment_manager_get_if_valid (rx_fifo->segment_manager);
485
486   /* It's possible to have no segment manager if the session was removed
487    * as result of a detach. */
488   if (!sm)
489     return;
490
491   fifo_segment = svm_fifo_segment_get_segment (svm_segment_index);
492   svm_fifo_segment_free_fifo (fifo_segment, rx_fifo,
493                               FIFO_SEGMENT_RX_FREELIST);
494   svm_fifo_segment_free_fifo (fifo_segment, tx_fifo,
495                               FIFO_SEGMENT_TX_FREELIST);
496
497   /*
498    * Try to remove svm segment if it has no fifos. This can be done only if
499    * the segment is not the first in the segment manager or if it is first
500    * and it is not protected. Moreover, if the segment is first and the app
501    * has detached from the segment manager, remove the segment manager.
502    */
503   if (!svm_fifo_segment_has_fifos (fifo_segment))
504     {
505       is_first = sm->segment_indices[0] == svm_segment_index;
506
507       /* Remove segment if it holds no fifos or first but not protected */
508       if (!is_first || !sm->first_is_protected)
509         {
510           /* Find the segment manager segment index */
511           for (i = 0; i < vec_len (sm->segment_indices); i++)
512             if (sm->segment_indices[i] == svm_segment_index)
513               {
514                 segment_index = i;
515                 break;
516               }
517           ASSERT (segment_index != (u32) ~ 0);
518           segment_manager_del_segment (sm, segment_index);
519         }
520
521       /* Remove segment manager if no sessions and detached from app */
522       if (segment_manager_app_detached (sm)
523           && !segment_manager_has_fifos (sm))
524         segment_manager_del (sm);
525     }
526 }
527
528 /**
529  * Allocates shm queue in the first segment
530  */
531 svm_queue_t *
532 segment_manager_alloc_queue (segment_manager_t * sm, u32 queue_size)
533 {
534   ssvm_shared_header_t *sh;
535   svm_fifo_segment_private_t *segment;
536   svm_queue_t *q;
537   void *oldheap;
538
539   ASSERT (sm->segment_indices != 0);
540
541   segment = svm_fifo_segment_get_segment (sm->segment_indices[0]);
542   sh = segment->ssvm.sh;
543
544   oldheap = ssvm_push_heap (sh);
545   q = svm_queue_init (queue_size,
546                       sizeof (session_fifo_event_t), 0 /* consumer pid */ ,
547                       0 /* signal when queue non-empty */ );
548   ssvm_pop_heap (oldheap);
549   return q;
550 }
551
552 /**
553  * Frees shm queue allocated in the first segment
554  */
555 void
556 segment_manager_dealloc_queue (segment_manager_t * sm, svm_queue_t * q)
557 {
558   ssvm_shared_header_t *sh;
559   svm_fifo_segment_private_t *segment;
560   void *oldheap;
561
562   ASSERT (sm->segment_indices != 0);
563
564   segment = svm_fifo_segment_get_segment (sm->segment_indices[0]);
565   sh = segment->ssvm.sh;
566
567   oldheap = ssvm_push_heap (sh);
568   svm_queue_free (q);
569   ssvm_pop_heap (oldheap);
570 }
571
572 static clib_error_t *
573 segment_manager_show_fn (vlib_main_t * vm, unformat_input_t * input,
574                          vlib_cli_command_t * cmd)
575 {
576   svm_fifo_segment_private_t *segments, *seg;
577   segment_manager_t *sm;
578   u8 show_segments = 0, verbose = 0;
579   uword address;
580   u64 size;
581   u32 active_fifos;
582   u32 free_fifos;
583
584   while (unformat_check_input (input) != UNFORMAT_END_OF_INPUT)
585     {
586       if (unformat (input, "segments"))
587         show_segments = 1;
588       else if (unformat (input, "verbose"))
589         verbose = 1;
590       else
591         return clib_error_return (0, "unknown input `%U'",
592                                   format_unformat_error, input);
593     }
594   vlib_cli_output (vm, "%d segment managers allocated",
595                    pool_elts (segment_managers));
596   if (verbose && pool_elts (segment_managers))
597     {
598       vlib_cli_output (vm, "%-10s%=15s%=12s", "Index", "App Index",
599                        "Segments");
600
601       /* *INDENT-OFF* */
602       pool_foreach (sm, segment_managers, ({
603         vlib_cli_output (vm, "%-10d%=15d%=12d", segment_manager_index(sm),
604                            sm->app_index, vec_len (sm->segment_indices));
605       }));
606       /* *INDENT-ON* */
607
608     }
609   if (show_segments)
610     {
611       segments = svm_fifo_segment_segments_pool ();
612       vlib_cli_output (vm, "%d svm fifo segments allocated",
613                        pool_elts (segments));
614       vlib_cli_output (vm, "%-15s%10s%15s%15s%15s%15s", "Name", "Type",
615                        "HeapSize (M)", "ActiveFifos", "FreeFifos", "Address");
616
617       /* *INDENT-OFF* */
618       pool_foreach (seg, segments, ({
619         svm_fifo_segment_info (seg, &address, &size);
620         active_fifos = svm_fifo_segment_num_fifos (seg);
621         free_fifos = svm_fifo_segment_num_free_fifos (seg, ~0 /* size */);
622         vlib_cli_output (vm, "%-15v%10U%15llu%15u%15u%15llx",
623                          ssvm_name (&seg->ssvm), format_svm_fifo_segment_type,
624                          seg, size >> 20ULL, active_fifos, free_fifos,
625                          address);
626         if (verbose)
627           vlib_cli_output (vm, "%U", format_svm_fifo_segment, seg, verbose);
628       }));
629       /* *INDENT-ON* */
630
631     }
632   return 0;
633 }
634
635 /* *INDENT-OFF* */
636 VLIB_CLI_COMMAND (segment_manager_show_command, static) =
637 {
638   .path = "show segment-manager",
639   .short_help = "show segment-manager [segments][verbose]",
640   .function = segment_manager_show_fn,
641 };
642 /* *INDENT-ON* */
643
644 /*
645  * fd.io coding-style-patch-verification: ON
646  *
647  * Local Variables:
648  * eval: (c-set-style "gnu")
649  * End:
650  */