blob: 9eabb6879d13794cc3c3ac4e190fed97be4be76c [file] [log] [blame]
Gabriel Oginski4e88e042022-06-29 12:54:30 +00001/*
2 * Copyright (c) 2022 Intel 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 <library.h>
17#include <utils/debug.h>
18#include <threading/thread.h>
19#include <threading/condvar.h>
20#include <threading/mutex.h>
21#include <collections/array.h>
22#include <collections/hashtable.h>
23#include <vlibapi/api.h>
24#include <vlibmemory/api.h>
25#include <vlibmemory/memclnt.api_enum.h>
26
27#include "kernel_vpp_shared.h"
28
29#define vl_typedefs
30#define vl_endianfun
31/* Include the (first) vlib-api API definition layer */
32#include <vlibmemory/vl_memory_api_h.h>
33/* Include the current layer (third) vpp API definition layer */
34#include <vpp/api/vpe_types.api.h>
35#include <vpp/api/vpe.api.h>
36#undef vl_typedefs
37#undef vl_endianfun
38
39typedef struct private_vac_t private_vac_t;
40typedef struct vl_api_header_t vl_api_header_t;
41typedef struct vl_api_rheader_t vl_api_rheader_t;
42typedef struct want_event_reply_t want_event_reply_t;
43
44vac_t *vac;
45
46/**
47 * Private variables and functions of vac_t class.
48 */
49struct private_vac_t
50{
51
52 /**
53 * public part of the vac_t object.
54 */
55 vac_t public;
56
57 /**
58 * Timeout for VPP API replies, in ms
59 */
60 uint16_t read_timeout;
61
62 /**
63 * True if connected to VPP vlib
64 */
65 bool connected_to_vlib;
66
67 /**
68 * True if receive thread is running
69 */
70 bool rx_is_running;
71
72 /**
73 * Receive thread
74 */
75 thread_t *rx;
76
77 /**
78 * Mutex to lock receive queue
79 */
80 mutex_t *queue_lock;
81
82 /**
83 * Condition variable rx thread susspend
84 */
85 condvar_t *suspend_cv;
86
87 /**
88 * Condition variable rx thread resume
89 */
90 condvar_t *resume_cv;
91
92 /**
93 * Condition variable rx thread terminate
94 */
95 condvar_t *terminate_cv;
96
97 /**
98 * Mutex to lock send VPP API message entries
99 */
100 mutex_t *entries_lock;
101
102 /**
103 * VPP API message entries currently active, uintptr_t seq => entry_t
104 */
105 hashtable_t *entries;
106
107 /**
108 * Mutex to lock VPP API event entries
109 */
110 mutex_t *events_lock;
111
112 /**
113 * VPP API event entries currently active, uintptr_t id = event_t
114 */
115 hashtable_t *events;
116
117 /**
118 * Current sequence number for VPP API messages
119 */
120 refcount_t seq;
121};
122
123/**
124 * VPP API message header
125 */
126struct vl_api_header_t
127{
128
129 /** message ID */
130 uint16_t _vl_msg_id;
131
132 /** opaque cookie to identify the client */
133 uint32_t client_index;
134
135 /** client context, to match reply with request */
136 uint32_t context;
137} __attribute__ ((packed));
138
139/**
140 * VPP API response message header
141 */
142struct vl_api_rheader_t
143{
144
145 /** message ID */
146 uint16_t _vl_msg_id;
147
148 /** opaque cookie to identify the client */
149 uint32_t context;
150} __attribute__ ((packed));
151
152/**
153 * VPP API register event response message header
154 */
155struct want_event_reply_t
156{
157
158 /** message ID */
159 uint16_t _vl_msg_id;
160
161 /** opaque cookie to identify the client */
162 uint32_t context;
163
164 /** retrun code for the request */
165 int32_t retval;
166} __attribute__ ((packed));
167
168/**
169 * VPP API request entry the answer for a waiting thread is collected in
170 */
171typedef struct
172{
173 /** Condition variable thread is waiting */
174 condvar_t *condvar;
175 /** Array of reply msgs in a multi-message response, as struct rmsgbuf_t */
176 array_t *rmsgs;
177 /** All response messages received? */
178 bool complete;
179 /** Is VPP API dump? */
180 bool is_dump;
181} entry_t;
182
183/**
184 * Reply message buffer
185 */
186typedef struct
187{
188 /** Data length */
189 uint32_t data_len;
190 /** Reply data */
191 uint8_t data[0];
192} rmsgbuf_t;
193
194/**
195 * VPP API event entry
196 */
197typedef struct
198{
199 /** Event callback */
200 event_cb_t cb;
201 /** User data passed to callback */
202 void *ctx;
203} event_t;
204
205/**
206 * Free VPP API message
207 */
208static void
209vac_free (void *msg)
210{
211 vl_msg_api_free (msg);
212}
213
214/**
215 * Process a single VPP API message
216 */
217static void
218vac_api_handler (private_vac_t *this, void *msg)
219{
220 vl_api_rheader_t *rmp;
221 entry_t *entry;
222 rmsgbuf_t *rmsg;
223 uintptr_t seq, event_id;
224 u16 id = ntohs (*((u16 *) msg));
225 msgbuf_t *msgbuf = (msgbuf_t *) (((u8 *) msg) - offsetof (msgbuf_t, data));
226 int l = ntohl (msgbuf->data_len);
227 event_t *event;
228
229 if (l == 0)
230 {
231 DBG2 (DBG_KNL, "vac msg ID %d has wrong len %d", id, l);
232 vac_free (msg);
233 return;
234 }
235
236 rmp = (void *) msg;
237 seq = (uintptr_t) rmp->context;
238
239 this->entries_lock->lock (this->entries_lock);
240 entry = this->entries->get (this->entries, (void *) seq);
241 if (entry)
242 {
243 if (entry->is_dump)
244 {
245 u16 msg_id =
246 vl_msg_api_get_msg_index ((u8 *) "control_ping_reply_f6b0b8ca");
247 if (id == msg_id)
248 {
249 entry->complete = TRUE;
250 entry->condvar->signal (entry->condvar);
251 vac_free (msg);
252 this->entries_lock->unlock (this->entries_lock);
253 return;
254 }
255 }
256 else
257 {
258 entry->complete = TRUE;
259 entry->condvar->signal (entry->condvar);
260 }
261
262 rmsg = malloc (l + sizeof (msgbuf_t));
263 rmsg->data_len = l;
264 memcpy (rmsg->data, msg, l);
265 array_insert (entry->rmsgs, ARRAY_TAIL, rmsg);
266 }
267 else
268 {
269 this->events_lock->lock (this->events_lock);
270 event_id = (uintptr_t) id;
271 event = this->events->get (this->events, (void *) event_id);
272 if (event)
273 event->cb (msg, l, event->ctx);
274 else
275 DBG1 (DBG_KNL, "received unknown vac msg seq %u id %d len %d, ignored",
276 seq, id, l);
277 this->events_lock->unlock (this->events_lock);
278 }
279
280 this->entries_lock->unlock (this->entries_lock);
281 vac_free (msg);
282}
283
284/**
285 * VPP API receive thread
286 */
287static void *
288vac_rx_thread_fn (private_vac_t *this)
289{
290 svm_queue_t *q;
291 api_main_t *am = vlibapi_get_main ();
292 vl_api_memclnt_keepalive_t *mp;
293 vl_api_memclnt_keepalive_reply_t *rmp;
294 vl_shmem_hdr_t *shmem_hdr;
295 uword msg;
296
297 q = am->vl_input_queue;
298
299 while (TRUE)
300 {
301 while (!svm_queue_sub (q, (u8 *) &msg, SVM_Q_WAIT, 0))
302 {
303 u16 id = ntohs (*((u16 *) msg));
304 switch (id)
305 {
306 case VL_API_RX_THREAD_EXIT:
307 vl_msg_api_free ((void *) msg);
308 this->queue_lock->lock (this->queue_lock);
309 this->terminate_cv->signal (this->terminate_cv);
310 this->queue_lock->unlock (this->queue_lock);
311 DBG3 (DBG_KNL, "vac received rx thread exit [%d]",
312 VL_API_RX_THREAD_EXIT);
313 thread_exit (NULL);
314 return NULL;
315 break;
316
317 case VL_API_MEMCLNT_RX_THREAD_SUSPEND:
318 vl_msg_api_free ((void *) msg);
319 this->queue_lock->lock (this->queue_lock);
320 this->suspend_cv->signal (this->suspend_cv);
321 this->resume_cv->wait (this->resume_cv, this->queue_lock);
322 this->queue_lock->unlock (this->queue_lock);
323 DBG3 (DBG_KNL, "vac received rx thread suspend [%d]",
324 VL_API_MEMCLNT_RX_THREAD_SUSPEND);
325 break;
326
327 case VL_API_MEMCLNT_READ_TIMEOUT:
328 DBG3 (DBG_KNL, "vac received read timeout [%d]",
329 VL_API_MEMCLNT_READ_TIMEOUT);
330 vl_msg_api_free ((void *) msg);
331 break;
332
333 case VL_API_MEMCLNT_KEEPALIVE:
334 mp = (void *) msg;
335 rmp = vl_msg_api_alloc (sizeof (*rmp));
336 memset (rmp, 0, sizeof (*rmp));
337 u16 msg_id = vl_msg_api_get_msg_index (
338 (u8 *) "memclnt_keepalive_reply_e8d4e804");
339 rmp->_vl_msg_id = ntohs (msg_id);
340 rmp->context = mp->context;
341 shmem_hdr = am->shmem_hdr;
342 vl_msg_api_send_shmem (shmem_hdr->vl_input_queue, (u8 *) &rmp);
343 vl_msg_api_free ((void *) msg);
344 DBG3 (DBG_KNL, "vac received keepalive %d",
345 VL_API_MEMCLNT_KEEPALIVE);
346 break;
347
348 default:
349 vac_api_handler (this, (void *) msg);
350 }
351 }
352 }
353
354 return NULL;
355}
356
357METHOD (vac_t, destroy, void, private_vac_t *this)
358{
359 if (this->connected_to_vlib)
360 {
361 if (this->rx)
362 {
363 api_main_t *am = vlibapi_get_main ();
364 vl_api_rx_thread_exit_t *ep;
365 bool timed_out;
366 ep = vl_msg_api_alloc (sizeof (*ep));
367 memset (ep, 0, sizeof (*ep));
368 u16 msg_id =
369 vl_msg_api_get_msg_index ((u8 *) "rx_thread_exit_c3a3a452");
370 ep->_vl_msg_id = ntohs (msg_id);
371 vl_msg_api_send_shmem (am->vl_input_queue, (u8 *) &ep);
372 this->queue_lock->lock (this->queue_lock);
373 timed_out = this->terminate_cv->timed_wait (this->terminate_cv,
374 this->queue_lock, 5000);
375 this->queue_lock->unlock (this->queue_lock);
376 if (timed_out)
377 this->rx->cancel (this->rx);
378 else
379 this->rx->join (this->rx);
380 }
381 vl_client_disconnect ();
382 vl_client_api_unmap ();
383 }
384
385 this->queue_lock->destroy (this->queue_lock);
386 this->suspend_cv->destroy (this->suspend_cv);
387 this->resume_cv->destroy (this->resume_cv);
388 this->terminate_cv->destroy (this->terminate_cv);
389 this->entries->destroy (this->entries);
390 this->entries_lock->destroy (this->entries_lock);
391 this->events->destroy (this->events);
392 this->events_lock->destroy (this->events_lock);
393
394 vac = NULL;
395 free (this);
396}
397
398/**
399 * Write a VPP API message to shared memory
400 */
401static status_t
402vac_write (private_vac_t *this, char *p, int l, uint32_t ctx)
403{
404 api_main_t *am = vlibapi_get_main ();
405 vl_api_header_t *mp = vl_msg_api_alloc (l);
406 memset (mp, 0, sizeof (*mp));
407 svm_queue_t *q;
408
409 if (!this->connected_to_vlib)
410 return FAILED;
411
412 if (!mp)
413 return FAILED;
414
415 memcpy (mp, p, l);
416 mp->client_index = am->my_client_index;
417 mp->context = ctx;
418 q = am->shmem_hdr->vl_input_queue;
419 if (svm_queue_add (q, (u8 *) &mp, 0))
420 {
421 DBG1 (DBG_KNL, "vac vpe_api_write failed");
422 vac_free (mp);
423 return FAILED;
424 }
425
426 return SUCCESS;
427}
428
429/**
430 * Clean up a thread waiting entry
431 */
432static void
433destroy_entry (entry_t *entry)
434{
435 entry->condvar->destroy (entry->condvar);
436 array_destroy_function (entry->rmsgs, (void *) free, NULL);
437 free (entry);
438}
439
440/**
441 * Send VPP API message and wait for a reply
442 */
443static status_t
444send_vac (private_vac_t *this, char *in, int in_len, char **out, int *out_len,
445 bool is_dump)
446{
447 entry_t *entry;
448 uint32_t ctx = ref_get (&this->seq);
449 uintptr_t seq = (uintptr_t) ctx;
450 rmsgbuf_t *rmsg;
451 char *ptr;
452 int i;
453
454 this->entries_lock->lock (this->entries_lock);
455 INIT (entry, .condvar = condvar_create (CONDVAR_TYPE_DEFAULT),
456 .rmsgs = array_create (0, 0), .is_dump = is_dump, );
457 this->entries->put (this->entries, (void *) seq, entry);
458
459 if (vac_write (this, in, in_len, ctx))
460 {
461 destroy_entry (entry);
462 this->entries_lock->unlock (this->entries_lock);
463 return FAILED;
464 }
465
466 if (is_dump)
467 {
468 vl_api_control_ping_t *mp;
469 status_t rv;
470 mp = vl_msg_api_alloc (sizeof (*mp));
471 memset (mp, 0, sizeof (*mp));
472 u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "control_ping_51077d14");
473 mp->_vl_msg_id = ntohs (msg_id);
474 rv = vac_write (this, (char *) mp, sizeof (*mp), ctx);
475 vl_msg_api_free (mp);
476 if (rv)
477 {
478 DBG2 (DBG_KNL, "vac_write VL_API_CONTROL_PING failed");
479 destroy_entry (entry);
480 this->entries_lock->unlock (this->entries_lock);
481 return FAILED;
482 }
483 }
484
485 while (!entry->complete)
486 {
487 if (this->read_timeout)
488 {
489 if (entry->condvar->timed_wait (entry->condvar, this->entries_lock,
490 this->read_timeout * 1000))
491 {
492 break;
493 }
494 }
495 else
496 {
497 entry->condvar->wait (entry->condvar, this->entries_lock);
498 }
499 }
500
501 this->entries->remove (this->entries, (void *) seq);
502 this->entries_lock->unlock (this->entries_lock);
503
504 if (!entry->complete)
505 {
506 destroy_entry (entry);
507 DBG1 (DBG_KNL, "vac timeout");
508 return OUT_OF_RES;
509 }
510
511 for (i = 0, *out_len = 0; i < array_count (entry->rmsgs); i++)
512 {
513 array_get (entry->rmsgs, i, &rmsg);
514 *out_len += rmsg->data_len;
515 }
516 ptr = malloc (*out_len);
517 *out = ptr;
518 while (array_remove (entry->rmsgs, ARRAY_HEAD, &rmsg))
519 {
520 memcpy (ptr, rmsg->data, rmsg->data_len);
521 ptr += rmsg->data_len;
522 free (rmsg);
523 }
524
525 destroy_entry (entry);
526
527 return SUCCESS;
528}
529
530METHOD (vac_t, vac_send, status_t, private_vac_t *this, char *in, int in_len,
531 char **out, int *out_len)
532{
533 return send_vac (this, in, in_len, out, out_len, FALSE);
534}
535
536METHOD (vac_t, vac_send_dump, status_t, private_vac_t *this, char *in,
537 int in_len, char **out, int *out_len)
538{
539 return send_vac (this, in, in_len, out, out_len, TRUE);
540}
541
542METHOD (vac_t, register_event, status_t, private_vac_t *this, char *in,
543 int in_len, event_cb_t cb, uint16_t event_id, void *ctx)
544{
545 char *out;
546 int out_len;
547 want_event_reply_t *rmp;
548 uintptr_t id = (uintptr_t) event_id;
549 event_t *event;
550
551 if (vac->send (vac, in, in_len, &out, &out_len))
552 return FAILED;
553 rmp = (void *) out;
554 if (rmp->retval)
555 return FAILED;
556 free (out);
557 vl_msg_api_free (in);
558 this->events_lock->lock (this->events_lock);
559 INIT (event, .cb = cb, .ctx = ctx, );
560 this->events->put (this->events, (void *) id, event);
561 this->events_lock->unlock (this->events_lock);
562
563 return SUCCESS;
564}
565
566vac_t *
567vac_create (char *name)
568{
569 private_vac_t *this;
570
571 INIT(this,
572 .public = {
573 .destroy = _destroy,
574 .send = _vac_send,
575 .send_dump = _vac_send_dump,
576 .register_event = _register_event,
577 },
578 .rx_is_running = FALSE,
579 .read_timeout = lib->settings->get_int(lib->settings,
580 "%s.plugins.kernel-vpp.read_timeout", 0, lib->ns),
581 .queue_lock = mutex_create(MUTEX_TYPE_DEFAULT),
582 .suspend_cv = condvar_create(CONDVAR_TYPE_DEFAULT),
583 .resume_cv = condvar_create(CONDVAR_TYPE_DEFAULT),
584 .terminate_cv = condvar_create(CONDVAR_TYPE_DEFAULT),
585 .entries_lock = mutex_create(MUTEX_TYPE_RECURSIVE),
586 .entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4),
587 .events_lock = mutex_create(MUTEX_TYPE_DEFAULT),
588 .events = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4),
589 .seq = 0,
590 );
591
592 clib_mem_init_thread_safe (0, 256 << 20);
593
594 if (vl_client_api_map ("/vpe-api"))
595 {
596 DBG1 (DBG_KNL, "vac unable to map");
597 destroy (this);
598 return NULL;
599 }
600
601 if (vl_client_connect (name, 0, 32) < 0)
602 {
603 DBG1 (DBG_KNL, "vac unable to connect");
604 vl_client_api_unmap ();
605 destroy (this);
606 return NULL;
607 }
608
609 this->connected_to_vlib = TRUE;
610
611 this->rx = thread_create ((thread_main_t) vac_rx_thread_fn, this);
612 if (!this->rx)
613 {
614 vl_client_api_unmap ();
615 destroy (this);
616 return NULL;
617 }
618 this->rx_is_running = TRUE;
619
620 vac = &this->public;
621 return &this->public;
622}