media: video: tegra: Add Tegra RPC support for tegra multimedia framework

Change-Id: I9233c5d7c678f6a9ba1c23af686137bf4d6a4291
Signed-off-by: Dima Zavin <dima@android.com>
This commit is contained in:
Dima Zavin
2010-11-01 14:20:17 -07:00
parent 23e9aed0c0
commit 44729054a5
11 changed files with 1499 additions and 0 deletions

View File

@@ -6,3 +6,6 @@ config TEGRA_CAMERA
Enables support for the Tegra camera interface
If unsure, say Y
source "drivers/media/video/tegra/avp/Kconfig"

View File

@@ -1 +1,2 @@
obj-$(CONFIG_TEGRA_CAMERA) += tegra_camera.o
obj-y += avp/

View File

@@ -0,0 +1,15 @@
config TEGRA_RPC
bool "Enable support for Tegra RPC"
depends on ARCH_TEGRA
default y
help
Enables support for the RPC mechanism necessary for the Tegra
multimedia framework. It is both used to communicate locally on the
CPU between multiple multimedia components as well as to communicate
with the AVP for offloading media decode.
Exports the local tegra RPC interface on device node
/dev/tegra_rpc. Also provides tegra fd based semaphores needed by
the tegra multimedia framework.
If unsure, say Y

View File

@@ -0,0 +1,3 @@
obj-$(CONFIG_TEGRA_RPC) += tegra_rpc.o
obj-$(CONFIG_TEGRA_RPC) += trpc_local.o
obj-$(CONFIG_TEGRA_RPC) += trpc_sema.o

View File

