Session layer refactoring
[vpp.git] / src / svm / svm_fifo.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.h>
17
18 /** create an svm fifo, in the current heap. Fails vs blow up the process */
19 svm_fifo_t *
20 svm_fifo_create (u32 data_size_in_bytes)
21 {
22   svm_fifo_t *f;
23
24   f = clib_mem_alloc_aligned_or_null (sizeof (*f) + data_size_in_bytes,
25                                       CLIB_CACHE_LINE_BYTES);
26   if (f == 0)
27     return 0;
28
29   memset (f, 0, sizeof (*f) + data_size_in_bytes);
30   f->nitems = data_size_in_bytes;
31   f->ooos_list_head = OOO_SEGMENT_INVALID_INDEX;
32
33   return (f);
34 }
35
36 void
37 svm_fifo_free (svm_fifo_t * f)
38 {
39   pool_free (f->ooo_segments);
40   clib_mem_free (f);
41 }
42
43 always_inline ooo_segment_t *
44 ooo_segment_new (svm_fifo_t * f, u32 start, u32 length)
45 {
46   ooo_segment_t *s;
47
48   pool_get (f->ooo_segments, s);
49
50   s->fifo_position = start;
51   s->length = length;
52
53   s->prev = s->next = OOO_SEGMENT_INVALID_INDEX;
54
55   return s;
56 }
57
58 always_inline void
59 ooo_segment_del (svm_fifo_t * f, u32 index)
60 {
61   ooo_segment_t *cur, *prev = 0, *next = 0;
62   cur = pool_elt_at_index (f->ooo_segments, index);
63
64   if (cur->next != OOO_SEGMENT_INVALID_INDEX)
65     {
66       next = pool_elt_at_index (f->ooo_segments, cur->next);
67       next->prev = cur->prev;
68     }
69
70   if (cur->prev != OOO_SEGMENT_INVALID_INDEX)
71     {
72       prev = pool_elt_at_index (f->ooo_segments, cur->prev);
73       prev->next = cur->next;
74     }
75   else
76     {
77       f->ooos_list_head = cur->next;
78     }
79
80   pool_put (f->ooo_segments, cur);
81 }
82
83 /**
84  * Add segment to fifo's out-of-order segment list. Takes care of merging
85  * adjacent segments and removing overlapping ones.
86  */
87 static void
88 ooo_segment_add (svm_fifo_t * f, u32 offset, u32 length)
89 {
90   ooo_segment_t *s, *new_s, *prev, *next, *it;
91   u32 new_index, position, end_offset, s_sof, s_eof, s_index;
92
93   position = (f->tail + offset) % f->nitems;
94   end_offset = offset + length;
95
96   if (f->ooos_list_head == OOO_SEGMENT_INVALID_INDEX)
97     {
98       s = ooo_segment_new (f, position, length);
99       f->ooos_list_head = s - f->ooo_segments;
100       f->ooos_newest = f->ooos_list_head;
101       return;
102     }
103
104   /* Find first segment that starts after new segment */
105   s = pool_elt_at_index (f->ooo_segments, f->ooos_list_head);
106   while (s->next != OOO_SEGMENT_INVALID_INDEX
107          && ooo_segment_offset (f, s) <= offset)
108     s = pool_elt_at_index (f->ooo_segments, s->next);
109
110   s_index = s - f->ooo_segments;
111   s_sof = ooo_segment_offset (f, s);
112   s_eof = ooo_segment_end_offset (f, s);
113
114   /* No overlap, add before current segment */
115   if (end_offset < s_sof)
116     {
117       new_s = ooo_segment_new (f, position, length);
118       new_index = new_s - f->ooo_segments;
119
120       /* Pool might've moved, get segment again */
121       s = pool_elt_at_index (f->ooo_segments, s_index);
122
123       if (s->prev != OOO_SEGMENT_INVALID_INDEX)
124         {
125           new_s->prev = s->prev;
126
127           prev = pool_elt_at_index (f->ooo_segments, new_s->prev);
128           prev->next = new_index;
129         }
130       else
131         {
132           /* New head */
133           f->ooos_list_head = new_index;
134         }
135
136       new_s->next = s - f->ooo_segments;
137       s->prev = new_index;
138       f->ooos_newest = new_index;
139       return;
140     }
141   /* No overlap, add after current segment */
142   else if (s_eof < offset)
143     {
144       new_s = ooo_segment_new (f, position, length);
145       new_index = new_s - f->ooo_segments;
146
147       /* Pool might've moved, get segment again */
148       s = pool_elt_at_index (f->ooo_segments, s_index);
149
150       if (s->next != OOO_SEGMENT_INVALID_INDEX)
151         {
152           new_s->next = s->next;
153
154           next = pool_elt_at_index (f->ooo_segments, new_s->next);
155           next->prev = new_index;
156         }
157
158       new_s->prev = s - f->ooo_segments;
159       s->next = new_index;
160       f->ooos_newest = new_index;
161
162       return;
163     }
164
165   /*
166    * Merge needed
167    */
168
169   /* Merge at head */
170   if (offset <= s_sof)
171     {
172       /* If we have a previous, check if we overlap */
173       if (s->prev != OOO_SEGMENT_INVALID_INDEX)
174         {
175           prev = pool_elt_at_index (f->ooo_segments, s->prev);
176
177           /* New segment merges prev and current. Remove previous and
178            * update position of current. */
179           if (ooo_segment_end_offset (f, prev) >= offset)
180             {
181               s->fifo_position = prev->fifo_position;
182               s->length = s_eof - ooo_segment_offset (f, prev);
183               ooo_segment_del (f, s->prev);
184             }
185         }
186       else
187         {
188           s->fifo_position = position;
189           s->length = s_eof - ooo_segment_offset (f, s);
190         }
191
192       /* The new segment's tail may cover multiple smaller ones */
193       if (s_eof < end_offset)
194         {
195           /* Remove segments completely covered */
196           it = (s->next != OOO_SEGMENT_INVALID_INDEX) ?
197             pool_elt_at_index (f->ooo_segments, s->next) : 0;
198           while (it && ooo_segment_end_offset (f, it) < end_offset)
199             {
200               next = (it->next != OOO_SEGMENT_INVALID_INDEX) ?
201                 pool_elt_at_index (f->ooo_segments, it->next) : 0;
202               ooo_segment_del (f, it - f->ooo_segments);
203               it = next;
204             }
205
206           /* Update length. Segment's start might have changed. */
207           s->length = end_offset - ooo_segment_offset (f, s);
208
209           /* If partial overlap with last, merge */
210           if (it && ooo_segment_offset (f, it) < end_offset)
211             {
212               s->length +=
213                 it->length - (ooo_segment_offset (f, it) - end_offset);
214               ooo_segment_del (f, it - f->ooo_segments);
215             }
216         }
217     }
218   /* Last but overlapping previous */
219   else if (s_eof <= end_offset)
220     {
221       s->length = end_offset - ooo_segment_offset (f, s);
222     }
223   /* New segment completely covered by current one */
224   else
225     {
226       /* Do Nothing */
227     }
228
229   /* Most recently updated segment */
230   f->ooos_newest = s - f->ooo_segments;
231 }
232
233 /**
234  * Removes segments that can now be enqueued because the fifo's tail has
235  * advanced. Returns the number of bytes added to tail.
236  */
237 static int
238 ooo_segment_try_collect (svm_fifo_t * f, u32 n_bytes_enqueued)
239 {
240   ooo_segment_t *s;
241   u32 index, bytes = 0, diff;
242   u32 cursize;
243
244   /* read cursize, which can only increase while we're working */
245   cursize = svm_fifo_max_dequeue (f);
246
247   s = pool_elt_at_index (f->ooo_segments, f->ooos_list_head);
248
249   /* If last tail update overlaps one/multiple ooo segments, remove them */
250   diff = (f->nitems + f->tail - s->fifo_position) % f->nitems;
251   while (0 < diff && diff < n_bytes_enqueued)
252     {
253       /* Segment end is beyond the tail. Advance tail and be done */
254       if (diff < s->length)
255         {
256           f->tail += s->length - diff;
257           f->tail %= f->nitems;
258           break;
259         }
260       /* If we have next go on */
261       else if (s->next != OOO_SEGMENT_INVALID_INDEX)
262         {
263           index = s - f->ooo_segments;
264           s = pool_elt_at_index (f->ooo_segments, s->next);
265           diff = (f->nitems + f->tail - s->fifo_position) % f->nitems;
266           ooo_segment_del (f, index);
267         }
268       /* End of search */
269       else
270         {
271           break;
272         }
273     }
274
275   /* If tail is adjacent to an ooo segment, 'consume' it */
276   if (diff == 0)
277     {
278       bytes = ((f->nitems - cursize) >= s->length) ? s->length :
279         f->nitems - cursize;
280
281       f->tail += bytes;
282       f->tail %= f->nitems;
283
284       ooo_segment_del (f, s - f->ooo_segments);
285     }
286
287   return bytes;
288 }
289
290 static int
291 svm_fifo_enqueue_internal (svm_fifo_t * f,
292                            int pid, u32 max_bytes, u8 * copy_from_here)
293 {
294   u32 total_copy_bytes, first_copy_bytes, second_copy_bytes;
295   u32 cursize, nitems;
296
297   /* read cursize, which can only increase while we're working */
298   cursize = svm_fifo_max_dequeue (f);
299
300   if (PREDICT_FALSE (cursize == f->nitems))
301     return -2;                  /* fifo stuffed */
302
303   nitems = f->nitems;
304
305   /* Number of bytes we're going to copy */
306   total_copy_bytes = (nitems - cursize) < max_bytes ?
307     (nitems - cursize) : max_bytes;
308
309   if (PREDICT_TRUE (copy_from_here != 0))
310     {
311       /* Number of bytes in first copy segment */
312       first_copy_bytes = ((nitems - f->tail) < total_copy_bytes)
313         ? (nitems - f->tail) : total_copy_bytes;
314
315       clib_memcpy (&f->data[f->tail], copy_from_here, first_copy_bytes);
316       f->tail += first_copy_bytes;
317       f->tail = (f->tail == nitems) ? 0 : f->tail;
318
319       /* Number of bytes in second copy segment, if any */
320       second_copy_bytes = total_copy_bytes - first_copy_bytes;
321       if (second_copy_bytes)
322         {
323           clib_memcpy (&f->data[f->tail], copy_from_here + first_copy_bytes,
324                        second_copy_bytes);
325           f->tail += second_copy_bytes;
326           f->tail = (f->tail == nitems) ? 0 : f->tail;
327         }
328     }
329   else
330     {
331       /* Account for a zero-copy enqueue done elsewhere */
332       ASSERT (max_bytes <= (nitems - cursize));
333       f->tail += max_bytes;
334       f->tail = f->tail % nitems;
335       total_copy_bytes = max_bytes;
336     }
337
338   /* Any out-of-order segments to collect? */
339   if (PREDICT_FALSE (f->ooos_list_head != OOO_SEGMENT_INVALID_INDEX))
340     total_copy_bytes += ooo_segment_try_collect (f, total_copy_bytes);
341
342   /* Atomically increase the queue length */
343   __sync_fetch_and_add (&f->cursize, total_copy_bytes);
344
345   return (total_copy_bytes);
346 }
347
348 int
349 svm_fifo_enqueue_nowait (svm_fifo_t * f,
350                          int pid, u32 max_bytes, u8 * copy_from_here)
351 {
352   return svm_fifo_enqueue_internal (f, pid, max_bytes, copy_from_here);
353 }
354
355 /**
356  * Enqueue a future segment.
357  *
358  * Two choices: either copies the entire segment, or copies nothing
359  * Returns 0 of the entire segment was copied
360  * Returns -1 if none of the segment was copied due to lack of space
361  */
362 static int
363 svm_fifo_enqueue_with_offset_internal (svm_fifo_t * f,
364                                        int pid,
365                                        u32 offset,
366                                        u32 required_bytes,
367                                        u8 * copy_from_here)
368 {
369   u32 total_copy_bytes, first_copy_bytes, second_copy_bytes;
370   u32 cursize, nitems;
371   u32 tail_plus_offset;
372
373   ASSERT (offset > 0);
374
375   /* read cursize, which can only increase while we're working */
376   cursize = svm_fifo_max_dequeue (f);
377   nitems = f->nitems;
378
379   /* Will this request fit? */
380   if ((required_bytes + offset) > (nitems - cursize))
381     return -1;
382
383   ooo_segment_add (f, offset, required_bytes);
384
385   /* Number of bytes we're going to copy */
386   total_copy_bytes = required_bytes;
387   tail_plus_offset = (f->tail + offset) % nitems;
388
389   /* Number of bytes in first copy segment */
390   first_copy_bytes = ((nitems - tail_plus_offset) < total_copy_bytes)
391     ? (nitems - tail_plus_offset) : total_copy_bytes;
392
393   clib_memcpy (&f->data[tail_plus_offset], copy_from_here, first_copy_bytes);
394
395   /* Number of bytes in second copy segment, if any */
396   second_copy_bytes = total_copy_bytes - first_copy_bytes;
397   if (second_copy_bytes)
398     {
399       tail_plus_offset += first_copy_bytes;
400       tail_plus_offset %= nitems;
401
402       ASSERT (tail_plus_offset == 0);
403
404       clib_memcpy (&f->data[tail_plus_offset],
405                    copy_from_here + first_copy_bytes, second_copy_bytes);
406     }
407
408   return (0);
409 }
410
411
412 int
413 svm_fifo_enqueue_with_offset (svm_fifo_t * f,
414                               int pid,
415                               u32 offset,
416                               u32 required_bytes, u8 * copy_from_here)
417 {
418   return svm_fifo_enqueue_with_offset_internal
419     (f, pid, offset, required_bytes, copy_from_here);
420 }
421
422
423 static int
424 svm_fifo_dequeue_internal (svm_fifo_t * f,
425                            int pid, u32 max_bytes, u8 * copy_here)
426 {
427   u32 total_copy_bytes, first_copy_bytes, second_copy_bytes;
428   u32 cursize, nitems;
429
430   /* read cursize, which can only increase while we're working */
431   cursize = svm_fifo_max_dequeue (f);
432   if (PREDICT_FALSE (cursize == 0))
433     return -2;                  /* nothing in the fifo */
434
435   nitems = f->nitems;
436
437   /* Number of bytes we're going to copy */
438   total_copy_bytes = (cursize < max_bytes) ? cursize : max_bytes;
439
440   if (PREDICT_TRUE (copy_here != 0))
441     {
442       /* Number of bytes in first copy segment */
443       first_copy_bytes = ((nitems - f->head) < total_copy_bytes)
444         ? (nitems - f->head) : total_copy_bytes;
445       clib_memcpy (copy_here, &f->data[f->head], first_copy_bytes);
446       f->head += first_copy_bytes;
447       f->head = (f->head == nitems) ? 0 : f->head;
448
449       /* Number of bytes in second copy segment, if any */
450       second_copy_bytes = total_copy_bytes - first_copy_bytes;
451       if (second_copy_bytes)
452         {
453           clib_memcpy (copy_here + first_copy_bytes,
454                        &f->data[f->head], second_copy_bytes);
455           f->head += second_copy_bytes;
456           f->head = (f->head == nitems) ? 0 : f->head;
457         }
458     }
459   else
460     {
461       /* Account for a zero-copy dequeue done elsewhere */
462       ASSERT (max_bytes <= cursize);
463       f->head += max_bytes;
464       f->head = f->head % nitems;
465       cursize -= max_bytes;
466       total_copy_bytes = max_bytes;
467     }
468
469   __sync_fetch_and_sub (&f->cursize, total_copy_bytes);
470
471   return (total_copy_bytes);
472 }
473
474 int
475 svm_fifo_dequeue_nowait (svm_fifo_t * f,
476                          int pid, u32 max_bytes, u8 * copy_here)
477 {
478   return svm_fifo_dequeue_internal (f, pid, max_bytes, copy_here);
479 }
480
481 int
482 svm_fifo_peek (svm_fifo_t * f, int pid, u32 offset, u32 max_bytes,
483                u8 * copy_here)
484 {
485   u32 total_copy_bytes, first_copy_bytes, second_copy_bytes;
486   u32 cursize, nitems, real_head;
487
488   /* read cursize, which can only increase while we're working */
489   cursize = svm_fifo_max_dequeue (f);
490   if (PREDICT_FALSE (cursize == 0))
491     return -2;                  /* nothing in the fifo */
492
493   nitems = f->nitems;
494   real_head = f->head + offset;
495   real_head = real_head >= nitems ? real_head - nitems : real_head;
496
497   /* Number of bytes we're going to copy */
498   total_copy_bytes = (cursize < max_bytes) ? cursize : max_bytes;
499
500   if (PREDICT_TRUE (copy_here != 0))
501     {
502       /* Number of bytes in first copy segment */
503       first_copy_bytes =
504         ((nitems - real_head) < total_copy_bytes) ?
505         (nitems - real_head) : total_copy_bytes;
506       clib_memcpy (copy_here, &f->data[real_head], first_copy_bytes);
507
508       /* Number of bytes in second copy segment, if any */
509       second_copy_bytes = total_copy_bytes - first_copy_bytes;
510       if (second_copy_bytes)
511         {
512           clib_memcpy (copy_here + first_copy_bytes, &f->data[0],
513                        second_copy_bytes);
514         }
515     }
516   return total_copy_bytes;
517 }
518
519 int
520 svm_fifo_dequeue_drop (svm_fifo_t * f, int pid, u32 max_bytes)
521 {
522   u32 total_drop_bytes, first_drop_bytes, second_drop_bytes;
523   u32 cursize, nitems;
524
525   /* read cursize, which can only increase while we're working */
526   cursize = svm_fifo_max_dequeue (f);
527   if (PREDICT_FALSE (cursize == 0))
528     return -2;                  /* nothing in the fifo */
529
530   nitems = f->nitems;
531
532   /* Number of bytes we're going to drop */
533   total_drop_bytes = (cursize < max_bytes) ? cursize : max_bytes;
534
535   /* Number of bytes in first copy segment */
536   first_drop_bytes =
537     ((nitems - f->head) < total_drop_bytes) ?
538     (nitems - f->head) : total_drop_bytes;
539   f->head += first_drop_bytes;
540   f->head = (f->head == nitems) ? 0 : f->head;
541
542   /* Number of bytes in second drop segment, if any */
543   second_drop_bytes = total_drop_bytes - first_drop_bytes;
544   if (second_drop_bytes)
545     {
546       f->head += second_drop_bytes;
547       f->head = (f->head == nitems) ? 0 : f->head;
548     }
549
550   __sync_fetch_and_sub (&f->cursize, total_drop_bytes);
551
552   return total_drop_bytes;
553 }
554
555 u8 *
556 format_svm_fifo (u8 * s, va_list * args)
557 {
558   svm_fifo_t *f = va_arg (*args, svm_fifo_t *);
559   int verbose = va_arg (*args, int);
560
561   s = format (s, "cursize %u nitems %u has_event %d\n",
562               f->cursize, f->nitems, f->has_event);
563   s = format (s, "head %d tail %d\n", f->head, f->tail);
564
565   if (verbose > 1)
566     s = format
567       (s, "server session %d thread %d client session %d thread %d\n",
568        f->server_session_index, f->server_thread_index,
569        f->client_session_index, f->client_thread_index);
570
571   if (verbose)
572     {
573       ooo_segment_t *seg;
574       u32 seg_index;
575
576       s =
577         format (s, "ooo pool %d active elts\n", pool_elts (f->ooo_segments));
578
579       seg_index = f->ooos_list_head;
580
581       while (seg_index != OOO_SEGMENT_INVALID_INDEX)
582         {
583           seg = pool_elt_at_index (f->ooo_segments, seg_index);
584           s = format (s, "  pos %u, len %u next %d\n",
585                       seg->fifo_position, seg->length, seg->next);
586           seg_index = seg->next;
587         }
588     }
589   return s;
590 }
591
592 /*
593  * fd.io coding-style-patch-verification: ON
594  *
595  * Local Variables:
596  * eval: (c-set-style "gnu")
597  * End:
598  */