blob: 854e844664551a9b6233edea7ad52f5ad48802b7 [file] [log] [blame]
/**
* system/src/rpclnt/rpdev.c
*
* History:
* 2012/08/15 - [Tzu-Jung Lee] created file
*
* Copyright 2008-2015 Ambarella Inc. All Rights Reserved.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* 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/string.h>
#include <linux/kernel.h>
#include <linux/rpmsg.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <mach/hardware.h>
#include <plat/rpdev.h>
#include "rpclnt.h"
#include "vq.h"
static struct rpdev *g_registered_rpdev[64];
static int g_registered_cnt;
DEFINE_SPINLOCK(spinlock_rpdev_tbl);
static struct rpdev *rpdev_lookup(int src, int dst)
{
struct rpdev *rpdev = NULL;
unsigned long flag;
int i;
spin_lock_irqsave(&spinlock_rpdev_tbl, flag);
for (i = 0; i < g_registered_cnt; i++) {
rpdev = g_registered_rpdev[i];
if (rpdev->src == src && rpdev->dst == dst) {
spin_unlock_irqrestore(&spinlock_rpdev_tbl, flag);
return rpdev;
}
}
spin_unlock_irqrestore(&spinlock_rpdev_tbl, flag);
return NULL;
}
void rpdev_rvq_cb(struct vq *vq)
{
struct rpmsg_hdr *hdr;
struct rpdev *rpdev;
int len;
int idx;
while (1) {
idx = vq_get_avail_buf(vq, (void**)&hdr, &len);
if (idx < 0) {
vq_enable_used_notify(vq);
/*
* In case any message slipped in the window, check
* again. Otherwise, it will be delayed until next
* interrupt.
*/
idx = vq_get_avail_buf(vq, (void**)&hdr, &len);
if (idx < 0)
break;
/* Disable the intruupt, and continue to poll */
vq_disable_used_notify(vq);
}
rpdev = rpdev_lookup(hdr->dst, hdr->src);
if (rpdev) {
rpdev->cb(rpdev, &hdr->data, hdr->len, rpdev->priv, hdr->src);
vq_add_used_buf(vq, idx, RPMSG_BUF_SIZE);
continue;
}
rpdev = rpdev_lookup(hdr->dst, RPMSG_NS_ADDR);
if (rpdev) {
if (hdr->len == sizeof(struct rpmsg_ns_msg)) {
rpdev->dst = hdr->src;
complete(&rpdev->comp);
vq_add_used_buf(vq, idx, RPMSG_BUF_SIZE);
}
}
}
vq->kick(vq);
}
void rpdev_svq_cb(struct vq *vq)
{
vq_complete(vq);
vq_enable_used_notify(vq);
}
int rpdev_send_offchannel(struct rpdev *rpdev, u32 src, u32 dst, void *data, int len)
{
int idx = 0;
int buf_len = 0;
struct rpmsg_hdr *hdr = NULL;
struct vq *vq;
vq = rpdev->rpclnt->svq;
idx = vq_get_avail_buf(vq, (void**)&hdr, &buf_len);
if (idx < 0)
return -1;
hdr->src = src;
hdr->dst = dst;
hdr->reserved = 0;
hdr->len = len;
hdr->flags = 0;
memcpy(hdr->data, data, len);
vq_add_used_buf(vq, idx, buf_len);
vq->kick(vq);
return 0;
}
int rpdev_send(struct rpdev *rpdev, void *data, int len)
{
struct vq *vq;
while (rpdev_send_offchannel(rpdev, rpdev->src, rpdev->dst, data, len)) {
vq = rpdev->rpclnt->svq;
vq_enable_used_notify(vq);
vq_wait_for_completion(vq);
vq_disable_used_notify(vq);
}
return 0;
}
int rpdev_trysend(struct rpdev *rpdev, void *data, int len)
{
return rpdev_send_offchannel(rpdev, rpdev->src, rpdev->dst,
data, len);
}
int rpdev_register(struct rpdev *rpdev, const char *bus_name)
{
struct rpmsg_ns_msg nsm;
struct rpclnt *rpclnt;
unsigned long flag;
rpclnt = rpclnt_sync(bus_name);
rpdev->rpclnt = rpclnt;
memcpy(&nsm.name, rpdev->name, RPMSG_NAME_LEN);
nsm.addr = rpdev->src;
nsm.flags = rpdev->flags;
rpdev_send_offchannel(rpdev, rpdev->src, RPMSG_NS_ADDR,
&nsm, sizeof(nsm));
wait_for_completion(&rpdev->comp);
/*
* Supress the tx-complete interrupt by default.
*
* This interuupt will be re-enabled when necessary.
* When all available tx buffers are used up, users may
* be blocked if they were calling rpdev_send() API.
*
* In this case, we'd like to recieve interrupt when any
* tx-buffer is available so we can waken up the blocked
* users.
*/
vq_disable_used_notify(rpclnt->svq);
return 0;
}
struct rpdev *rpdev_alloc(const char *name, int flags, rpdev_cb cb, void *priv)
{
struct rpdev *rpdev;
unsigned long flag;
rpdev = kmalloc(sizeof(*rpdev), GFP_KERNEL);
strcpy(rpdev->name, name);
spin_lock_irqsave(&spinlock_rpdev_tbl, flag);
g_registered_rpdev[g_registered_cnt] = rpdev;
rpdev->src = RPMSG_RESERVED_ADDRESSES + g_registered_cnt++;
spin_unlock_irqrestore(&spinlock_rpdev_tbl, flag);
rpdev->dst = RPMSG_NS_ADDR;
rpdev->flags = flags;
rpdev->cb = cb;
rpdev->priv = priv;
init_completion(&rpdev->comp);
return rpdev;
}