@@ -0,0 +1,738 @@
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Dima Zavin <dima@android.com>
*
* Based on original NVRM code from NVIDIA, and a partial rewrite by:
* Gary King <gking@nvidia.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/delay.h>
#include <linux/err.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/rbtree.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/tegra_rpc.h>
#include <linux/types.h>
#include <linux/wait.h>
#include "trpc.h"
struct trpc_port;
struct trpc_endpoint {
struct list_head msg_list;
wait_queue_head_t msg_waitq;
struct trpc_endpoint *out;
struct trpc_port *port;
struct trpc_node *owner;
struct completion *connect_done;
bool ready;
struct trpc_ep_ops *ops;
void *priv;
};
struct trpc_port {
char name[TEGRA_RPC_MAX_NAME_LEN];
/* protects peer and closed state */
spinlock_t lock;
struct trpc_endpoint peers[2];
bool closed;
/* private */
struct kref ref;
struct rb_node rb_node;
};
enum {
TRPC_TRACE_MSG = 1U << 0,
TRPC_TRACE_CONN = 1U << 1,
TRPC_TRACE_PORT = 1U << 2,
};
static u32 trpc_debug_mask = TRPC_TRACE_CONN | TRPC_TRACE_PORT;
module_param_named(debug_mask, trpc_debug_mask, uint, S_IWUSR | S_IRUGO);
#define DBG(flag, args...) \
do { if (trpc_debug_mask & (flag)) pr_info(args); } while (0)
struct tegra_rpc_info {
struct kmem_cache *msg_cache;
spinlock_t ports_lock;
struct rb_root ports;
struct list_head node_list;
struct mutex node_lock;
};
struct trpc_msg {
struct list_head list;
size_t len;
u8 payload[TEGRA_RPC_MAX_MSG_LEN];
};
static struct tegra_rpc_info *tegra_rpc;
static struct trpc_msg *dequeue_msg_locked(struct trpc_endpoint *ep);
/* a few accessors for the outside world to keep the trpc_endpoint struct
* definition private to this module */
void *trpc_priv(struct trpc_endpoint *ep)
{
return ep->priv;
}
struct trpc_endpoint *trpc_peer(struct trpc_endpoint *ep)
{
return ep->out;
}
const char *trpc_name(struct trpc_endpoint *ep)
{
return ep->port->name;
}
static inline bool is_connected(struct trpc_port *port)
{
return port->peers[0].ready && port->peers[1].ready;
}
static inline bool is_closed(struct trpc_port *port)
{
return port->closed;
}
static void rpc_port_free(struct tegra_rpc_info *info, struct trpc_port *port)
{
struct trpc_msg *msg;
int i;
for (i = 0; i < 2; ++i) {
struct list_head *list = &port->peers[i].msg_list;
while (!list_empty(list)) {
msg = list_first_entry(list, struct trpc_msg, list);
list_del(&msg->list);
kmem_cache_free(info->msg_cache, msg);
}
}
kfree(port);
}
static void _rpc_port_release(struct kref *kref)
{
struct tegra_rpc_info *info = tegra_rpc;
struct trpc_port *port = container_of(kref, struct trpc_port, ref);
unsigned long flags;
DBG(TRPC_TRACE_PORT, "%s: releasing port '%s' (%p)\n", __func__,
port->name, port);
spin_lock_irqsave(&info->ports_lock, flags);
rb_erase(&port->rb_node, &info->ports);
spin_unlock_irqrestore(&info->ports_lock, flags);
rpc_port_free(info, port);
}
/* note that the refcount is actually on the port and not on the endpoint */
void trpc_put(struct trpc_endpoint *ep)
{
kref_put(&ep->port->ref, _rpc_port_release);
}
void trpc_get(struct trpc_endpoint *ep)
{
kref_get(&ep->port->ref);
}
/* Searches the rb_tree for a port with the provided name. If one is not found,
* the new port in inserted. Otherwise, the existing port is returned.
* Must be called with the ports_lock held */
static struct trpc_port *rpc_port_find_insert(struct tegra_rpc_info *info,
struct trpc_port *port)
{
struct rb_node **p;
struct rb_node *parent;
struct trpc_port *tmp;
int ret = 0;
p = &info->ports.rb_node;
parent = NULL;
while (*p) {
parent = *p;
tmp = rb_entry(parent, struct trpc_port, rb_node);
ret = strncmp(port->name, tmp->name, TEGRA_RPC_MAX_NAME_LEN);
if (ret < 0)
p = &(*p)->rb_left;
else if (ret > 0)
p = &(*p)->rb_right;
else
return tmp;
}
rb_link_node(&port->rb_node, parent, p);
rb_insert_color(&port->rb_node, &info->ports);
DBG(TRPC_TRACE_PORT, "%s: inserted port '%s' (%p)\n", __func__,
port->name, port);
return port;
}
static int nodes_try_connect(struct tegra_rpc_info *info,
struct trpc_node *src,
struct trpc_endpoint *from)
{
struct trpc_node *node;
int ret;
mutex_lock(&info->node_lock);
list_for_each_entry(node, &info->node_list, list) {
if (!node->try_connect)
continue;
ret = node->try_connect(node, src, from);
if (!ret) {
mutex_unlock(&info->node_lock);
return 0;
}
}
mutex_unlock(&info->node_lock);
return -ECONNREFUSED;
}
static struct trpc_port *rpc_port_alloc(const char *name)
{
struct trpc_port *port;
int i;
port = kzalloc(sizeof(struct trpc_port), GFP_KERNEL);
if (!port) {
pr_err("%s: can't alloc rpc_port\n", __func__);
return NULL;
}
BUILD_BUG_ON(2 != ARRAY_SIZE(port->peers));
spin_lock_init(&port->lock);
kref_init(&port->ref);
strlcpy(port->name, name, TEGRA_RPC_MAX_NAME_LEN);
for (i = 0; i < 2; i++) {
struct trpc_endpoint *ep = port->peers + i;
INIT_LIST_HEAD(&ep->msg_list);
init_waitqueue_head(&ep->msg_waitq);
ep->port = port;
}
port->peers[0].out = &port->peers[1];
port->peers[1].out = &port->peers[0];
return port;
}
/* must be holding the ports lock */
static inline void handle_port_connected(struct trpc_port *port)
{
int i;
DBG(TRPC_TRACE_CONN, "tegra_rpc: port '%s' connected\n", port->name);
for (i = 0; i < 2; i++)
if (port->peers[i].connect_done)
complete(port->peers[i].connect_done);
}
static inline void _ready_ep(struct trpc_endpoint *ep,
struct trpc_node *owner,
struct trpc_ep_ops *ops,
void *priv)
{
ep->ready = true;
ep->owner = owner;
ep->ops = ops;
ep->priv = priv;
}
/* this keeps a reference on the port */
static struct trpc_endpoint *_create_peer(struct tegra_rpc_info *info,
struct trpc_node *owner,
struct trpc_endpoint *ep,
struct trpc_ep_ops *ops,
void *priv)
{
struct trpc_port *port = ep->port;
struct trpc_endpoint *peer = ep->out;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
BUG_ON(port->closed);
if (peer->ready || !ep->ready) {
peer = NULL;
goto out;
}
_ready_ep(peer, owner, ops, priv);
if (WARN_ON(!is_connected(port)))
pr_warning("%s: created peer but no connection established?!\n",
__func__);
else
handle_port_connected(port);
trpc_get(peer);
out:
spin_unlock_irqrestore(&port->lock, flags);
return peer;
}
/* Exported code. This is out interface to the outside world */
struct trpc_endpoint *trpc_create(struct trpc_node *owner, const char *name,
struct trpc_ep_ops *ops, void *priv)
{
struct tegra_rpc_info *info = tegra_rpc;
struct trpc_endpoint *ep;
struct trpc_port *new_port;
struct trpc_port *port;
unsigned long flags;
BUG_ON(!owner);
/* we always allocate a new port even if one already might exist. This
* is slightly inefficient, but it allows us to do the allocation
* without holding our ports_lock spinlock. */
new_port = rpc_port_alloc(name);
if (!new_port) {
pr_err("%s: can't allocate memory for '%s'\n", __func__, name);
return ERR_PTR(-ENOMEM);
}
spin_lock_irqsave(&info->ports_lock, flags);
port = rpc_port_find_insert(info, new_port);
if (port != new_port) {
rpc_port_free(info, new_port);
/* There was already a port by that name in the rb_tree,
* so just try to create its peer[1], i.e. peer for peer[0]
*/
ep = _create_peer(info, owner, &port->peers[0], ops, priv);
if (!ep) {
pr_err("%s: port '%s' is not in a connectable state\n",
__func__, port->name);
ep = ERR_PTR(-EINVAL);
}
goto out;
}
/* don't need to grab the individual port lock here since we must be
* holding the ports_lock to add the new element, and never dropped
* it, and thus noone could have gotten a reference to this port
* and thus the state couldn't have been touched */
ep = &port->peers[0];
_ready_ep(ep, owner, ops, priv);
out:
spin_unlock_irqrestore(&info->ports_lock, flags);
return ep;
}
struct trpc_endpoint *trpc_create_peer(struct trpc_node *owner,
struct trpc_endpoint *ep,
struct trpc_ep_ops *ops,
void *priv)
{
struct tegra_rpc_info *info = tegra_rpc;
struct trpc_endpoint *peer;
unsigned long flags;
BUG_ON(!owner);
spin_lock_irqsave(&info->ports_lock, flags);
peer = _create_peer(info, owner, ep, ops, priv);
spin_unlock_irqrestore(&info->ports_lock, flags);
return peer;
}
/* timeout == -1, waits forever
* timeout == 0, return immediately
*/
int trpc_connect(struct trpc_endpoint *from, long timeout)
{
struct tegra_rpc_info *info = tegra_rpc;
struct trpc_port *port = from->port;
struct trpc_node *src = from->owner;
int ret;
bool no_retry = !timeout;
unsigned long endtime = jiffies + msecs_to_jiffies(timeout);
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
/* XXX: add state for connections and ports to prevent invalid
* states like multiple connections, etc. ? */
if (unlikely(is_closed(port))) {
ret = -ECONNRESET;
pr_err("%s: can't connect to %s, closed\n", __func__,
port->name);
goto out;
} else if (is_connected(port)) {
ret = 0;
goto out;
}
spin_unlock_irqrestore(&port->lock, flags);
do {
ret = nodes_try_connect(info, src, from);
spin_lock_irqsave(&port->lock, flags);
if (is_connected(port)) {
ret = 0;
goto out;
} else if (no_retry) {
goto out;
} else if (signal_pending(current)) {
ret = -EINTR;
goto out;
}
spin_unlock_irqrestore(&port->lock, flags);
usleep_range(5000, 20000);
} while (timeout < 0 || time_before(jiffies, endtime));
return -ETIMEDOUT;
out:
spin_unlock_irqrestore(&port->lock, flags);
return ret;
}
/* convenience function for doing this common pattern in a single call */
struct trpc_endpoint *trpc_create_connect(struct trpc_node *src,
char *name,
struct trpc_ep_ops *ops,
void *priv,
long timeout)
{
struct trpc_endpoint *ep;
int ret;
ep = trpc_create(src, name, ops, priv);
if (IS_ERR(ep))
return ep;
ret = trpc_connect(ep, timeout);
if (ret) {
trpc_close(ep);
return ERR_PTR(ret);
}
return ep;
}
void trpc_close(struct trpc_endpoint *ep)
{
struct trpc_port *port = ep->port;
struct trpc_endpoint *peer = ep->out;
bool need_close_op = false;
unsigned long flags;
spin_lock_irqsave(&port->lock, flags);
BUG_ON(!ep->ready);
ep->ready = false;
port->closed = true;
if (peer->ready) {
need_close_op = true;
/* the peer may be waiting for a message */
wake_up_all(&peer->msg_waitq);
if (peer->connect_done)
complete(peer->connect_done);
}
spin_unlock_irqrestore(&port->lock, flags);
if (need_close_op && peer->ops && peer->ops->close)
peer->ops->close(peer);
trpc_put(ep);
}
int trpc_wait_peer(struct trpc_endpoint *ep, long timeout)
{
struct trpc_port *port = ep->port;
DECLARE_COMPLETION_ONSTACK(event);
int ret;
unsigned long flags;
if (timeout < 0)
timeout = MAX_SCHEDULE_TIMEOUT;
else if (timeout > 0)
timeout = msecs_to_jiffies(timeout);
spin_lock_irqsave(&port->lock, flags);
if (ep->connect_done) {
ret = -EBUSY;
goto done;
} else if (is_connected(port)) {
ret = 0;
goto done;
} else if (is_closed(port)) {
ret = -ECONNRESET;
goto done;
} else if (!timeout) {
ret = -EAGAIN;
goto done;
}
ep->connect_done = &event;
spin_unlock_irqrestore(&port->lock, flags);
ret = wait_for_completion_interruptible_timeout(&event, timeout);
spin_lock_irqsave(&port->lock, flags);
ep->connect_done = NULL;
if (is_connected(port)) {
ret = 0;
} else {
if (is_closed(port))
ret = -ECONNRESET;
else if (ret == -ERESTARTSYS)
ret = -EINTR;
else if (!ret)
ret = -ETIMEDOUT;
}
done:
spin_unlock_irqrestore(&port->lock, flags);
return ret;
}
static inline int _ep_id(struct trpc_endpoint *ep)
{
return ep - ep->port->peers;
}
static int queue_msg(struct trpc_node *src, struct trpc_endpoint *from,
void *buf, size_t len, gfp_t gfp_flags)
{
struct tegra_rpc_info *info = tegra_rpc;
struct trpc_endpoint *peer = from->out;
struct trpc_port *port = from->port;
struct trpc_msg *msg;
unsigned long flags;
int ret;
BUG_ON(len > TEGRA_RPC_MAX_MSG_LEN);
/* shouldn't be enqueueing to the endpoint */
BUG_ON(peer->ops && peer->ops->send);
DBG(TRPC_TRACE_MSG, "%s: queueing message for %s.%d\n", __func__,
port->name, _ep_id(peer));
msg = kmem_cache_alloc(info->msg_cache, gfp_flags);
if (!msg) {
pr_err("%s: can't alloc memory for msg\n", __func__);
return -ENOMEM;
}
memcpy(msg->payload, buf, len);
msg->len = len;
spin_lock_irqsave(&port->lock, flags);
if (is_closed(port)) {
pr_err("%s: cannot send message for closed port %s.%d\n",
__func__, port->name, _ep_id(peer));
ret = -ECONNRESET;
goto err;
} else if (!is_connected(port)) {
pr_err("%s: cannot send message for unconnected port %s.%d\n",
__func__, port->name, _ep_id(peer));
ret = -ENOTCONN;
goto err;
}
list_add_tail(&msg->list, &peer->msg_list);
if (peer->ops && peer->ops->notify_recv)
peer->ops->notify_recv(peer);
wake_up_all(&peer->msg_waitq);
spin_unlock_irqrestore(&port->lock, flags);
return 0;
err:
spin_unlock_irqrestore(&port->lock, flags);
kmem_cache_free(info->msg_cache, msg);
return ret;
}
/* Returns -ENOMEM if failed to allocate memory for the message. */
int trpc_send_msg(struct trpc_node *src, struct trpc_endpoint *from,
void *buf, size_t len, gfp_t gfp_flags)
{
struct trpc_endpoint *peer = from->out;
struct trpc_port *port = from->port;
BUG_ON(len > TEGRA_RPC_MAX_MSG_LEN);
DBG(TRPC_TRACE_MSG, "%s: sending message from %s.%d to %s.%d\n",
__func__, port->name, _ep_id(from), port->name, _ep_id(peer));
if (peer->ops && peer->ops->send) {
might_sleep();
return peer->ops->send(peer, buf, len);
} else {
might_sleep_if(gfp_flags & __GFP_WAIT);
return queue_msg(src, from, buf, len, gfp_flags);
}
}
static inline struct trpc_msg *dequeue_msg_locked(struct trpc_endpoint *ep)
{
struct trpc_msg *msg = NULL;
if (!list_empty(&ep->msg_list)) {
msg = list_first_entry(&ep->msg_list, struct trpc_msg, list);
list_del_init(&msg->list);
}
return msg;
}
static bool __should_wake(struct trpc_endpoint *ep)
{
struct trpc_port *port = ep->port;
unsigned long flags;
bool ret;
spin_lock_irqsave(&port->lock, flags);
ret = !list_empty(&ep->msg_list) || is_closed(port);
spin_unlock_irqrestore(&port->lock, flags);
return ret;
}
int trpc_recv_msg(struct trpc_node *src, struct trpc_endpoint *ep,
void *buf, size_t buf_len, long timeout)
{
struct tegra_rpc_info *info = tegra_rpc;
struct trpc_port *port = ep->port;
struct trpc_msg *msg;
size_t len;
long ret;
unsigned long flags;
BUG_ON(buf_len > TEGRA_RPC_MAX_MSG_LEN);
spin_lock_irqsave(&port->lock, flags);
/* we allow closed ports to finish receiving already-queued messages */
msg = dequeue_msg_locked(ep);
if (msg) {
goto got_msg;
} else if (is_closed(port)) {
ret = -ECONNRESET;
goto out;
} else if (!is_connected(port)) {
ret = -ENOTCONN;
goto out;
}
if (timeout == 0) {
ret = 0;
goto out;
} else if (timeout < 0) {
timeout = MAX_SCHEDULE_TIMEOUT;
} else {
timeout = msecs_to_jiffies(timeout);
}
spin_unlock_irqrestore(&port->lock, flags);
DBG(TRPC_TRACE_MSG, "%s: waiting for message for %s.%d\n", __func__,
port->name, _ep_id(ep));
ret = wait_event_interruptible_timeout(ep->msg_waitq, __should_wake(ep),
timeout);
DBG(TRPC_TRACE_MSG, "%s: woke up for %s\n", __func__, port->name);
spin_lock_irqsave(&port->lock, flags);
msg = dequeue_msg_locked(ep);
if (!msg) {
if (is_closed(port))
ret = -ECONNRESET;
else if (!ret)
ret = -ETIMEDOUT;
else if (ret == -ERESTARTSYS)
ret = -EINTR;
else
pr_err("%s: error (%d) while receiving msg for '%s'\n",
__func__, (int)ret, port->name);
goto out;
}
got_msg:
spin_unlock_irqrestore(&port->lock, flags);
len = min(buf_len, msg->len);
memcpy(buf, msg->payload, len);
kmem_cache_free(info->msg_cache, msg);
return len;
out:
spin_unlock_irqrestore(&port->lock, flags);
return ret;
}
int trpc_node_register(struct trpc_node *node)
{
struct tegra_rpc_info *info = tegra_rpc;
if (!info)
return -ENOMEM;
pr_info("%s: Adding '%s' to node list\n", __func__, node->name);
mutex_lock(&info->node_lock);
if (node->type == TRPC_NODE_LOCAL)
list_add(&node->list, &info->node_list);
else
list_add_tail(&node->list, &info->node_list);
mutex_unlock(&info->node_lock);
return 0;
}
void trpc_node_unregister(struct trpc_node *node)
{
struct tegra_rpc_info *info = tegra_rpc;
mutex_lock(&info->node_lock);
list_del(&node->list);
mutex_unlock(&info->node_lock);
}
static int __init tegra_rpc_init(void)
{
struct tegra_rpc_info *rpc_info;
int ret;
rpc_info = kzalloc(sizeof(struct tegra_rpc_info), GFP_KERNEL);
if (!rpc_info) {
pr_err("%s: error allocating rpc_info\n", __func__);
return -ENOMEM;
}
rpc_info->ports = RB_ROOT;
spin_lock_init(&rpc_info->ports_lock);
INIT_LIST_HEAD(&rpc_info->node_list);
mutex_init(&rpc_info->node_lock);
rpc_info->msg_cache = KMEM_CACHE(trpc_msg, 0);
if (!rpc_info->msg_cache) {
pr_err("%s: unable to create message cache\n", __func__);
ret = -ENOMEM;
goto err_kmem_cache;
}
tegra_rpc = rpc_info;
return 0;
err_kmem_cache:
kfree(rpc_info);
return ret;
}
subsys_initcall(tegra_rpc_init);

