summaryrefslogtreecommitdiff
path: root/middleware/multicore/open-amp/rpmsg/remote_device.c
diff options
context:
space:
mode:
authorStefan Agner <stefan.agner@toradex.com>2016-01-12 14:06:54 -0800
committerStefan Agner <stefan.agner@toradex.com>2016-01-12 14:06:54 -0800
commita57cc2c988482010061b9e68344fdf1969889763 (patch)
tree5c050337492ce27c09b47421b123980b5a79f8d9 /middleware/multicore/open-amp/rpmsg/remote_device.c
initial commit, FreeRTOS_BSP_1.0.0_iMX7D
Diffstat (limited to 'middleware/multicore/open-amp/rpmsg/remote_device.c')
-rw-r--r--middleware/multicore/open-amp/rpmsg/remote_device.c534
1 files changed, 534 insertions, 0 deletions
diff --git a/middleware/multicore/open-amp/rpmsg/remote_device.c b/middleware/multicore/open-amp/rpmsg/remote_device.c
new file mode 100644
index 0000000..f07faab
--- /dev/null
+++ b/middleware/multicore/open-amp/rpmsg/remote_device.c
@@ -0,0 +1,534 @@
+/*
+ * Copyright (c) 2014, Mentor Graphics Corporation
+ * All rights reserved.
+ * Copyright (c) 2015 Xilinx, 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 Mentor Graphics Corporation 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
+ *
+ * remote_device.c
+ *
+ * COMPONENT
+ *
+ * OpenAMP Stack
+ *
+ * DESCRIPTION
+ *
+ * This file provides services to manage the remote devices.It also implements
+ * the interface defined by the virtio and provides few other utility functions.
+ *
+ *
+ **************************************************************************/
+
+#include "rpmsg.h"
+
+/* Macro to initialize vring HW info */
+#define INIT_VRING_ALLOC_INFO(ring_info,vring_hw) \
+ (ring_info).phy_addr = (vring_hw).phy_addr; \
+ (ring_info).align = (vring_hw).align; \
+ (ring_info).num_descs = (vring_hw).num_descs
+
+/* Local functions */
+static int rpmsg_rdev_init_channels(struct remote_device *rdev);
+
+/* Ops table for virtio device */
+virtio_dispatch rpmsg_rdev_config_ops =
+{
+ rpmsg_rdev_create_virtqueues,
+ rpmsg_rdev_get_status,
+ rpmsg_rdev_set_status,
+ rpmsg_rdev_get_feature,
+ rpmsg_rdev_set_feature,
+ rpmsg_rdev_negotiate_feature,
+ rpmsg_rdev_read_config,
+ rpmsg_rdev_write_config,
+ rpmsg_rdev_reset
+};
+
+/**
+ * rpmsg_rdev_init
+ *
+ * This function creates and initializes the remote device. The remote device
+ * encapsulates virtio device.
+ *
+ * @param rdev - pointer to newly created remote device
+ * @param dev-id - ID of device to create , remote cpu id
+ * @param role - role of the other device, Master or Remote
+ * @param channel_created - callback function for channel creation
+ * @param channel_destroyed - callback function for channel deletion
+ * @param default_cb - default callback for channel
+ *
+ * @return - status of function execution
+ *
+ */
+int rpmsg_rdev_init(struct remote_device **rdev, int dev_id, int role,
+ rpmsg_chnl_cb_t channel_created,
+ rpmsg_chnl_cb_t channel_destroyed,
+ rpmsg_rx_cb_t default_cb) {
+
+ struct remote_device *rdev_loc;
+ struct virtio_device *virt_dev;
+ struct hil_proc *proc;
+ struct proc_shm *shm;
+ int status;
+
+ /* Initialize HIL data structures for given device */
+ proc = hil_create_proc(dev_id);
+
+ if (!proc) {
+ return RPMSG_ERR_DEV_ID;
+ }
+
+ /* Create software representation of remote processor. */
+ rdev_loc = (struct remote_device *) env_allocate_memory(
+ sizeof(struct remote_device));
+
+ if (!rdev_loc) {
+ return RPMSG_ERR_NO_MEM;
+ }
+
+ env_memset(rdev_loc, 0x00, sizeof(struct remote_device));
+ status = env_create_mutex(&rdev_loc->lock, 1);
+
+ if (status != RPMSG_SUCCESS) {
+
+ /* Cleanup required in case of error is performed by caller */
+ return status;
+ }
+
+ rdev_loc->proc = proc;
+ rdev_loc->role = role;
+ rdev_loc->channel_created = channel_created;
+ rdev_loc->channel_destroyed = channel_destroyed;
+ rdev_loc->default_cb = default_cb;
+
+ /* Initialize the virtio device */
+ virt_dev = &rdev_loc->virt_dev;
+ virt_dev->device = proc;
+ virt_dev->func = &rpmsg_rdev_config_ops;
+ if (virt_dev->func->set_features != RPMSG_NULL) {
+ virt_dev->func->set_features(virt_dev, proc->vdev.dfeatures); /* rpmsg_rdev_set_feature */
+ }
+
+ /*
+ * Linux don't use this way to manage memory pool
+ */
+ if (rdev_loc->role == RPMSG_REMOTE) {
+ /*
+ * Since device is RPMSG Remote so we need to manage the
+ * shared buffers. Create shared memory pool to handle buffers.
+ */
+ shm = hil_get_shm_info(proc); /*proc_table*/
+ rdev_loc->mem_pool = sh_mem_create_pool(shm->start_addr, shm->size,
+ RPMSG_BUFFER_SIZE);
+
+ if (!rdev_loc->mem_pool) {
+ return RPMSG_ERR_NO_MEM;
+ }
+ }
+
+ /* Initialize channels for RPMSG Remote */
+ status = rpmsg_rdev_init_channels(rdev_loc);
+
+ if (status != RPMSG_SUCCESS) {
+ return status;
+ }
+
+ *rdev = rdev_loc;
+
+ return RPMSG_SUCCESS;
+}
+
+/**
+ * rpmsg_rdev_deinit
+ *
+ * This function un-initializes the remote device.
+ *
+ * @param rdev - pointer to remote device to deinit.
+ *
+ * @return - none
+ *
+ */
+void rpmsg_rdev_deinit(struct remote_device *rdev) {
+ struct llist *rp_chnl_head, *rp_chnl_temp, *node;
+ struct rpmsg_channel *rp_chnl;
+
+ rp_chnl_head = rdev->rp_channels;
+
+ while (rp_chnl_head != RPMSG_NULL ) {
+
+ rp_chnl_temp = rp_chnl_head->next;
+ rp_chnl = (struct rpmsg_channel *) rp_chnl_head->data;
+
+ if (rdev->channel_destroyed) {
+ rdev->channel_destroyed(rp_chnl);
+ }
+
+ if ((rdev->support_ns) && (rdev->role == RPMSG_MASTER)) {
+ rpmsg_send_ns_message(rdev, rp_chnl, RPMSG_NS_DESTROY);
+ }
+
+ /* Delete default endpoint for channel */
+ if (rp_chnl->rp_ept) {
+ rpmsg_destroy_ept(rp_chnl->rp_ept);
+ }
+
+ _rpmsg_delete_channel(rp_chnl);
+ rp_chnl_head = rp_chnl_temp;
+ }
+
+ /* Delete name service endpoint */
+ node = rpmsg_rdev_get_endpoint_from_addr(rdev,RPMSG_NS_EPT_ADDR);
+ if (node) {
+ _destroy_endpoint(rdev, (struct rpmsg_endpoint *) node->data);
+ }
+
+ if (rdev->rvq) {
+ virtqueue_free(rdev->rvq);
+ }
+ if (rdev->tvq) {
+ virtqueue_free(rdev->tvq);
+ }
+ if (rdev->mem_pool) {
+ sh_mem_delete_pool(rdev->mem_pool);
+ }
+ if (rdev->lock) {
+ env_delete_mutex(rdev->lock);
+ }
+
+ env_free_memory(rdev);
+}
+
+/**
+ * rpmsg_rdev_get_chnl_node_from_id
+ *
+ * This function returns channel node based on channel name.
+ *
+ * @param stack - pointer to remote device
+ * @param rp_chnl_id - rpmsg channel name
+ *
+ * @return - channel node
+ *
+ */
+struct llist *rpmsg_rdev_get_chnl_node_from_id(struct remote_device *rdev,
+ char *rp_chnl_id) {
+ struct rpmsg_channel *rp_chnl;
+ struct llist *rp_chnl_head;
+
+ rp_chnl_head = rdev->rp_channels;
+
+ env_lock_mutex(rdev->lock);
+ while (rp_chnl_head) {
+ rp_chnl = (struct rpmsg_channel *) rp_chnl_head->data;
+ if (env_strncmp(rp_chnl->name, rp_chnl_id, sizeof(rp_chnl->name))
+ == 0) {
+ env_unlock_mutex(rdev->lock);
+ return rp_chnl_head;
+ }
+ rp_chnl_head = rp_chnl_head->next;
+ }
+ env_unlock_mutex(rdev->lock);
+
+ return RPMSG_NULL ;
+}
+
+/**
+ * rpmsg_rdev_get_chnl_from_addr
+ *
+ * This function returns channel node based on src/dst address.
+ *
+ * @param rdev - pointer remote device control block
+ * @param addr - src/dst address
+ *
+ * @return - channel node
+ *
+ */
+struct llist *rpmsg_rdev_get_chnl_from_addr(struct remote_device *rdev,
+ unsigned long addr) {
+ struct rpmsg_channel *rp_chnl;
+ struct llist *rp_chnl_head;
+
+ rp_chnl_head = rdev->rp_channels;
+
+ env_lock_mutex(rdev->lock);
+ while (rp_chnl_head) {
+ rp_chnl = (struct rpmsg_channel *) rp_chnl_head->data;
+ if ((rp_chnl->src == addr) || (rp_chnl->dst == addr)) {
+ env_unlock_mutex(rdev->lock);
+ return rp_chnl_head;
+ }
+ rp_chnl_head = rp_chnl_head->next;
+ }
+ env_unlock_mutex(rdev->lock);
+
+ return RPMSG_NULL ;
+}
+
+/**
+ * rpmsg_rdev_get_endpoint_from_addr
+ *
+ * This function returns endpoint node based on src address.
+ *
+ * @param rdev - pointer remote device control block
+ * @param addr - src address
+ *
+ * @return - endpoint node
+ *
+ */
+struct llist *rpmsg_rdev_get_endpoint_from_addr(struct remote_device *rdev,
+ unsigned long addr) {
+ struct llist *rp_ept_lut_head;
+
+ rp_ept_lut_head = rdev->rp_endpoints;
+
+ env_lock_mutex(rdev->lock);
+ while (rp_ept_lut_head) {
+ struct rpmsg_endpoint *rp_ept =
+ (struct rpmsg_endpoint *) rp_ept_lut_head->data;
+ if (rp_ept->addr == addr) {
+ env_unlock_mutex(rdev->lock);
+ return rp_ept_lut_head;
+ }
+ rp_ept_lut_head = rp_ept_lut_head->next;
+ }
+ env_unlock_mutex(rdev->lock);
+
+ return RPMSG_NULL ;
+}
+/*
+ * rpmsg_rdev_notify
+ *
+ * This function checks whether remote device is up or not. If it is up then
+ * notification is sent based on device role to start IPC.
+ *
+ * @param rdev - pointer to remote device
+ *
+ * @return - status of function execution
+ *
+ */
+int rpmsg_rdev_notify(struct remote_device *rdev) {
+ int status = RPMSG_SUCCESS;
+
+ if (rdev->role == RPMSG_REMOTE) {
+ status = hil_get_status(rdev->proc);
+
+ /*
+ * Let the remote device know that Master is ready for
+ * communication.
+ */
+ if (!status)
+ virtqueue_kick(rdev->rvq); /*will triggle rpmsg_tx_callback in the REMOTE side*/
+
+ } else {
+ status = hil_set_status(rdev->proc);
+ }
+
+ if (status == RPMSG_SUCCESS) {
+ rdev->state = RPMSG_DEV_STATE_ACTIVE;
+ }
+
+ return status;
+}
+/**
+ * rpmsg_rdev_init_channels
+ *
+ * This function is only applicable to RPMSG remote. It obtains channel IDs
+ * from the HIL and creates RPMSG channels corresponding to each ID.
+ *
+ * @param rdev - pointer to remote device
+ *
+ * @return - status of function execution
+ *
+ */
+int rpmsg_rdev_init_channels(struct remote_device *rdev) {
+ struct rpmsg_channel *rp_chnl;
+ struct proc_chnl *chnl_info;
+ int num_chnls, idx;
+
+ if (rdev->role == RPMSG_MASTER) {
+
+ chnl_info = hil_get_chnl_info(rdev->proc, &num_chnls); /*proc_table*/
+ for (idx = 0; idx < num_chnls; idx++) {
+
+ rp_chnl = _rpmsg_create_channel(rdev, chnl_info[idx].name, 0x00,
+ RPMSG_NS_EPT_ADDR); /*the channel is put to "rp_channels" field of remote_device data structure*/
+ if (!rp_chnl) {
+ return RPMSG_ERR_NO_MEM;
+ }
+
+ rp_chnl->rp_ept = rpmsg_create_ept(rp_chnl, rdev->default_cb, rdev,
+ RPMSG_ADDR_ANY); /*the endpoint is put ot "rp_endpoints" field of remote_device data structure*/
+
+ if (!rp_chnl->rp_ept) {
+ return RPMSG_ERR_NO_MEM;
+ }
+
+ rp_chnl->src = rp_chnl->rp_ept->addr; /*channel source is the default endpoint address, destination is NS endpoint address*/
+
+ }
+ }
+
+ return RPMSG_SUCCESS;
+}
+
+/**
+ *------------------------------------------------------------------------
+ * The rest of the file implements the virtio device interface as defined
+ * by the virtio.h file.
+ *------------------------------------------------------------------------
+ */
+int rpmsg_rdev_create_virtqueues(struct virtio_device *dev, int flags, int nvqs, // invoked by virtio_device->create_virtqueues
+ const char *names[], vq_callback *callbacks[],
+ struct virtqueue *vqs_[]) {
+ struct remote_device *rdev;
+ struct vring_alloc_info ring_info;
+ struct virtqueue *vqs[RPMSG_MAX_VQ_PER_RDEV];
+ struct proc_vring *vring_table;
+ void *buffer;
+ struct llist node;
+ int idx, num_vrings, status;
+
+ rdev = (struct remote_device*) dev;
+
+ /* Get the vring HW info for the given virtio device */
+ vring_table = hil_get_vring_info(&rdev->proc->vdev,
+ &num_vrings);
+
+
+ if (num_vrings > nvqs) {
+ return RPMSG_ERR_MAX_VQ;
+ }
+
+ /* Create virtqueue for each vring. */
+ for (idx = 0; idx < num_vrings; idx++) {
+
+ INIT_VRING_ALLOC_INFO( ring_info, vring_table[idx]); /*ring_info comes from proc_table*/
+
+ /*
+ * - phy_addr
+ * - align
+ * - num_descs
+ */
+
+ if (rdev->role == RPMSG_REMOTE) {
+ env_memset((void*) ring_info.phy_addr, 0x00,
+ vring_size(vring_table[idx].num_descs,
+ vring_table[idx].align));
+ }
+
+ /*
+ * created virtqueue should be in vqs[0], vqs[1]
+ */
+ status = virtqueue_create(dev, idx, (char *) names[idx], &ring_info,
+ callbacks[idx], hil_vring_notify,
+ &vqs[idx]);
+
+ if (status != RPMSG_SUCCESS) {
+ return status;
+ }
+ }
+
+ //FIXME - a better way to handle this , tx for master is rx for remote and vice versa.
+ /*
+ * MASTER REMOTE
+ * vqs[0] RX TX
+ * vqs[1] TX RX
+ */
+ if (rdev->role == RPMSG_MASTER) {
+ rdev->tvq = vqs[0];
+ rdev->rvq = vqs[1];
+ } else {
+ rdev->tvq = vqs[1];
+ rdev->rvq = vqs[0];
+ }
+
+ if (rdev->role == RPMSG_REMOTE) {
+ for (idx = 0; ((idx < rdev->rvq->vq_nentries)
+ && (idx < rdev->mem_pool->total_buffs / 2));
+ idx++) {
+
+
+ /* Initialize TX virtqueue buffers for remote device */
+ buffer = sh_mem_get_buffer(rdev->mem_pool);
+
+ if (!buffer) {
+ return RPMSG_ERR_NO_BUFF;
+ }
+
+ node.data = buffer;
+ node.attr = RPMSG_BUFFER_SIZE;
+ node.next = RPMSG_NULL;
+
+ env_memset(buffer, 0x00, RPMSG_BUFFER_SIZE);
+ status = virtqueue_add_buffer(rdev->rvq, &node, 0, 1, buffer); /* this is where vq_ring.avial.idx is updated by "vq_ring_update_avail"*/
+
+ if (status != RPMSG_SUCCESS) {
+ return status;
+ }
+ }
+ }
+
+ return RPMSG_SUCCESS;
+}
+
+unsigned char rpmsg_rdev_get_status(struct virtio_device *dev) {
+ return 0;
+}
+
+void rpmsg_rdev_set_status(struct virtio_device *dev, unsigned char status) {
+
+}
+
+uint32_t rpmsg_rdev_get_feature(struct virtio_device *dev) {
+ return dev->features;
+}
+
+void rpmsg_rdev_set_feature(struct virtio_device *dev, uint32_t feature) {
+ dev->features |= feature; /*refer to name service endpoint creation logic in the caller rpmsg_start_ipc*/
+}
+
+uint32_t rpmsg_rdev_negotiate_feature(struct virtio_device *dev,
+ uint32_t features) {
+ return 0;
+}
+/*
+ * Read/write a variable amount from the device specific (ie, network)
+ * configuration region. This region is encoded in the same endian as
+ * the guest.
+ */
+void rpmsg_rdev_read_config(struct virtio_device *dev, uint32_t offset,
+ void *dst, int length) {
+ return;
+}
+void rpmsg_rdev_write_config(struct virtio_device *dev, uint32_t offset,
+ void *src, int length) {
+ return;
+}
+void rpmsg_rdev_reset(struct virtio_device *dev) {
+ return;
+}