From: Dima Zavin Date: Mon, 1 Nov 2010 21:20:17 +0000 (-0700) Subject: media: video: tegra: Add Tegra RPC support for tegra multimedia framework X-Git-Tag: firefly_0821_release~9833^2~120^2~3 X-Git-Url: http://demsky.eecs.uci.edu/git/?a=commitdiff_plain;h=44729054a5c38c702ebada8ba31184d8f938d0b0;p=firefly-linux-kernel-4.4.55.git media: video: tegra: Add Tegra RPC support for tegra multimedia framework Change-Id: I9233c5d7c678f6a9ba1c23af686137bf4d6a4291 Signed-off-by: Dima Zavin --- diff --git a/drivers/media/video/tegra/Kconfig b/drivers/media/video/tegra/Kconfig index 86c6aa2c466b..ce31c4ecbe97 100644 --- a/drivers/media/video/tegra/Kconfig +++ b/drivers/media/video/tegra/Kconfig @@ -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" diff --git a/drivers/media/video/tegra/Makefile b/drivers/media/video/tegra/Makefile index e5a074ea6608..4cb8e3c78485 100644 --- a/drivers/media/video/tegra/Makefile +++ b/drivers/media/video/tegra/Makefile @@ -1 +1,2 @@ obj-$(CONFIG_TEGRA_CAMERA) += tegra_camera.o +obj-y += avp/ diff --git a/drivers/media/video/tegra/avp/Kconfig b/drivers/media/video/tegra/avp/Kconfig new file mode 100644 index 000000000000..dca58da0862c --- /dev/null +++ b/drivers/media/video/tegra/avp/Kconfig @@ -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 diff --git a/drivers/media/video/tegra/avp/Makefile b/drivers/media/video/tegra/avp/Makefile new file mode 100644 index 000000000000..438cbb78d8cd --- /dev/null +++ b/drivers/media/video/tegra/avp/Makefile @@ -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 diff --git a/drivers/media/video/tegra/avp/tegra_rpc.c b/drivers/media/video/tegra/avp/tegra_rpc.c new file mode 100644 index 000000000000..c4e707e9a89e --- /dev/null +++ b/drivers/media/video/tegra/avp/tegra_rpc.c @@ -0,0 +1,738 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Dima Zavin + * + * Based on original NVRM code from NVIDIA, and a partial rewrite by: + * Gary King + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); diff --git a/drivers/media/video/tegra/avp/trpc.h b/drivers/media/video/tegra/avp/trpc.h new file mode 100644 index 000000000000..859c94e7460c --- /dev/null +++ b/drivers/media/video/tegra/avp/trpc.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Dima Zavin + * + * 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 +#include + +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 diff --git a/drivers/media/video/tegra/avp/trpc_local.c b/drivers/media/video/tegra/avp/trpc_local.c new file mode 100644 index 000000000000..5a941a78fc40 --- /dev/null +++ b/drivers/media/video/tegra/avp/trpc_local.c @@ -0,0 +1,333 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Dima Zavin + * + * Based on original NVRM code from NVIDIA, and a partial rewrite by + * Gary King + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); diff --git a/drivers/media/video/tegra/avp/trpc_sema.c b/drivers/media/video/tegra/avp/trpc_sema.c new file mode 100644 index 000000000000..b8772573d956 --- /dev/null +++ b/drivers/media/video/tegra/avp/trpc_sema.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Dima Zavin + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/drivers/media/video/tegra/avp/trpc_sema.h b/drivers/media/video/tegra/avp/trpc_sema.h new file mode 100644 index 000000000000..566bbdbe739e --- /dev/null +++ b/drivers/media/video/tegra/avp/trpc_sema.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Dima Zavin + * + * 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 +#include + +struct file *trpc_sema_get_from_fd(int fd); +int trpc_sema_signal(struct file *file); +int __init trpc_sema_init(void); + +#endif diff --git a/include/linux/tegra_rpc.h b/include/linux/tegra_rpc.h new file mode 100644 index 000000000000..16e6367cf569 --- /dev/null +++ b/include/linux/tegra_rpc.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Dima Zavin + * + * Based on original code from NVIDIA, and a partial rewrite by: + * Gary King + * + * 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 diff --git a/include/linux/tegra_sema.h b/include/linux/tegra_sema.h new file mode 100644 index 000000000000..7b423b6cb5c4 --- /dev/null +++ b/include/linux/tegra_sema.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Dima Zavin + * + * 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