View File

@@ -0,0 +1,77 @@
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Dima Zavin <dima@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef __ARM_MACH_TEGRA_RPC_H
#define __ARM_MACH_TEGRA_RPC_H
#include <linux/list.h>
#include <linux/tegra_rpc.h>
struct trpc_endpoint;
struct trpc_ep_ops {
/* send is allowed to sleep */
int (*send)(struct trpc_endpoint *ep, void *buf, size_t len);
/* notify_recv is NOT allowed to sleep */
void (*notify_recv)(struct trpc_endpoint *ep);
/* close is allowed to sleep */
void (*close)(struct trpc_endpoint *ep);
};
enum {
TRPC_NODE_LOCAL,
TRPC_NODE_REMOTE,
};
struct trpc_node {
struct list_head list;
const char *name;
int type;
void *priv;
int (*try_connect)(struct trpc_node *node,
struct trpc_node *src,
struct trpc_endpoint *from);
};
struct trpc_endpoint *trpc_peer(struct trpc_endpoint *ep);
void *trpc_priv(struct trpc_endpoint *ep);
const char *trpc_name(struct trpc_endpoint *ep);
void trpc_put(struct trpc_endpoint *ep);
void trpc_get(struct trpc_endpoint *ep);
int trpc_send_msg(struct trpc_node *src, struct trpc_endpoint *ep, void *buf,
size_t len, gfp_t gfp_flags);
int trpc_recv_msg(struct trpc_node *src, struct trpc_endpoint *ep,
void *buf, size_t len, long timeout);
struct trpc_endpoint *trpc_create(struct trpc_node *owner, const char *name,
struct trpc_ep_ops *ops, void *priv);
struct trpc_endpoint *trpc_create_connect(struct trpc_node *src, char *name,
struct trpc_ep_ops *ops, void *priv,
long timeout);
int trpc_connect(struct trpc_endpoint *from, long timeout);
struct trpc_endpoint *trpc_create_peer(struct trpc_node *owner,
struct trpc_endpoint *ep,
struct trpc_ep_ops *ops,
void *priv);
void trpc_close(struct trpc_endpoint *ep);
int trpc_wait_peer(struct trpc_endpoint *ep, long timeout);
int trpc_node_register(struct trpc_node *node);
void trpc_node_unregister(struct trpc_node *node);
#endif

