mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-11 05:17:10 +09:00
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:
@@ -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"
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
obj-$(CONFIG_TEGRA_CAMERA) += tegra_camera.o
|
||||
obj-y += avp/
|
||||
|
||||
15
drivers/media/video/tegra/avp/Kconfig
Normal file
15
drivers/media/video/tegra/avp/Kconfig
Normal 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
|
||||
3
drivers/media/video/tegra/avp/Makefile
Normal file
3
drivers/media/video/tegra/avp/Makefile
Normal 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
|
||||
738
drivers/media/video/tegra/avp/tegra_rpc.c
Normal file
738
drivers/media/video/tegra/avp/tegra_rpc.c
Normal 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);
|
||||
77
drivers/media/video/tegra/avp/trpc.h
Normal file
77
drivers/media/video/tegra/avp/trpc.h
Normal 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
|
||||
333
drivers/media/video/tegra/avp/trpc_local.c
Normal file
333
drivers/media/video/tegra/avp/trpc_local.c
Normal 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);
|
||||
220
drivers/media/video/tegra/avp/trpc_sema.c
Normal file
220
drivers/media/video/tegra/avp/trpc_sema.c
Normal 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;
|
||||
}
|
||||
28
drivers/media/video/tegra/avp/trpc_sema.h
Normal file
28
drivers/media/video/tegra/avp/trpc_sema.h
Normal 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
47
include/linux/tegra_rpc.h
Normal 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
|
||||
34
include/linux/tegra_sema.h
Normal file
34
include/linux/tegra_sema.h
Normal 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
|
||||
Reference in New Issue
Block a user