| /* |
| * Copyright (c) 2022 Intel and/or its affiliates. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <library.h> |
| #include <utils/debug.h> |
| #include <threading/thread.h> |
| #include <threading/condvar.h> |
| #include <threading/mutex.h> |
| #include <collections/array.h> |
| #include <collections/hashtable.h> |
| #include <vlibapi/api.h> |
| #include <vlibmemory/api.h> |
| #include <vlibmemory/memclnt.api_enum.h> |
| |
| #include "kernel_vpp_shared.h" |
| |
| #define vl_typedefs |
| #define vl_endianfun |
| /* Include the (first) vlib-api API definition layer */ |
| #include <vlibmemory/vl_memory_api_h.h> |
| /* Include the current layer (third) vpp API definition layer */ |
| #include <vpp/api/vpe_types.api.h> |
| #include <vpp/api/vpe.api.h> |
| #undef vl_typedefs |
| #undef vl_endianfun |
| |
| typedef struct private_vac_t private_vac_t; |
| typedef struct vl_api_header_t vl_api_header_t; |
| typedef struct vl_api_rheader_t vl_api_rheader_t; |
| typedef struct want_event_reply_t want_event_reply_t; |
| |
| vac_t *vac; |
| |
| /** |
| * Private variables and functions of vac_t class. |
| */ |
| struct private_vac_t |
| { |
| |
| /** |
| * public part of the vac_t object. |
| */ |
| vac_t public; |
| |
| /** |
| * Timeout for VPP API replies, in ms |
| */ |
| uint16_t read_timeout; |
| |
| /** |
| * True if connected to VPP vlib |
| */ |
| bool connected_to_vlib; |
| |
| /** |
| * True if receive thread is running |
| */ |
| bool rx_is_running; |
| |
| /** |
| * Receive thread |
| */ |
| thread_t *rx; |
| |
| /** |
| * Mutex to lock receive queue |
| */ |
| mutex_t *queue_lock; |
| |
| /** |
| * Condition variable rx thread susspend |
| */ |
| condvar_t *suspend_cv; |
| |
| /** |
| * Condition variable rx thread resume |
| */ |
| condvar_t *resume_cv; |
| |
| /** |
| * Condition variable rx thread terminate |
| */ |
| condvar_t *terminate_cv; |
| |
| /** |
| * Mutex to lock send VPP API message entries |
| */ |
| mutex_t *entries_lock; |
| |
| /** |
| * VPP API message entries currently active, uintptr_t seq => entry_t |
| */ |
| hashtable_t *entries; |
| |
| /** |
| * Mutex to lock VPP API event entries |
| */ |
| mutex_t *events_lock; |
| |
| /** |
| * VPP API event entries currently active, uintptr_t id = event_t |
| */ |
| hashtable_t *events; |
| |
| /** |
| * Current sequence number for VPP API messages |
| */ |
| refcount_t seq; |
| }; |
| |
| /** |
| * VPP API message header |
| */ |
| struct vl_api_header_t |
| { |
| |
| /** message ID */ |
| uint16_t _vl_msg_id; |
| |
| /** opaque cookie to identify the client */ |
| uint32_t client_index; |
| |
| /** client context, to match reply with request */ |
| uint32_t context; |
| } __attribute__ ((packed)); |
| |
| /** |
| * VPP API response message header |
| */ |
| struct vl_api_rheader_t |
| { |
| |
| /** message ID */ |
| uint16_t _vl_msg_id; |
| |
| /** opaque cookie to identify the client */ |
| uint32_t context; |
| } __attribute__ ((packed)); |
| |
| /** |
| * VPP API register event response message header |
| */ |
| struct want_event_reply_t |
| { |
| |
| /** message ID */ |
| uint16_t _vl_msg_id; |
| |
| /** opaque cookie to identify the client */ |
| uint32_t context; |
| |
| /** retrun code for the request */ |
| int32_t retval; |
| } __attribute__ ((packed)); |
| |
| /** |
| * VPP API request entry the answer for a waiting thread is collected in |
| */ |
| typedef struct |
| { |
| /** Condition variable thread is waiting */ |
| condvar_t *condvar; |
| /** Array of reply msgs in a multi-message response, as struct rmsgbuf_t */ |
| array_t *rmsgs; |
| /** All response messages received? */ |
| bool complete; |
| /** Is VPP API dump? */ |
| bool is_dump; |
| } entry_t; |
| |
| /** |
| * Reply message buffer |
| */ |
| typedef struct |
| { |
| /** Data length */ |
| uint32_t data_len; |
| /** Reply data */ |
| uint8_t data[0]; |
| } rmsgbuf_t; |
| |
| /** |
| * VPP API event entry |
| */ |
| typedef struct |
| { |
| /** Event callback */ |
| event_cb_t cb; |
| /** User data passed to callback */ |
| void *ctx; |
| } event_t; |
| |
| /** |
| * Free VPP API message |
| */ |
| static void |
| vac_free (void *msg) |
| { |
| vl_msg_api_free (msg); |
| } |
| |
| /** |
| * Process a single VPP API message |
| */ |
| static void |
| vac_api_handler (private_vac_t *this, void *msg) |
| { |
| vl_api_rheader_t *rmp; |
| entry_t *entry; |
| rmsgbuf_t *rmsg; |
| uintptr_t seq, event_id; |
| u16 id = ntohs (*((u16 *) msg)); |
| msgbuf_t *msgbuf = (msgbuf_t *) (((u8 *) msg) - offsetof (msgbuf_t, data)); |
| int l = ntohl (msgbuf->data_len); |
| event_t *event; |
| |
| if (l == 0) |
| { |
| DBG2 (DBG_KNL, "vac msg ID %d has wrong len %d", id, l); |
| vac_free (msg); |
| return; |
| } |
| |
| rmp = (void *) msg; |
| seq = (uintptr_t) rmp->context; |
| |
| this->entries_lock->lock (this->entries_lock); |
| entry = this->entries->get (this->entries, (void *) seq); |
| if (entry) |
| { |
| if (entry->is_dump) |
| { |
| u16 msg_id = |
| vl_msg_api_get_msg_index ((u8 *) "control_ping_reply_f6b0b8ca"); |
| if (id == msg_id) |
| { |
| entry->complete = TRUE; |
| entry->condvar->signal (entry->condvar); |
| vac_free (msg); |
| this->entries_lock->unlock (this->entries_lock); |
| return; |
| } |
| } |
| else |
| { |
| entry->complete = TRUE; |
| entry->condvar->signal (entry->condvar); |
| } |
| |
| rmsg = malloc (l + sizeof (msgbuf_t)); |
| rmsg->data_len = l; |
| memcpy (rmsg->data, msg, l); |
| array_insert (entry->rmsgs, ARRAY_TAIL, rmsg); |
| } |
| else |
| { |
| this->events_lock->lock (this->events_lock); |
| event_id = (uintptr_t) id; |
| event = this->events->get (this->events, (void *) event_id); |
| if (event) |
| event->cb (msg, l, event->ctx); |
| else |
| DBG1 (DBG_KNL, "received unknown vac msg seq %u id %d len %d, ignored", |
| seq, id, l); |
| this->events_lock->unlock (this->events_lock); |
| } |
| |
| this->entries_lock->unlock (this->entries_lock); |
| vac_free (msg); |
| } |
| |
| /** |
| * VPP API receive thread |
| */ |
| static void * |
| vac_rx_thread_fn (private_vac_t *this) |
| { |
| svm_queue_t *q; |
| api_main_t *am = vlibapi_get_main (); |
| vl_api_memclnt_keepalive_t *mp; |
| vl_api_memclnt_keepalive_reply_t *rmp; |
| vl_shmem_hdr_t *shmem_hdr; |
| uword msg; |
| |
| q = am->vl_input_queue; |
| |
| while (TRUE) |
| { |
| while (!svm_queue_sub (q, (u8 *) &msg, SVM_Q_WAIT, 0)) |
| { |
| u16 id = ntohs (*((u16 *) msg)); |
| switch (id) |
| { |
| case VL_API_RX_THREAD_EXIT: |
| vl_msg_api_free ((void *) msg); |
| this->queue_lock->lock (this->queue_lock); |
| this->terminate_cv->signal (this->terminate_cv); |
| this->queue_lock->unlock (this->queue_lock); |
| DBG3 (DBG_KNL, "vac received rx thread exit [%d]", |
| VL_API_RX_THREAD_EXIT); |
| thread_exit (NULL); |
| return NULL; |
| break; |
| |
| case VL_API_MEMCLNT_RX_THREAD_SUSPEND: |
| vl_msg_api_free ((void *) msg); |
| this->queue_lock->lock (this->queue_lock); |
| this->suspend_cv->signal (this->suspend_cv); |
| this->resume_cv->wait (this->resume_cv, this->queue_lock); |
| this->queue_lock->unlock (this->queue_lock); |
| DBG3 (DBG_KNL, "vac received rx thread suspend [%d]", |
| VL_API_MEMCLNT_RX_THREAD_SUSPEND); |
| break; |
| |
| case VL_API_MEMCLNT_READ_TIMEOUT: |
| DBG3 (DBG_KNL, "vac received read timeout [%d]", |
| VL_API_MEMCLNT_READ_TIMEOUT); |
| vl_msg_api_free ((void *) msg); |
| break; |
| |
| case VL_API_MEMCLNT_KEEPALIVE: |
| mp = (void *) msg; |
| rmp = vl_msg_api_alloc (sizeof (*rmp)); |
| memset (rmp, 0, sizeof (*rmp)); |
| u16 msg_id = vl_msg_api_get_msg_index ( |
| (u8 *) "memclnt_keepalive_reply_e8d4e804"); |
| rmp->_vl_msg_id = ntohs (msg_id); |
| rmp->context = mp->context; |
| shmem_hdr = am->shmem_hdr; |
| vl_msg_api_send_shmem (shmem_hdr->vl_input_queue, (u8 *) &rmp); |
| vl_msg_api_free ((void *) msg); |
| DBG3 (DBG_KNL, "vac received keepalive %d", |
| VL_API_MEMCLNT_KEEPALIVE); |
| break; |
| |
| default: |
| vac_api_handler (this, (void *) msg); |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| METHOD (vac_t, destroy, void, private_vac_t *this) |
| { |
| if (this->connected_to_vlib) |
| { |
| if (this->rx) |
| { |
| api_main_t *am = vlibapi_get_main (); |
| vl_api_rx_thread_exit_t *ep; |
| bool timed_out; |
| ep = vl_msg_api_alloc (sizeof (*ep)); |
| memset (ep, 0, sizeof (*ep)); |
| u16 msg_id = |
| vl_msg_api_get_msg_index ((u8 *) "rx_thread_exit_c3a3a452"); |
| ep->_vl_msg_id = ntohs (msg_id); |
| vl_msg_api_send_shmem (am->vl_input_queue, (u8 *) &ep); |
| this->queue_lock->lock (this->queue_lock); |
| timed_out = this->terminate_cv->timed_wait (this->terminate_cv, |
| this->queue_lock, 5000); |
| this->queue_lock->unlock (this->queue_lock); |
| if (timed_out) |
| this->rx->cancel (this->rx); |
| else |
| this->rx->join (this->rx); |
| } |
| vl_client_disconnect (); |
| vl_client_api_unmap (); |
| } |
| |
| this->queue_lock->destroy (this->queue_lock); |
| this->suspend_cv->destroy (this->suspend_cv); |
| this->resume_cv->destroy (this->resume_cv); |
| this->terminate_cv->destroy (this->terminate_cv); |
| this->entries->destroy (this->entries); |
| this->entries_lock->destroy (this->entries_lock); |
| this->events->destroy (this->events); |
| this->events_lock->destroy (this->events_lock); |
| |
| vac = NULL; |
| free (this); |
| } |
| |
| /** |
| * Write a VPP API message to shared memory |
| */ |
| static status_t |
| vac_write (private_vac_t *this, char *p, int l, uint32_t ctx) |
| { |
| api_main_t *am = vlibapi_get_main (); |
| vl_api_header_t *mp = vl_msg_api_alloc (l); |
| memset (mp, 0, sizeof (*mp)); |
| svm_queue_t *q; |
| |
| if (!this->connected_to_vlib) |
| return FAILED; |
| |
| if (!mp) |
| return FAILED; |
| |
| memcpy (mp, p, l); |
| mp->client_index = am->my_client_index; |
| mp->context = ctx; |
| q = am->shmem_hdr->vl_input_queue; |
| if (svm_queue_add (q, (u8 *) &mp, 0)) |
| { |
| DBG1 (DBG_KNL, "vac vpe_api_write failed"); |
| vac_free (mp); |
| return FAILED; |
| } |
| |
| return SUCCESS; |
| } |
| |
| /** |
| * Clean up a thread waiting entry |
| */ |
| static void |
| destroy_entry (entry_t *entry) |
| { |
| entry->condvar->destroy (entry->condvar); |
| array_destroy_function (entry->rmsgs, (void *) free, NULL); |
| free (entry); |
| } |
| |
| /** |
| * Send VPP API message and wait for a reply |
| */ |
| static status_t |
| send_vac (private_vac_t *this, char *in, int in_len, char **out, int *out_len, |
| bool is_dump) |
| { |
| entry_t *entry; |
| uint32_t ctx = ref_get (&this->seq); |
| uintptr_t seq = (uintptr_t) ctx; |
| rmsgbuf_t *rmsg; |
| char *ptr; |
| int i; |
| |
| this->entries_lock->lock (this->entries_lock); |
| INIT (entry, .condvar = condvar_create (CONDVAR_TYPE_DEFAULT), |
| .rmsgs = array_create (0, 0), .is_dump = is_dump, ); |
| this->entries->put (this->entries, (void *) seq, entry); |
| |
| if (vac_write (this, in, in_len, ctx)) |
| { |
| destroy_entry (entry); |
| this->entries_lock->unlock (this->entries_lock); |
| return FAILED; |
| } |
| |
| if (is_dump) |
| { |
| vl_api_control_ping_t *mp; |
| status_t rv; |
| mp = vl_msg_api_alloc (sizeof (*mp)); |
| memset (mp, 0, sizeof (*mp)); |
| u16 msg_id = vl_msg_api_get_msg_index ((u8 *) "control_ping_51077d14"); |
| mp->_vl_msg_id = ntohs (msg_id); |
| rv = vac_write (this, (char *) mp, sizeof (*mp), ctx); |
| vl_msg_api_free (mp); |
| if (rv) |
| { |
| DBG2 (DBG_KNL, "vac_write VL_API_CONTROL_PING failed"); |
| destroy_entry (entry); |
| this->entries_lock->unlock (this->entries_lock); |
| return FAILED; |
| } |
| } |
| |
| while (!entry->complete) |
| { |
| if (this->read_timeout) |
| { |
| if (entry->condvar->timed_wait (entry->condvar, this->entries_lock, |
| this->read_timeout * 1000)) |
| { |
| break; |
| } |
| } |
| else |
| { |
| entry->condvar->wait (entry->condvar, this->entries_lock); |
| } |
| } |
| |
| this->entries->remove (this->entries, (void *) seq); |
| this->entries_lock->unlock (this->entries_lock); |
| |
| if (!entry->complete) |
| { |
| destroy_entry (entry); |
| DBG1 (DBG_KNL, "vac timeout"); |
| return OUT_OF_RES; |
| } |
| |
| for (i = 0, *out_len = 0; i < array_count (entry->rmsgs); i++) |
| { |
| array_get (entry->rmsgs, i, &rmsg); |
| *out_len += rmsg->data_len; |
| } |
| ptr = malloc (*out_len); |
| *out = ptr; |
| while (array_remove (entry->rmsgs, ARRAY_HEAD, &rmsg)) |
| { |
| memcpy (ptr, rmsg->data, rmsg->data_len); |
| ptr += rmsg->data_len; |
| free (rmsg); |
| } |
| |
| destroy_entry (entry); |
| |
| return SUCCESS; |
| } |
| |
| METHOD (vac_t, vac_send, status_t, private_vac_t *this, char *in, int in_len, |
| char **out, int *out_len) |
| { |
| return send_vac (this, in, in_len, out, out_len, FALSE); |
| } |
| |
| METHOD (vac_t, vac_send_dump, status_t, private_vac_t *this, char *in, |
| int in_len, char **out, int *out_len) |
| { |
| return send_vac (this, in, in_len, out, out_len, TRUE); |
| } |
| |
| METHOD (vac_t, register_event, status_t, private_vac_t *this, char *in, |
| int in_len, event_cb_t cb, uint16_t event_id, void *ctx) |
| { |
| char *out; |
| int out_len; |
| want_event_reply_t *rmp; |
| uintptr_t id = (uintptr_t) event_id; |
| event_t *event; |
| |
| if (vac->send (vac, in, in_len, &out, &out_len)) |
| return FAILED; |
| rmp = (void *) out; |
| if (rmp->retval) |
| return FAILED; |
| free (out); |
| vl_msg_api_free (in); |
| this->events_lock->lock (this->events_lock); |
| INIT (event, .cb = cb, .ctx = ctx, ); |
| this->events->put (this->events, (void *) id, event); |
| this->events_lock->unlock (this->events_lock); |
| |
| return SUCCESS; |
| } |
| |
| vac_t * |
| vac_create (char *name) |
| { |
| private_vac_t *this; |
| |
| INIT(this, |
| .public = { |
| .destroy = _destroy, |
| .send = _vac_send, |
| .send_dump = _vac_send_dump, |
| .register_event = _register_event, |
| }, |
| .rx_is_running = FALSE, |
| .read_timeout = lib->settings->get_int(lib->settings, |
| "%s.plugins.kernel-vpp.read_timeout", 0, lib->ns), |
| .queue_lock = mutex_create(MUTEX_TYPE_DEFAULT), |
| .suspend_cv = condvar_create(CONDVAR_TYPE_DEFAULT), |
| .resume_cv = condvar_create(CONDVAR_TYPE_DEFAULT), |
| .terminate_cv = condvar_create(CONDVAR_TYPE_DEFAULT), |
| .entries_lock = mutex_create(MUTEX_TYPE_RECURSIVE), |
| .entries = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), |
| .events_lock = mutex_create(MUTEX_TYPE_DEFAULT), |
| .events = hashtable_create(hashtable_hash_ptr, hashtable_equals_ptr, 4), |
| .seq = 0, |
| ); |
| |
| clib_mem_init_thread_safe (0, 256 << 20); |
| |
| if (vl_client_api_map ("/vpe-api")) |
| { |
| DBG1 (DBG_KNL, "vac unable to map"); |
| destroy (this); |
| return NULL; |
| } |
| |
| if (vl_client_connect (name, 0, 32) < 0) |
| { |
| DBG1 (DBG_KNL, "vac unable to connect"); |
| vl_client_api_unmap (); |
| destroy (this); |
| return NULL; |
| } |
| |
| this->connected_to_vlib = TRUE; |
| |
| this->rx = thread_create ((thread_main_t) vac_rx_thread_fn, this); |
| if (!this->rx) |
| { |
| vl_client_api_unmap (); |
| destroy (this); |
| return NULL; |
| } |
| this->rx_is_running = TRUE; |
| |
| vac = &this->public; |
| return &this->public; |
| } |