blob: 122b0ccd61389f6fc7d9cf82510881867318dc60 [file] [log] [blame]
/*
* INTEL MID Remote Processor Core driver
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/remoteproc.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/virtio_ring.h>
#include <linux/virtio_ids.h>
#include <linux/platform_data/intel_mid_remoteproc.h>
#include "intel_mid_rproc_core.h"
#include "remoteproc_internal.h"
#define RPMSG_NS_ADDR 53
/**
* rpmsg_ns_alloc() - allocate a name service annoucement structure
* @name: name of remote service
* @id: rproc type
* @addr: address of remote service
*/
struct rpmsg_ns_info *rpmsg_ns_alloc(const char *name, int id, u32 addr)
{
struct rpmsg_ns_info *ns_info;
ns_info = kzalloc(sizeof(struct rpmsg_ns_info), GFP_KERNEL);
if (ns_info) {
strncpy(ns_info->name, name, RPMSG_NAME_SIZE);
ns_info->type = id;
ns_info->addr = addr;
ns_info->flags = RPMSG_NS_CREATE;
}
return ns_info;
};
EXPORT_SYMBOL_GPL(rpmsg_ns_alloc);
/**
* rpmsg_ns_add_to_list() -- add a name service node to the global list
* @info: name service node
*/
void rpmsg_ns_add_to_list(struct rpmsg_ns_info *info,
struct rpmsg_ns_list *nslist)
{
mutex_lock(&nslist->lock);
list_add_tail(&info->node, &nslist->list);
mutex_unlock(&nslist->lock);
}
EXPORT_SYMBOL_GPL(rpmsg_ns_add_to_list);
/**
* free_rpmsg_ns() -- free rpmsg name service node
* @info: name service node
*/
void free_rpmsg_ns(struct rpmsg_ns_info *info)
{
kfree(info);
}
/**
* rpmsg_ns_del_list() -- free rpmsg name service list
*/
void rpmsg_ns_del_list(struct rpmsg_ns_list *nslist)
{
struct rpmsg_ns_info *info, *next;
mutex_lock(&nslist->lock);
list_for_each_entry_safe(info, next, &nslist->list, node) {
list_del(&info->node);
free_rpmsg_ns(info);
}
mutex_unlock(&nslist->lock);
}
EXPORT_SYMBOL_GPL(rpmsg_ns_del_list);
/**
* find_rvdev() - find the rproc state of a supported virtio device
* @rproc: rproc handle
* @id: virtio device id
*/
struct rproc_vdev *find_rvdev(struct rproc *rproc, int id)
{
struct rproc_vdev *rvdev;
list_for_each_entry(rvdev, &rproc->rvdevs, node)
if (rvdev->vdev.id.device == id)
return rvdev;
return NULL;
}
/*
* Since we could not get vring structure directly from rproc_vring
* structure, we have to create two local vrings and identify them
* by matching with rproc_vrings.
* @id: virtio device id.
* Currently one rproc_vdev is supported by firmware, and the id is
* VIRTIO_ID_RPMSG (declared in linux/virtio_ids.h).
*/
int find_vring_index(struct rproc *rproc, int vqid, int id)
{
struct rproc_vdev *rvdev;
struct device *dev = rproc->dev.parent;
int vring_idx = 0;
rvdev = find_rvdev(rproc, id);
if (rvdev == NULL) {
dev_err(dev, "virtio device not found\n");
return -EINVAL;
}
while (vring_idx < RVDEV_NUM_VRINGS) {
if (rvdev->vring[vring_idx].notifyid == vqid)
break;
vring_idx++;
}
/* no match found? there's a problem */
if (vring_idx == RVDEV_NUM_VRINGS) {
dev_err(dev, "Can not find vring\n");
return -EINVAL;
}
return vring_idx;
}
void intel_mid_rproc_vring_init(struct rproc *rproc,
struct vring *vring, enum local_vring_idx id)
{
int align, len;
void *addr;
struct rproc_vdev *rvdev;
struct device *dev = rproc->dev.parent;
rvdev = find_rvdev(rproc, VIRTIO_ID_RPMSG);
if (rvdev == NULL) {
dev_err(dev, "virtio device not found\n");
return;
}
addr = rvdev->vring[id].va;
align = rvdev->vring[id].align;
len = rvdev->vring[id].len;
vring_init(vring, len, addr, align);
}
/**
* intel_mid_rproc_vq_interrupt() - inform a vq interrupt to rproc
* after vq buffers are handled
* @rproc: rproc handle
* @msg: vq notify id
*/
void intel_mid_rproc_vq_interrupt(struct rproc *rproc, int msg)
{
struct device *dev = rproc->dev.parent;
if (rproc_vq_interrupt(rproc, msg) == IRQ_NONE)
dev_err(dev, "no message was found in vqid %d\n", msg);
}
/**
* intel_mid_rproc_msg_handle() - generic interface as a vq buffer handle
* during rpmsg transaction
* @iproc: intel mid rproc data
*/
int intel_mid_rproc_msg_handle(struct intel_mid_rproc *iproc)
{
int ret;
struct vring *r_vring, *s_vring;
void *r_virt_addr, *s_virt_addr;
u16 r_idx, s_idx;
u64 r_dma_addr, s_dma_addr;
u32 r_len, s_len;
r_vring = &iproc->rx_vring;
s_vring = &iproc->tx_vring;
r_idx = iproc->r_vring_last_used & (r_vring->num - 1);
s_idx = iproc->s_vring_last_used & (s_vring->num - 1);
r_dma_addr = r_vring->desc[r_idx].addr;
s_dma_addr = s_vring->desc[s_idx].addr;
r_virt_addr = phys_to_virt(r_dma_addr);
s_virt_addr = phys_to_virt(s_dma_addr);
ret = iproc->rproc_rpmsg_handle(r_virt_addr, s_virt_addr,
&r_len, &s_len);
r_vring->used->ring[r_idx].id = r_idx;
r_vring->used->ring[r_idx].len = r_len;
r_vring->used->idx++;
s_vring->used->ring[s_idx].id = s_idx;
s_vring->used->ring[s_idx].len = s_len;
s_vring->used->idx++;
iproc->r_vring_last_used++;
iproc->s_vring_last_used++;
return ret;
}
/**
* Remoteproc side rx buffer handler during name service creation.
* @iproc: intel mid rproc data
* @ns_info: name service info
*
* After remote processor receives name service messages, it needs to
* update the elements of its virtio device's rx virtqueue buffer
* before next rpmsg transaction.
* Here we have this function simulating the above effect.
*/
int intel_mid_rproc_ns_handle(struct intel_mid_rproc *iproc,
struct rpmsg_ns_info *ns_info)
{
u16 index;
u32 len;
u64 dma_addr;
void *virt_addr;
struct vring *r_vring;
struct rpmsg_hdr *msg;
struct rpmsg_ns_msg *nsm;
if (ns_info == NULL) {
pr_err("ns_info = NULL\n");
return -ENODEV;
}
r_vring = &iproc->rx_vring;
index = iproc->r_vring_last_used & (r_vring->num - 1);
len = sizeof(*msg) + sizeof(*nsm);
dma_addr = r_vring->desc[index].addr;
virt_addr = phys_to_virt(dma_addr);
msg = (struct rpmsg_hdr *)virt_addr;
nsm = (struct rpmsg_ns_msg *)(virt_addr + sizeof(*msg));
nsm->addr = ns_info->addr;
nsm->flags = ns_info->flags;
strncpy(nsm->name, ns_info->name, RPMSG_NAME_SIZE);
msg->len = sizeof(*nsm);
msg->src = nsm->addr;
msg->dst = RPMSG_NS_ADDR;
r_vring->used->ring[index].id = index;
r_vring->used->ring[index].len = len;
r_vring->used->idx++;
iproc->r_vring_last_used++;
return 0;
}