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