blob: e958866b8536b1f65f9d555c0fa3b63894b18c02 [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
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000299 const u16 msg_id_rx_thread_exit =
300 vl_msg_api_get_msg_index ((u8 *) "rx_thread_exit_c3a3a452");
301 const u16 msg_id_memclnt_rx_thread_suspend =
302 vl_msg_api_get_msg_index ((u8 *) "memclnt_rx_thread_suspend_c3a3a452");
303 const u16 msg_id_memclnt_read_timeout =
304 vl_msg_api_get_msg_index ((u8 *) "memclnt_read_timeout_c3a3a452");
305 const u16 msg_id_memclnt_keepalive =
306 vl_msg_api_get_msg_index ((u8 *) "memclnt_keepalive_51077d14");
307
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000308 while (TRUE)
309 {
310 while (!svm_queue_sub (q, (u8 *) &msg, SVM_Q_WAIT, 0))
311 {
312 u16 id = ntohs (*((u16 *) msg));
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000313
314 if (msg_id_rx_thread_exit == id)
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000315 {
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000316 vl_msg_api_free ((void *) msg);
317 this->queue_lock->lock (this->queue_lock);
318 this->terminate_cv->signal (this->terminate_cv);
319 this->queue_lock->unlock (this->queue_lock);
320 DBG3 (DBG_KNL, "vac received rx thread exit [%d]",
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000321 msg_id_rx_thread_exit);
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000322 thread_exit (NULL);
323 return NULL;
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000324 }
325 else if (msg_id_memclnt_rx_thread_suspend == id)
326 {
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000327 vl_msg_api_free ((void *) msg);
328 this->queue_lock->lock (this->queue_lock);
329 this->suspend_cv->signal (this->suspend_cv);
330 this->resume_cv->wait (this->resume_cv, this->queue_lock);
331 this->queue_lock->unlock (this->queue_lock);
332 DBG3 (DBG_KNL, "vac received rx thread suspend [%d]",
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000333 msg_id_memclnt_rx_thread_suspend);
334 }
335 else if (msg_id_memclnt_read_timeout == id)
336 {
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000337 DBG3 (DBG_KNL, "vac received read timeout [%d]",
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000338 msg_id_memclnt_read_timeout);
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000339 vl_msg_api_free ((void *) msg);
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000340 }
341 else if (msg_id_memclnt_keepalive == id)
342 {
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000343 mp = (void *) msg;
344 rmp = vl_msg_api_alloc (sizeof (*rmp));
345 memset (rmp, 0, sizeof (*rmp));
346 u16 msg_id = vl_msg_api_get_msg_index (
347 (u8 *) "memclnt_keepalive_reply_e8d4e804");
348 rmp->_vl_msg_id = ntohs (msg_id);
349 rmp->context = mp->context;
350 shmem_hdr = am->shmem_hdr;
351 vl_msg_api_send_shmem (shmem_hdr->vl_input_queue, (u8 *) &rmp);
352 vl_msg_api_free ((void *) msg);
353 DBG3 (DBG_KNL, "vac received keepalive %d",
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000354 msg_id_memclnt_keepalive);
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000355 }
Gabriel Oginskic3f505f2023-07-07 11:58:26 +0000356 else
357 vac_api_handler (this, (void *) msg);
Gabriel Oginski4e88e042022-06-29 12:54:30 +0000358 }
359 }
360
361 return NULL;
362}
363
364METHOD (vac_t, destroy, void, private_vac_t *this)
365{
366 if (this->connected_to_vlib)
367 {
368 if (this->rx)
369 {
370 api_main_t *am = vlibapi_get_main ();
371 vl_api_rx_thread_exit_t *ep;
372 bool timed_out;
373 ep = vl_msg_api_alloc (sizeof (*ep));
374 memset (ep, 0, sizeof (*ep));
375 u16 msg_id =
376 vl_msg_api_get_msg_index ((u8 *) "rx_thread_exit_c3a3a452");
377 ep->_vl_msg_id = ntohs (msg_id);
378 vl_msg_api_send_shmem (am->vl_input_queue, (u8 *) &ep);
379 this->queue_lock->lock (this->queue_lock);
380 timed_out = this->terminate_cv->timed_wait (this->terminate_cv,
381 this->queue_lock, 5000);
382 this->queue_lock->unlock (this->queue_lock);
383 if (timed_out)
384 this->rx->cancel (this->rx);
385 else
386 this->rx->join (this->rx);
387 }
388 vl_client_disconnect ();
389 vl_client_api_unmap ();
390 }
391
392 this->queue_lock->destroy (this->queue_lock);
393 this->suspend_cv->destroy (this->suspend_cv);
394 this->resume_cv->destroy (this->resume_cv);
395 this->terminate_cv->destroy (this->terminate_cv);
396 this->entries->destroy (this->entries);
397 this->entries_lock->destroy (this->entries_lock);
398 this->events->destroy (this->events);
399 this->events_lock->destroy (this->events_lock);
400
401 vac = NULL;
402 free (this);
403}
404
405/**
406 * Write a VPP API message to shared memory
407 */
408static status_t
409vac_write (private_vac_t *this, char *p, int l, uint32_t ctx)
410{
411 api_main_t *am = vlibapi_get_main ();
412 vl_api_header_t *mp = vl_msg_api_alloc (l);
413 memset (mp, 0, sizeof (*mp));
414 svm_queue_t *q;
415
416 if (!this->connected_to_vlib)
417 return FAILED;
418
419 if (!mp)
420 return FAILED;
421
422 memcpy (mp, p, l);
423 mp->client_index = am->my_client_index;
424 mp->context = ctx;
425 q = am->shmem_hdr->vl_input_queue;
426 if (svm_queue_add (q, (u8 *) &mp, 0))
427 {
428 DBG1 (DBG_KNL, "vac vpe_api_write failed");
429 vac_free (mp);
430 return FAILED;
431 }
432
433 return SUCCESS;
434}
435
436/**
437 * Clean up a thread waiting entry
438 */
439static void
440destroy_entry (entry_t *entry)
441{
442 entry->condvar->destroy (entry->condvar);
443 array_destroy_function (entry->rmsgs, (void *) free, NULL);
444 free (entry);
445}
446
447/**
448 * Send VPP API message and wait for a reply
449 */
450static status_t
451send_vac (private_vac_t *this, char *in, int in_len, char **out, int *out_len,
452 bool is_dump)
453{
454 entry_t *entry;
455 uint32_t ctx = ref_get (&this->seq);
456 uintptr_t seq = (uintptr_t) ctx;
457 rmsgbuf_t *rmsg;
458 char *ptr;
459 int i;
460
461 this->entries_lock->lock (this->entries_lock);
462 INIT (entry, .condvar = condvar_create (CONDVAR_TYPE_DEFAULT),
463 .rmsgs = array_create (0, 0), .is_dump = is_dump, );
464 this->entries->put (this->entries, (void *) seq, entry);
465
466 if (vac_write (this, in, in_len, ctx))
467 {
468 destroy_entry (entry);
469 this->entries_lock->unlock (this->entries_lock);
470 return FAILED;
471 }
472
473 if (is_dump)
474 {
475 vl_api_control_ping_t *mp;
476 status_t rv;
477 mp = vl_msg_api_alloc (sizeof (*mp));
478 memset (mp, 0, sizeof (*mp));
479 u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "control_ping_51077d14");
480 mp->_vl_msg_id = ntohs (msg_id);
481 rv = vac_write (this, (char *) mp, sizeof (*mp), ctx);
482 vl_msg_api_free (mp);
483 if (rv)
484 {
485 DBG2 (DBG_KNL, "vac_write VL_API_CONTROL_PING failed");
486 destroy_entry (entry);
487 this->entries_lock->unlock (this->entries_lock);
488 return FAILED;
489 }
490 }
491
492 while (!entry->complete)
493 {
494 if (this->read_timeout)
495 {
496 if (entry->condvar->timed_wait (entry->condvar, this->entries_lock,
497 this->read_timeout * 1000))
498 {
499 break;
500 }
501 }
502 else
503 {
504 entry->condvar->wait (entry->condvar, this->entries_lock);
505 }
506 }
507
508 this->entries->remove (this->entries, (void *) seq);
509 this->entries_lock->unlock (this->entries_lock);
510
511 if (!entry->complete)
512 {
513 destroy_entry (entry);
514 DBG1 (DBG_KNL, "vac timeout");
515 return OUT_OF_RES;
516 }
517
518 for (i = 0, *out_len = 0; i < array_count (entry->rmsgs); i++)
519 {
520 array_get (entry->rmsgs, i, &rmsg);
521 *out_len += rmsg->data_len;
522 }
523 ptr = malloc (*out_len);
524 *out = ptr;
525 while (array_remove (entry->rmsgs, ARRAY_HEAD, &rmsg))
526 {
527 memcpy (ptr, rmsg->data, rmsg->data_len);
528 ptr += rmsg->data_len;
529 free (rmsg);
530 }
531
532 destroy_entry (entry);
533
534 return SUCCESS;
535}
536
537METHOD (vac_t, vac_send, status_t, private_vac_t *this, char *in, int in_len,
538 char **out, int *out_len)
539{
540 return send_vac (this, in, in_len, out, out_len, FALSE);
541}
542
543METHOD (vac_t, vac_send_dump, status_t, private_vac_t *this, char *in,
544 int in_len, char **out, int *out_len)
545{
546 return send_vac (this, in, in_len, out, out_len, TRUE);
547}
548
549METHOD (vac_t, register_event, status_t, private_vac_t *this, char *in,
550 int in_len, event_cb_t cb, uint16_t event_id, void *ctx)
551{
552 char *out;
553 int out_len;
554 want_event_reply_t *rmp;
555 uintptr_t id = (uintptr_t) event_id;
556 event_t *event;
557
558 if (vac->send (vac, in, in_len, &out, &out_len))
559 return FAILED;
560 rmp = (void *) out;
561 if (rmp->retval)
562 return FAILED;
563 free (out);
564 vl_msg_api_free (in);
565 this->events_lock->lock (this->events_lock);
566 INIT (event, .cb = cb, .ctx = ctx, );
567 this->events->put (this->events, (void *) id, event);
568 this->events_lock->unlock (this->events_lock);
569
570 return SUCCESS;
571}
572
573vac_t *
574vac_create (char *name)
575{
576 private_vac_t *this;
577
578 INIT(this,
579 .public = {
580 .destroy = _destroy,
581 .send = _vac_send,
582 .send_dump = _vac_send_dump,
583 .register_event = _register_event,
584 },
585 .rx_is_running = FALSE,
586 .read_timeout = lib->settings->get_int(lib->settings,
587 "%s.plugins.kernel-vpp.read_timeout", 0, lib->ns),
588 .queue_lock = mutex_create(MUTEX_TYPE_DEFAULT),
589 .suspend_cv = condvar_create(CONDVAR_TYPE_DEFAULT),
590 .resume_cv = condvar_create(CONDVAR_TYPE_DEFAULT),
591 .terminate_cv = condvar_create(CONDVAR_TYPE_DEFAULT),
592 .entries_lock = mutex_create(MUTEX_TYPE_RECURSIVE),
593 .entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4),
594 .events_lock = mutex_create(MUTEX_TYPE_DEFAULT),
595 .events = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4),
596 .seq = 0,
597 );
598
599 clib_mem_init_thread_safe (0, 256 << 20);
600
601 if (vl_client_api_map ("/vpe-api"))
602 {
603 DBG1 (DBG_KNL, "vac unable to map");
604 destroy (this);
605 return NULL;
606 }
607
608 if (vl_client_connect (name, 0, 32) < 0)
609 {
610 DBG1 (DBG_KNL, "vac unable to connect");
611 vl_client_api_unmap ();
612 destroy (this);
613 return NULL;
614 }
615
616 this->connected_to_vlib = TRUE;
617
618 this->rx = thread_create ((thread_main_t) vac_rx_thread_fn, this);
619 if (!this->rx)
620 {
621 vl_client_api_unmap ();
622 destroy (this);
623 return NULL;
624 }
625 this->rx_is_running = TRUE;
626
627 vac = &this->public;
628 return &this->public;
629}