View File

@@ -0,0 +1,333 @@
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Dima Zavin <dima@android.com>
*
* Based on original NVRM code from NVIDIA, and a partial rewrite by
* Gary King <gking@nvidia.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/err.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/tegra_rpc.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include "trpc.h"
#include "trpc_sema.h"
struct rpc_info {
struct trpc_endpoint *rpc_ep;
struct file *sema_file;
};
/* ports names reserved for system functions, i.e. communicating with the
* AVP */
static const char reserved_ports[][TEGRA_RPC_MAX_NAME_LEN] = {
"RPC_AVP_PORT",
"RPC_CPU_PORT",
};
static int num_reserved_ports = ARRAY_SIZE(reserved_ports);
static void rpc_notify_recv(struct trpc_endpoint *ep);
/* TODO: do we need to do anything when port is closed from the other side? */
static struct trpc_ep_ops ep_ops = {
.notify_recv = rpc_notify_recv,
};
static struct trpc_node rpc_node = {
.name = "local",
.type = TRPC_NODE_LOCAL,
};
static void rpc_notify_recv(struct trpc_endpoint *ep)
{
struct rpc_info *info = trpc_priv(ep);
if (WARN_ON(!info))
return;
if (info->sema_file)
trpc_sema_signal(info->sema_file);
}
static int local_rpc_open(struct inode *inode, struct file *file)
{
struct rpc_info *info;
info = kzalloc(sizeof(struct rpc_info), GFP_KERNEL);
if (!info)
return -ENOMEM;
nonseekable_open(inode, file);
file->private_data = info;
return 0;
}
static int local_rpc_release(struct inode *inode, struct file *file)
{
struct rpc_info *info = file->private_data;
if (info->rpc_ep)
trpc_close(info->rpc_ep);
if (info->sema_file)
fput(info->sema_file);
kfree(info);
file->private_data = NULL;
return 0;
}
static int __get_port_desc(struct tegra_rpc_port_desc *desc,
unsigned int cmd, unsigned long arg)
{
unsigned int size = _IOC_SIZE(cmd);
if (size != sizeof(struct tegra_rpc_port_desc))
return -EINVAL;
if (copy_from_user(desc, (void __user *)arg, sizeof(*desc)))
return -EFAULT;
desc->name[TEGRA_RPC_MAX_NAME_LEN - 1] = '\0';
return 0;
}
static char uniq_name[] = "aaaaaaaa+";
static const int uniq_len = sizeof(uniq_name) - 1;
static DEFINE_MUTEX(uniq_lock);
static void _gen_port_name(char *new_name)
{
int i;
mutex_lock(&uniq_lock);
for (i = 0; i < uniq_len - 1; i++) {
++uniq_name[i];
if (uniq_name[i] != 'z')
break;
uniq_name[i] = 'a';
}
strlcpy(new_name, uniq_name, TEGRA_RPC_MAX_NAME_LEN);
mutex_unlock(&uniq_lock);
}
static int _validate_port_name(const char *name)
{
int i;
for (i = 0; i < num_reserved_ports; i++)
if (!strncmp(name, reserved_ports[i], TEGRA_RPC_MAX_NAME_LEN))
return -EINVAL;
return 0;
}
static long local_rpc_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct rpc_info *info = file->private_data;
struct tegra_rpc_port_desc desc;
struct trpc_endpoint *ep;
int ret = 0;
if (_IOC_TYPE(cmd) != TEGRA_RPC_IOCTL_MAGIC ||
_IOC_NR(cmd) < TEGRA_RPC_IOCTL_MIN_NR ||
_IOC_NR(cmd) > TEGRA_RPC_IOCTL_MAX_NR) {
ret = -ENOTTY;
goto err;
}
switch (cmd) {
case TEGRA_RPC_IOCTL_PORT_CREATE:
if (info->rpc_ep) {
ret = -EINVAL;
goto err;
}
ret = __get_port_desc(&desc, cmd, arg);
if (ret)
goto err;
if (desc.name[0]) {
ret = _validate_port_name(desc.name);
if (ret)
goto err;
} else {
_gen_port_name(desc.name);
}
if (desc.notify_fd != -1) {
/* grab a reference to the trpc_sema fd */
info->sema_file = trpc_sema_get_from_fd(desc.notify_fd);
if (IS_ERR(info->sema_file)) {
ret = PTR_ERR(info->sema_file);
info->sema_file = NULL;
goto err;
}
}
ep = trpc_create(&rpc_node, desc.name, &ep_ops, info);
if (IS_ERR(ep)) {
ret = PTR_ERR(ep);
if (info->sema_file)
fput(info->sema_file);
info->sema_file = NULL;
goto err;
}
info->rpc_ep = ep;
break;
case TEGRA_RPC_IOCTL_PORT_GET_NAME:
if (!info->rpc_ep) {
ret = -EINVAL;
goto err;
}
if (copy_to_user((void __user *)arg,
trpc_name(info->rpc_ep),
TEGRA_RPC_MAX_NAME_LEN)) {
ret = -EFAULT;
goto err;
}
break;
case TEGRA_RPC_IOCTL_PORT_CONNECT:
if (!info->rpc_ep) {
ret = -EINVAL;
goto err;
}
ret = trpc_connect(info->rpc_ep, (long)arg);
if (ret) {
pr_err("%s: can't connect to '%s' (%d)\n", __func__,
trpc_name(info->rpc_ep), ret);
goto err;
}
break;
case TEGRA_RPC_IOCTL_PORT_LISTEN:
if (!info->rpc_ep) {
ret = -EINVAL;
goto err;
}
ret = trpc_wait_peer(info->rpc_ep, (long)arg);
if (ret) {
pr_err("%s: error waiting for peer for '%s' (%d)\n",
__func__, trpc_name(info->rpc_ep), ret);
goto err;
}
break;
default:
pr_err("%s: unknown cmd %d\n", __func__, _IOC_NR(cmd));
ret = -EINVAL;
goto err;
}
return 0;
err:
if (ret && ret != -ERESTARTSYS)
pr_err("tegra_rpc: pid=%d ioctl=%x/%lx (%x) ret=%d\n",
current->pid, cmd, arg, _IOC_NR(cmd), ret);
return (long)ret;
}
static ssize_t local_rpc_write(struct file *file, const char __user *buf,
size_t count, loff_t *ppos)
{
struct rpc_info *info = file->private_data;
u8 data[TEGRA_RPC_MAX_MSG_LEN];
int ret;
if (!info)
return -EINVAL;
else if (count > TEGRA_RPC_MAX_MSG_LEN)
return -EINVAL;
if (copy_from_user(data, buf, count))
return -EFAULT;
ret = trpc_send_msg(&rpc_node, info->rpc_ep, data, count,
GFP_KERNEL);
if (ret)
return ret;
return count;
}
static ssize_t local_rpc_read(struct file *file, char __user *buf, size_t max,
loff_t *ppos)
{
struct rpc_info *info = file->private_data;
int ret;
u8 data[TEGRA_RPC_MAX_MSG_LEN];
if (max > TEGRA_RPC_MAX_MSG_LEN)
return -EINVAL;
ret = trpc_recv_msg(&rpc_node, info->rpc_ep, data,
TEGRA_RPC_MAX_MSG_LEN, 0);
if (ret == 0)
return 0;
else if (ret < 0)
return ret;
else if (ret > max)
return -ENOSPC;
else if (copy_to_user(buf, data, ret))
return -EFAULT;
return ret;
}
static const struct file_operations local_rpc_misc_fops = {
.owner = THIS_MODULE,
.open = local_rpc_open,
.release = local_rpc_release,
.unlocked_ioctl = local_rpc_ioctl,
.write = local_rpc_write,
.read = local_rpc_read,
};
static struct miscdevice local_rpc_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "tegra_rpc",
.fops = &local_rpc_misc_fops,
};
int __init rpc_local_init(void)
{
int ret;
ret = trpc_sema_init();
if (ret) {
pr_err("%s: error in trpc_sema_init\n", __func__);
goto err_sema_init;
}
ret = misc_register(&local_rpc_misc_device);
if (ret) {
pr_err("%s: can't register misc device\n", __func__);
goto err_misc;
}
ret = trpc_node_register(&rpc_node);
if (ret) {
pr_err("%s: can't register rpc node\n", __func__);
goto err_node_reg;
}
return 0;
err_node_reg:
misc_deregister(&local_rpc_misc_device);
err_misc:
err_sema_init:
return ret;
}
module_init(rpc_local_init);

