/* * Copyright (c) 2014, Mentor Graphics Corporation * All rights reserved. * Copyright (c) 2015 Xilinx, Inc. All rights reserved. * Copyright (c) 2016 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 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 #include "openamp/rpmsg.h" #include "openamp/remoteproc.h" #include "metal/utilities.h" #include "metal/alloc.h" #include "metal/atomic.h" #include "metal/cpu.h" /* Macro to initialize vring HW info */ #define INIT_VRING_ALLOC_INFO(ring_info,vring_hw) \ (ring_info).vaddr = (vring_hw).vaddr; \ (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 proc - pointer to hil_proc * @param rdev - pointer to newly created remote device * @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 hil_proc *proc, struct remote_device **rdev, 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 proc_shm *shm; int status; if (!proc) return RPMSG_ERR_PARAM; /* Initialize HIL data structures for given device */ if (hil_init_proc(proc)) return RPMSG_ERR_DEV_INIT; /* Create software representation of remote processor. */ rdev_loc = (struct remote_device *)metal_allocate_memory(sizeof(struct remote_device)); if (!rdev_loc) { return RPMSG_ERR_NO_MEM; } memset(rdev_loc, 0x00, sizeof(struct remote_device)); metal_mutex_init(&rdev_loc->lock); 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; /* Restrict the ept address - zero address can't be assigned */ rdev_loc->bitmap[0] = 1; /* 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); } 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); 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; } } if (!rpmsg_rdev_remote_ready(rdev_loc)) return RPMSG_ERR_DEV_INIT; /* Initialize endpoints list */ metal_list_init(&rdev_loc->rp_endpoints); /* 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 metal_list *node; struct rpmsg_channel *rp_chnl; struct rpmsg_endpoint *rp_ept; while(!metal_list_is_empty(&rdev->rp_channels)) { node = rdev->rp_channels.next; rp_chnl = metal_container_of(node, struct rpmsg_channel, node); 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); } /* Delete name service endpoint */ metal_mutex_acquire(&rdev->lock); rp_ept = rpmsg_rdev_get_endpoint_from_addr(rdev, RPMSG_NS_EPT_ADDR); metal_mutex_release(&rdev->lock); if (rp_ept) { _destroy_endpoint(rdev, rp_ept); } metal_mutex_acquire(&rdev->lock); rdev->rvq = 0; rdev->tvq = 0; if (rdev->mem_pool) { sh_mem_delete_pool(rdev->mem_pool); rdev->mem_pool = 0; } metal_mutex_release(&rdev->lock); hil_free_vqs(&rdev->virt_dev); metal_mutex_deinit(&rdev->lock); metal_free_memory(rdev); } /** * rpmsg_rdev_get_chnl_from_id * * This function returns channel node based on channel name. It must be called * with mutex locked. * * @param stack - pointer to remote device * @param rp_chnl_id - rpmsg channel name * * @return - rpmsg channel * */ struct rpmsg_channel *rpmsg_rdev_get_chnl_from_id(struct remote_device *rdev, char *rp_chnl_id) { struct rpmsg_channel *rp_chnl; struct metal_list *node; metal_list_for_each(&rdev->rp_channels, node) { rp_chnl = metal_container_of(node, struct rpmsg_channel, node); if (strncmp (rp_chnl->name, rp_chnl_id, sizeof(rp_chnl->name)) == 0) { return rp_chnl; } } return RPMSG_NULL; } /** * rpmsg_rdev_get_endpoint_from_addr * * This function returns endpoint node based on src address. It must be called * with mutex locked. * * @param rdev - pointer remote device control block * @param addr - src address * * @return - rpmsg endpoint * */ struct rpmsg_endpoint *rpmsg_rdev_get_endpoint_from_addr(struct remote_device *rdev, unsigned long addr) { struct rpmsg_endpoint *rp_ept; struct metal_list *node; metal_list_for_each(&rdev->rp_endpoints, node) { rp_ept = metal_container_of(node, struct rpmsg_endpoint, node); if (rp_ept->addr == addr) { return rp_ept; } } 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) { struct virtio_device *vdev = &rdev->virt_dev; hil_vdev_notify(vdev); return RPMSG_SUCCESS; } /** * 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; metal_list_init(&rdev->rp_channels); if (rdev->role == RPMSG_MASTER) { chnl_info = hil_get_chnl_info(rdev->proc, &num_chnls); for (idx = 0; idx < num_chnls; idx++) { rp_chnl = _rpmsg_create_channel(rdev, chnl_info[idx].name, 0x00, RPMSG_NS_EPT_ADDR); if (!rp_chnl) { return RPMSG_ERR_NO_MEM; } rp_chnl->rp_ept = rpmsg_create_ept(rp_chnl, rdev->default_cb, rdev, RPMSG_ADDR_ANY); if (!rp_chnl->rp_ept) { return RPMSG_ERR_NO_MEM; } rp_chnl->src = rp_chnl->rp_ept->addr; } } return RPMSG_SUCCESS; } /** * check if the remote is ready to start RPMsg communication */ int rpmsg_rdev_remote_ready(struct remote_device *rdev) { struct virtio_device *vdev = &rdev->virt_dev; uint8_t status; if (rdev->role == RPMSG_MASTER) { while (1) { status = vdev->func->get_status(vdev); /* Busy wait until the remote is ready */ if (status & VIRTIO_CONFIG_STATUS_NEEDS_RESET) { rpmsg_rdev_set_status(vdev, 0); hil_vdev_notify(vdev); } else if (status & VIRTIO_CONFIG_STATUS_DRIVER_OK) { return true; } metal_cpu_yield(); } } else { return true; } /* Never come here */ return false; } static void rpmsg_memset_io(struct metal_io_region *io, void *dst, int c, size_t count) { if ((io->mem_flags & METAL_IO_MAPPED)) { metal_memset_io(dst, c, count); } else { memset(dst, c, count); } } /** *------------------------------------------------------------------------ * 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, 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 metal_sg sg; int idx, num_vrings, status; (void)flags; (void)vqs_; 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]); if (rdev->role == RPMSG_REMOTE) { rpmsg_memset_io(vring_table[idx].io, (void *)ring_info.vaddr, 0x00, vring_size(vring_table[idx].num_descs, vring_table[idx].align)); } status = virtqueue_create(dev, idx, (char *)names[idx], &ring_info, callbacks[idx], hil_vring_notify, rdev->proc->sh_buff.io, &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. 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) { sg.io = rdev->proc->sh_buff.io; sg.len = RPMSG_BUFFER_SIZE; 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; } sg.virt = buffer; rpmsg_memset_io(sg.io, buffer, 0x00, RPMSG_BUFFER_SIZE); status = virtqueue_add_buffer(rdev->rvq, &sg, 0, 1, buffer); if (status != RPMSG_SUCCESS) { return status; } } } return RPMSG_SUCCESS; } unsigned char rpmsg_rdev_get_status(struct virtio_device *dev) { struct hil_proc *proc = dev->device; struct proc_vdev *pvdev = &proc->vdev; struct fw_rsc_vdev *vdev_rsc = pvdev->vdev_info; if (!vdev_rsc) return -1; return vdev_rsc->status; } void rpmsg_rdev_set_status(struct virtio_device *dev, unsigned char status) { struct hil_proc *proc = dev->device; struct proc_vdev *pvdev = &proc->vdev; struct fw_rsc_vdev *vdev_rsc = pvdev->vdev_info; if (!vdev_rsc) return; vdev_rsc->status = status; atomic_thread_fence(memory_order_seq_cst); } 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; } uint32_t rpmsg_rdev_negotiate_feature(struct virtio_device *dev, uint32_t features) { (void)dev; (void)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) { (void)dev; (void)offset; (void)dst; (void)length; return; } void rpmsg_rdev_write_config(struct virtio_device *dev, uint32_t offset, void *src, int length) { (void)dev; (void)offset; (void)src; (void)length; return; } void rpmsg_rdev_reset(struct virtio_device *dev) { (void)dev; return; }