summaryrefslogtreecommitdiff
path: root/middleware/multicore/open-amp/rpmsg/rpmsg_rtos.c
diff options
context:
space:
mode:
Diffstat (limited to 'middleware/multicore/open-amp/rpmsg/rpmsg_rtos.c')
-rw-r--r--middleware/multicore/open-amp/rpmsg/rpmsg_rtos.c615
1 files changed, 615 insertions, 0 deletions
diff --git a/middleware/multicore/open-amp/rpmsg/rpmsg_rtos.c b/middleware/multicore/open-amp/rpmsg/rpmsg_rtos.c
new file mode 100644
index 0000000..eff0d67
--- /dev/null
+++ b/middleware/multicore/open-amp/rpmsg/rpmsg_rtos.c
@@ -0,0 +1,615 @@
+/*
+ * Copyright (c) 2015 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3. Neither the name of Freescale Semiconductor nor the names of its
+ * contributors may be used to endorse or promote products derived from this
+ * software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**************************************************************************
+ * FILE NAME
+ *
+ * rpmsg_rtos.c
+ *
+ * COMPONENT
+ *
+ * OpenAMP
+ *
+ * DESCRIPTION
+ *
+ * This file provides RTOS adaptation layer that allows:
+ * - handling of received messages outside the interrupt context
+ * - the implementation of blocking API for the RPMsg receive side
+ * - provides zero-copy receive functionality
+ * - provides zero-copy send functionality
+ *
+ **************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stddef.h>
+#include <assert.h>
+#include "rpmsg.h"
+#include "rpmsg_ext.h"
+#include "rpmsg_rtos.h"
+
+#define RPMSG_RECV_NOCOPY_CHECK_PTRS (0)
+
+typedef struct
+{
+ unsigned long src;
+ void* data;
+ short int len;
+} rpmsg_callback_message_t;
+
+#if RPMSG_RECV_NOCOPY_CHECK_PTRS
+
+typedef struct rpmsg_ptr_llist
+{
+ void* ptr;
+ struct rpmsg_ptr_llist* next;
+
+} rpmsg_ptr_llist_t;
+
+static rpmsg_ptr_llist_t* rpmsg_in_app_buffers_head = NULL;
+static rpmsg_ptr_llist_t* rpmsg_in_app_buffers_tail = NULL;
+
+#endif
+
+static struct rpmsg_channel *default_chnl = NULL;
+static void *default_q = NULL;
+static void *callback_sync = NULL;
+static int rpmsg_queue_size;
+
+extern int platform_get_processor_info(struct hil_proc *proc, int cpu_id);
+
+/* This callback gets invoked when the remote chanl is created */
+static void rpmsg_channel_created_rtos(struct rpmsg_channel *rp_chnl)
+{
+ assert(rp_chnl && rp_chnl->rp_ept);
+ /* message queue for channel default endpoint should be available */
+ assert(default_q);
+ rp_chnl->rp_ept->priv = default_q;
+
+ default_chnl = rp_chnl;
+
+ /* Unblock the task by releasing the semaphore. */
+ env_release_sync_lock(callback_sync);
+}
+
+/* This callback gets invoked when the remote channel is deleted */
+static void rpmsg_channel_deleted_rtos(struct rpmsg_channel *rp_chnl)
+{
+ assert(rp_chnl);
+
+ default_chnl = NULL;
+}
+
+/**
+ * rpmsg_read_rtos_cb
+ *
+ * This is the RPMsg receive callback function used in an RTOS environment.
+ *
+ * @param rp_chnl - pointer to the RPMsg channel on which data is received
+ * @param data - pointer to the buffer containing received data
+ * @param len - size of data received, in bytes
+ * @param priv - private data provided during endpoint creation
+ * @param src - pointer to address of the endpoint from which data is received
+ *
+ * @return void
+ */
+static void rpmsg_read_rtos_cb(struct rpmsg_channel *rp_chnl, void *data, int len,
+ void * priv, unsigned long src)
+{
+ rpmsg_callback_message_t msg;
+
+ assert(priv);
+ assert(rp_chnl);
+
+ msg.data = data;
+ msg.len = len;
+ msg.src = src;
+
+ /* if message is successfully added into queue then hold rpmsg buffer */
+ if(env_put_queue(priv, &msg, 0))
+ {
+ /* hold the rx buffer */
+ rpmsg_hold_rx_buffer(rp_chnl, data);
+ }
+}
+
+/*!
+ * @brief
+ * This function allocates and initializes the rpmsg driver resources for
+ * given device ID (cpu id). The successful return from this function leaves
+ * fully enabled IPC link. RTOS aware version.
+ *
+ * @param[in] dev_id Remote device for which driver is to be initialized
+ * @param[out] rdev Pointer to newly created remote device
+ * @param[in] role Role of the other device, Master or Remote
+ * @param[out] def_chnl Pointer to rpmsg channel
+ *
+ * @return Status of function execution
+ *
+ * @see rpmsg_rtos_deinit
+ */
+int rpmsg_rtos_init(int dev_id, struct remote_device **rdev, int role, struct rpmsg_channel **def_chnl)
+{
+ int status;
+ struct hil_proc proc;
+
+ /* single instance allowed! */
+ if(callback_sync != NULL)
+ {
+ return RPMSG_ERR_NO_MEM;
+ }
+
+ /* get HW specific info */
+ status = platform_get_processor_info(&proc, dev_id);
+ if(status)
+ {
+ return RPMSG_ERR_DEV_ID;
+ }
+
+ /* create synchronization object used during the initialization process */
+ status = env_create_sync_lock(&callback_sync, 0);
+ if((status) || (callback_sync == NULL))
+ {
+ return RPMSG_ERR_NO_MEM;
+ }
+
+ /* get rpmsg vring rx buffer count */
+ rpmsg_queue_size = role == RPMSG_MASTER ? proc.vdev.vring_info[1].num_descs : proc.vdev.vring_info[0].num_descs;
+ /* create message queue for channel default endpoint */
+ status = env_create_queue(&default_q, rpmsg_queue_size, sizeof(rpmsg_callback_message_t));
+ if((status) || (default_q == NULL))
+ {
+ env_delete_sync_lock(callback_sync);
+ return RPMSG_ERR_NO_MEM;
+ }
+
+#if RPMSG_RECV_NOCOPY_CHECK_PTRS
+ rpmsg_in_app_buffers_head = (rpmsg_ptr_llist_t*)env_allocate_memory(sizeof(rpmsg_ptr_llist_t));
+ assert(rpmsg_in_app_buffers_head != NULL);
+ memset((void*)rpmsg_in_app_buffers_head, 0, sizeof(rpmsg_ptr_llist_t));
+ rpmsg_in_app_buffers_tail = rpmsg_in_app_buffers_head;
+#endif
+
+ /* initialize the RPMsg communication */
+ status = rpmsg_init(dev_id, rdev, rpmsg_channel_created_rtos, rpmsg_channel_deleted_rtos, rpmsg_read_rtos_cb, role);
+
+ /* wait until the channel is established (rpmsg_channel_created callback is issued) */
+ env_acquire_sync_lock(callback_sync);
+
+ /* delete synchronization object used during the initialization process */
+ env_delete_sync_lock(callback_sync);
+ callback_sync = NULL;
+
+ if(default_chnl == NULL)
+ {
+ return RPMSG_ERR_NO_MEM;
+ }
+ else
+ {
+ *def_chnl = default_chnl;
+ return status;
+ }
+}
+
+/*!
+ * @brief
+ * This function frees rpmsg driver resources for given remote device. RTOS aware version.
+ *
+ * @param[in] rdev Pointer to device to de-init
+ *
+ * @see rpmsg_rtos_init
+ */
+void rpmsg_rtos_deinit(struct remote_device *rdev)
+{
+ assert(rdev != NULL);
+
+#if RPMSG_RECV_NOCOPY_CHECK_PTRS
+ rpmsg_ptr_llist_t * iterator = NULL;
+ rpmsg_ptr_llist_t * next = NULL;
+
+ env_lock_mutex(rdev->lock);
+
+ assert(rpmsg_in_app_buffers_head != NULL);
+
+ for(iterator = rpmsg_in_app_buffers_head; iterator != NULL; iterator = next)
+ {
+ next = iterator->next;
+ env_free_memory((void*)iterator);
+ }
+
+ rpmsg_in_app_buffers_head = NULL;
+ rpmsg_in_app_buffers_tail = NULL;
+
+ env_unlock_mutex(rdev->lock);
+#endif
+ /* de-initialize the RPMsg communication */
+ rpmsg_deinit(rdev);
+
+ /* delete message queue used by the channel default endpoint */
+ if (default_q)
+ {
+ env_delete_queue(default_q);
+ default_q = NULL;
+ }
+}
+
+/*!
+ * @brief
+ * This function creates rpmsg endpoint for the rpmsg channel. RTOS aware version.
+ *
+ * @param[in] rp_chnl Pointer to rpmsg channel
+ * @param[in] addr Endpoint src address
+ *
+ * @return Pointer to endpoint control block
+ *
+ * @see rpmsg_rtos_destroy_ept
+ */
+struct rpmsg_endpoint * rpmsg_rtos_create_ept(struct rpmsg_channel *rp_chnl, unsigned long addr)
+{
+ struct rpmsg_endpoint * retval;
+ void *q = NULL;
+
+ if (!rp_chnl) return NULL;
+ if (!rp_chnl->rdev) return NULL;
+
+ /* create message queue for the specified endpoint */
+ env_create_queue(&q, rpmsg_queue_size, sizeof(rpmsg_callback_message_t));
+ if(q == NULL)
+ {
+ return NULL;
+ }
+
+ /* create the RPMsg endpoint */
+ retval = rpmsg_create_ept(rp_chnl, rpmsg_read_rtos_cb, (void*)q, addr);
+
+ if(retval == NULL)
+ {
+ env_delete_queue(q);
+ return NULL;
+ }
+ else
+ {
+ return retval;
+ }
+}
+
+/*!
+ * @brief
+ * This function deletes rpmsg endpoint and performs cleanup. RTOS aware version.
+ *
+ * @param[in] rp_ept Pointer to endpoint to destroy
+ *
+ * @see rpmsg_rtos_create_ept
+ */
+void rpmsg_rtos_destroy_ept(struct rpmsg_endpoint *rp_ept)
+{
+ void *q = rp_ept->priv;
+
+ /* delete the RPMsg endpoint */
+ rpmsg_destroy_ept(rp_ept);
+
+ /* delete message queue used by the specified endpoint */
+ env_delete_queue(q);
+}
+
+/*!
+ * @brief
+ * RTOS receive function - blocking version of the received function that can be called from an RTOS task.
+ * The data is copied from the receive buffer into the user supplied buffer.
+ *
+ * This is the "receive with copy" version of the RPMsg receive function. This version is simple
+ * to use but it requires copying data from shared memory into the user space buffer.
+ * The user has no obligation or burden to manage the shared memory buffers.
+ *
+ * @param[in] ept Pointer to the RPMsg endpoint on which data is received
+ * @param[in] data Pointer to the user buffer the received data are copied to
+ * @param[out] len Pointer to an int variable that will contain the number of bytes actually copied into the buffer
+ * @param[in] maxlen Maximum number of bytes to copy (received buffer size)
+ * @param[out] src Pointer to address of the endpoint from which data is received
+ * @param[in] timeout_ms Timeout, in milliseconds, to wait for a message. A value of 0 means don't wait (non-blocking call).
+ * A value of 0xffffffff means wait forever (blocking call).
+ *
+ * @return Status of function execution
+ *
+ * @see rpmsg_rtos_recv_nocopy
+ */
+int rpmsg_rtos_recv(struct rpmsg_endpoint *ept, void *data, int* len, int maxlen, unsigned long* src, int timeout_ms)
+{
+ rpmsg_callback_message_t msg;
+ int retval = RPMSG_SUCCESS;
+
+ if (!data) return RPMSG_ERR_PARAM;
+ if (!ept) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl->rdev) return RPMSG_ERR_PARAM;
+
+ /* Get an element out of the message queue for the selected endpoint */
+ if(env_get_queue(ept->priv, &msg, timeout_ms))
+ {
+ if(src != NULL) *src = msg.src;
+ if(len != NULL) *len = msg.len;
+
+ if(maxlen >= msg.len)
+ {
+ memcpy(data, msg.data, maxlen);
+ }
+ else
+ {
+ retval = RPMSG_ERR_BUFF_SIZE;
+ }
+
+ /* Return used buffers. */
+ rpmsg_release_rx_buffer(ept->rp_chnl, msg.data);
+
+ return retval;
+ }
+ else
+ {
+ return RPMSG_ERR_NO_BUFF; /* failed */
+ }
+}
+
+/*!
+ * @brief
+ * RTOS receive function - blocking version of the received function that can be called from an RTOS task.
+ * The data is NOT copied into the user-app. buffer.
+ *
+ * This is the "zero-copy receive" version of the RPMsg receive function. No data is copied.
+ * Only the pointer to the data is returned. This version is fast, but it requires the user to manage
+ * buffer allocation. Specifically, the user must decide when a buffer is no longer in use and
+ * make the appropriate API call to free it, see rpmsg_rtos_recv_nocopy_free().
+ *
+ * @param[in] ept Pointer to the RPMsg endpoint on which data is received
+ * @param[out] data Pointer to the RPMsg buffer of the shared memory where the received data is stored
+ * @param[out] len Pointer to an int variable that that will contain the number of valid bytes in the RPMsg buffer
+ * @param[out] src Pointer to address of the endpoint from which data is received
+ * @param[in] timeout_ms Timeout, in milliseconds, to wait for a message. A value of 0 means don't wait (non-blocking call).
+ * A value of 0xffffffff means wait forever (blocking call).
+ *
+ * @return Status of function execution
+ *
+ * @see rpmsg_rtos_recv_nocopy_free
+ * @see rpmsg_rtos_recv
+ */
+int rpmsg_rtos_recv_nocopy(struct rpmsg_endpoint *ept, void **data, int* len, unsigned long* src, int timeout_ms)
+{
+ rpmsg_callback_message_t msg;
+
+ if (!data) return RPMSG_ERR_PARAM;
+ if (!ept) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl->rdev) return RPMSG_ERR_PARAM;
+
+ /* Get an element out of the message queue for the selected endpoint */
+ if(env_get_queue(ept->priv, &msg, timeout_ms))
+ {
+ if(src != NULL) *src = msg.src;
+ if(len != NULL) *len = msg.len;
+
+ *data = msg.data;
+
+#if RPMSG_RECV_NOCOPY_CHECK_PTRS
+ {
+ struct remote_device * rdev = NULL;
+
+ if (!ept->rp_chnl->rdev) return RPMSG_ERR_PARAM;
+ rdev = ept->rp_chnl->rdev;
+
+ env_lock_mutex(rdev->lock);
+ assert(rpmsg_in_app_buffers_tail != NULL);
+ assert(msg.data != NULL);
+ rpmsg_in_app_buffers_tail->ptr = msg.data;
+ rpmsg_in_app_buffers_tail->next = (rpmsg_ptr_llist_t*)env_allocate_memory(sizeof(rpmsg_ptr_llist_t));
+ assert(rpmsg_in_app_buffers_tail->next);
+ rpmsg_in_app_buffers_tail = rpmsg_in_app_buffers_tail->next;
+ memset((void*)rpmsg_in_app_buffers_tail, 0, sizeof(rpmsg_ptr_llist_t));
+ env_unlock_mutex(rdev->lock);
+ }
+#endif
+
+ return RPMSG_SUCCESS; /* success */
+
+ }
+
+ return RPMSG_ERR_NO_BUFF; /* failed */
+}
+
+/*!
+ * @brief This function frees a buffer previously returned by rpmsg_rtos_recv_nocopy().
+ *
+ * Once the zero-copy mechanism of receiving data is used, this function
+ * has to be called to free a buffer and to make it available for the next data
+ * transfer.
+ *
+ * @param[in] ept Pointer to the RPMsg endpoint that has consumed received data
+ * @param[in] data Pointer to the RPMsg buffer of the shared memory that has to be freed
+ *
+ * @return Status of function execution
+ *
+ * @see rpmsg_rtos_recv_nocopy
+ */
+int rpmsg_rtos_recv_nocopy_free(struct rpmsg_endpoint *ept, void* data)
+{
+ if (!data) return RPMSG_ERR_PARAM;
+ if (!ept) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl->rdev) return RPMSG_ERR_PARAM;
+
+#if RPMSG_RECV_NOCOPY_CHECK_PTRS
+ {
+ /* Allow only previously allocated buffers to be freed,
+ * invalid pointer values could influence the destination core,
+ * which is a security issue. */
+ struct remote_device * rdev = NULL;
+ rpmsg_ptr_llist_t * iterator = NULL;
+ rpmsg_ptr_llist_t * prev = NULL;
+
+ if (!ept->rp_chnl->rdev) return RPMSG_ERR_PARAM;
+ rdev = ept->rp_chnl->rdev;
+
+ env_lock_mutex(rdev->lock);
+
+ assert(rpmsg_in_app_buffers_head != NULL);
+
+ for(iterator = rpmsg_in_app_buffers_head, prev = NULL; iterator->ptr != NULL; iterator = iterator->next)
+ {
+ if(iterator->ptr == data)
+ {
+ assert(iterator->next != NULL);
+ if(prev != NULL)
+ {
+ prev->next = iterator->next;
+ if(prev->next->ptr == NULL)
+ {
+ rpmsg_in_app_buffers_tail = prev->next;
+ }
+ }
+ else
+ {
+ rpmsg_in_app_buffers_head = iterator->next;
+ if(rpmsg_in_app_buffers_head->ptr == NULL)
+ {
+ rpmsg_in_app_buffers_tail = rpmsg_in_app_buffers_head;
+ }
+ }
+
+ env_unlock_mutex(rdev->lock);
+
+ env_free_memory((void*)iterator);
+
+ /* Return used buffer. */
+ rpmsg_release_rx_buffer(ept->rp_chnl, data);
+
+ return RPMSG_SUCCESS;
+ }
+ prev = iterator;
+ }
+
+ env_unlock_mutex(rdev->lock);
+ }
+ return RPMSG_ERR_PARAM;
+#else
+
+ /* Return used buffer. */
+ rpmsg_release_rx_buffer(ept->rp_chnl, data);
+
+ return RPMSG_SUCCESS;
+#endif
+}
+
+/*!
+ * @brief Allocates the tx buffer for message payload.
+ *
+ * This API can only be called at process context to get the tx buffer in vring. By this way, the
+ * application can directly put its message into the vring tx buffer without copy from an application buffer.
+ * It is the application responsibility to correctly fill the allocated tx buffer by data and passing correct
+ * parameters to the rpmsg_rtos_send_nocopy() function to perform data no-copy-send mechanism.
+ *
+ * @param[in] ept Pointer to the RPMsg endpoint that requests tx buffer allocation
+ * @param[out] size Pointer to store tx buffer size
+ *
+ * @return The tx buffer address on success and NULL on failure
+ *
+ * @see rpmsg_rtos_send_nocopy
+ */
+void *rpmsg_rtos_alloc_tx_buffer(struct rpmsg_endpoint *ept, unsigned long *size)
+{
+ if (!size) return NULL;
+ if (!ept) return NULL;
+ if (!ept->rp_chnl) return NULL;
+ if (!ept->rp_chnl->rdev) return NULL;
+
+ return rpmsg_alloc_tx_buffer(ept->rp_chnl, size, RPMSG_TRUE);
+}
+
+/*!
+ * @brief Sends a message across to the remote processor.
+ *
+ * This function sends data of length len to the remote dst address.
+ * In case there are no TX buffers available, the function will block until
+ * one becomes available, or a timeout of 15 seconds elapses. When the latter
+ * happens, -ERESTARTSYS is returned.
+ *
+ * @param[in] ept Pointer to the RPMsg endpoint
+ * @param[in] data Pointer to the application buffer containing data to be sent
+ * @param[in] len Size of the data, in bytes, to transmit
+ * @param[in] dst Destination address of the message
+ *
+ * @return 0 on success and an appropriate error value on failure
+ *
+ * @see rpmsg_rtos_send_nocopy
+ */
+int rpmsg_rtos_send(struct rpmsg_endpoint *ept, void *data, int len, unsigned long dst)
+{
+ if (!data) return RPMSG_ERR_PARAM;
+ if (!ept) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl->rdev) return RPMSG_ERR_PARAM;
+
+ return rpmsg_send_offchannel_raw(ept->rp_chnl, ept->addr, dst, (char *)data, len, RPMSG_TRUE);
+}
+
+/*!
+ * @brief Sends a message in tx buffer allocated by rpmsg_rtos_alloc_tx_buffer()
+ * to the remote processor.
+ *
+ * This function sends txbuf of length len to the remote dst address.
+ * The application has to take the responsibility for:
+ * 1. tx buffer allocation (rpmsg_rtos_alloc_tx_buffer() )
+ * 2. filling the data to be sent into the pre-allocated tx buffer
+ * 3. not exceeding the buffer size when filling the data
+ * 4. data cache coherency
+ *
+ * After the rpmsg_rtos_send_nocopy() function is issued the tx buffer is no more owned
+ * by the sending task and must not be touched anymore unless the rpmsg_rtos_send_nocopy()
+ * function fails and returns an error. In that case the application should try
+ * to re-issue the rpmsg_rtos_send_nocopy() again and if it is still not possible to send
+ * the message and the application wants to give it up from whatever reasons
+ * the rpmsg_rtos_recv_nocopy_free function could be called,
+ * passing the pointer to the tx buffer to be released as a parameter.
+ *
+ * @param[in] ept Pointer to the RPMsg endpoint
+ * @param[in] txbuf Tx buffer with message filled
+ * @param[in] len Size of the data, in bytes, to transmit
+ * @param[in] dst Destination address of the message
+ *
+ * @return 0 on success and an appropriate error value on failure
+ *
+ * @see rpmsg_rtos_alloc_tx_buffer
+ * @see rpmsg_rtos_send
+ */
+int rpmsg_rtos_send_nocopy(struct rpmsg_endpoint *ept, void *txbuf, int len, unsigned long dst)
+{
+ if (!txbuf) return RPMSG_ERR_PARAM;
+ if (!ept) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl) return RPMSG_ERR_PARAM;
+ if (!ept->rp_chnl->rdev) return RPMSG_ERR_PARAM;
+
+ return rpmsg_send_offchannel_nocopy(ept->rp_chnl, ept->addr, dst, txbuf, len);
+}