View File

@@ -0,0 +1,220 @@
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Dima Zavin <dima@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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/err.h>
#include <linux/file.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/tegra_sema.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include "trpc_sema.h"
struct trpc_sema {
wait_queue_head_t wq;
spinlock_t lock;
int count;
};
static int rpc_sema_minor = -1;
static inline bool is_trpc_sema_file(struct file *file)
{
dev_t rdev = file->f_dentry->d_inode->i_rdev;
if (MAJOR(rdev) == MISC_MAJOR && MINOR(rdev) == rpc_sema_minor)
return true;
return false;
}
struct file *trpc_sema_get_from_fd(int fd)
{
struct file *file;
file = fget(fd);
if (unlikely(file == NULL)) {
pr_err("%s: fd %d is invalid\n", __func__, fd);
return ERR_PTR(-EINVAL);
}
if (!is_trpc_sema_file(file)) {
pr_err("%s: fd (%d) is not a trpc_sema file\n", __func__, fd);
fput(file);
return ERR_PTR(-EINVAL);
}
return file;
}
int trpc_sema_signal(struct file *file)
{
struct trpc_sema *info = file->private_data;
unsigned long flags;
if (!info)
return -EINVAL;
spin_lock_irqsave(&info->lock, flags);
info->count++;
wake_up_interruptible_all(&info->wq);
spin_unlock_irqrestore(&info->lock, flags);
return 0;
}
static int trpc_sema_wait(struct trpc_sema *info, long *timeleft)
{
unsigned long flags;
int ret = 0;
unsigned long endtime;
long timeout = *timeleft;
*timeleft = 0;
if (timeout < 0) {
timeout = MAX_SCHEDULE_TIMEOUT;
} else if (timeout > 0) {
timeout = msecs_to_jiffies(timeout);
endtime = jiffies + timeout;
}
again:
if (timeout)
ret = wait_event_interruptible_timeout(info->wq,
info->count > 0,
timeout);
spin_lock_irqsave(&info->lock, flags);
if (info->count > 0) {
info->count--;
ret = 0;
} else if (ret == 0 || timeout == 0) {
ret = -ETIMEDOUT;
} else if (ret < 0) {
ret = -EINTR;
if (timeout != MAX_SCHEDULE_TIMEOUT &&
time_before(jiffies, endtime))
*timeleft = jiffies_to_msecs(endtime - jiffies);
else
*timeleft = 0;
} else {
/* we woke up but someone else got the semaphore and we have
* time left, try again */
timeout = ret;
spin_unlock_irqrestore(&info->lock, flags);
goto again;
}
spin_unlock_irqrestore(&info->lock, flags);
return ret;
}
static int trpc_sema_open(struct inode *inode, struct file *file)
{
struct trpc_sema *info;
info = kzalloc(sizeof(struct trpc_sema), GFP_KERNEL);
if (!info)
return -ENOMEM;
nonseekable_open(inode, file);
init_waitqueue_head(&info->wq);
spin_lock_init(&info->lock);
file->private_data = info;
return 0;
}
static int trpc_sema_release(struct inode *inode, struct file *file)
{
struct trpc_sema *info = file->private_data;
file->private_data = NULL;
kfree(info);
return 0;
}
static long trpc_sema_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
struct trpc_sema *info = file->private_data;
int ret;
long timeout;
if (_IOC_TYPE(cmd) != TEGRA_SEMA_IOCTL_MAGIC ||
_IOC_NR(cmd) < TEGRA_SEMA_IOCTL_MIN_NR ||
_IOC_NR(cmd) > TEGRA_SEMA_IOCTL_MAX_NR)
return -ENOTTY;
else if (!info)
return -EINVAL;
switch (cmd) {
case TEGRA_SEMA_IOCTL_WAIT:
if (copy_from_user(&timeout, (void __user *)arg, sizeof(long)))
return -EFAULT;
ret = trpc_sema_wait(info, &timeout);
if (ret != -EINTR)
break;
if (copy_to_user((void __user *)arg, &timeout, sizeof(long)))
ret = -EFAULT;
break;
case TEGRA_SEMA_IOCTL_SIGNAL:
ret = trpc_sema_signal(file);
break;
default:
pr_err("%s: Unknown tegra_sema ioctl 0x%x\n", __func__,
_IOC_NR(cmd));
ret = -ENOTTY;
break;
}
return ret;
}
static const struct file_operations trpc_sema_misc_fops = {
.owner = THIS_MODULE,
.open = trpc_sema_open,
.release = trpc_sema_release,
.unlocked_ioctl = trpc_sema_ioctl,
};
static struct miscdevice trpc_sema_misc_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = "tegra_sema",
.fops = &trpc_sema_misc_fops,
};
int __init trpc_sema_init(void)
{
int ret;
if (rpc_sema_minor >= 0) {
pr_err("%s: trpc_sema already registered\n", __func__);
return -EBUSY;
}
ret = misc_register(&trpc_sema_misc_device);
if (ret) {
pr_err("%s: can't register misc device\n", __func__);
return ret;
}
rpc_sema_minor = trpc_sema_misc_device.minor;
pr_info("%s: registered misc dev %d:%d\n", __func__, MISC_MAJOR,
rpc_sema_minor);
return 0;
}

View File

@@ -0,0 +1,28 @@
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Dima Zavin <dima@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef __ARM_MACH_TEGRA_RPC_SEMA_H
#define __ARM_MACH_TEGRA_RPC_SEMA_H
#include <linux/types.h>
#include <linux/fs.h>
struct file *trpc_sema_get_from_fd(int fd);
int trpc_sema_signal(struct file *file);
int __init trpc_sema_init(void);
#endif

47
include/linux/tegra_rpc.h Normal file
View File

@@ -0,0 +1,47 @@
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Dima Zavin <dima@android.com>
*
* Based on original code from NVIDIA, and a partial rewrite by:
* Gary King <gking@nvidia.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef __LINUX_TEGRA_RPC_H
#define __LINUX_TEGRA_RPC_H
#define TEGRA_RPC_MAX_MSG_LEN 256
/* Note: the actual size of the name in the protocol message is 16 bytes,
* but that is because the name there is not NUL terminated, only NUL
* padded. */
#define TEGRA_RPC_MAX_NAME_LEN 17
struct tegra_rpc_port_desc {
char name[TEGRA_RPC_MAX_NAME_LEN];
int notify_fd; /* fd representing a trpc_sema to signal when a
* message has been received */
};
#define TEGRA_RPC_IOCTL_MAGIC 'r'
#define TEGRA_RPC_IOCTL_PORT_CREATE _IOW(TEGRA_RPC_IOCTL_MAGIC, 0x20, struct tegra_rpc_port_desc)
#define TEGRA_RPC_IOCTL_PORT_GET_NAME _IOR(TEGRA_RPC_IOCTL_MAGIC, 0x21, char *)
#define TEGRA_RPC_IOCTL_PORT_CONNECT _IOR(TEGRA_RPC_IOCTL_MAGIC, 0x22, long)
#define TEGRA_RPC_IOCTL_PORT_LISTEN _IOR(TEGRA_RPC_IOCTL_MAGIC, 0x23, long)
#define TEGRA_RPC_IOCTL_MIN_NR _IOC_NR(TEGRA_RPC_IOCTL_PORT_CREATE)
#define TEGRA_RPC_IOCTL_MAX_NR _IOC_NR(TEGRA_RPC_IOCTL_PORT_LISTEN)
#endif

View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2010 Google, Inc.
*
* Author:
* Dima Zavin <dima@android.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#ifndef __LINUX_TEGRA_SEMA_H
#define __LINUX_TEGRA_SEMA_H
/* this shares the magic with the tegra RPC and AVP drivers.
* See include/linux/tegra_avp.h and include/linux/tegra_rpc.h */
#define TEGRA_SEMA_IOCTL_MAGIC 'r'
/* If IOCTL_WAIT is interrupted by a signal and the timeout was not -1,
* then the value pointed to by the argument will be updated with the amount
* of time remaining for the wait. */
#define TEGRA_SEMA_IOCTL_WAIT _IOW(TEGRA_SEMA_IOCTL_MAGIC, 0x30, long *)
#define TEGRA_SEMA_IOCTL_SIGNAL _IO(TEGRA_SEMA_IOCTL_MAGIC, 0x31)
#define TEGRA_SEMA_IOCTL_MIN_NR _IOC_NR(TEGRA_SEMA_IOCTL_WAIT)
#define TEGRA_SEMA_IOCTL_MAX_NR _IOC_NR(TEGRA_SEMA_IOCTL_SIGNAL)
#endif