diff options
Diffstat (limited to 'drivers/rpmsg')
-rw-r--r-- | drivers/rpmsg/Kconfig | 54 | ||||
-rw-r--r-- | drivers/rpmsg/Makefile | 6 | ||||
-rw-r--r-- | drivers/rpmsg/rpmsg_client_sample.c | 100 | ||||
-rw-r--r-- | drivers/rpmsg/rpmsg_omx.c | 833 | ||||
-rw-r--r-- | drivers/rpmsg/rpmsg_resmgr.c | 1211 | ||||
-rw-r--r-- | drivers/rpmsg/rpmsg_server_sample.c | 99 | ||||
-rw-r--r-- | drivers/rpmsg/virtio_rpmsg_bus.c | 818 |
7 files changed, 3121 insertions, 0 deletions
diff --git a/drivers/rpmsg/Kconfig b/drivers/rpmsg/Kconfig new file mode 100644 index 0000000..e40c9d8 --- /dev/null +++ b/drivers/rpmsg/Kconfig @@ -0,0 +1,54 @@ +config RPMSG + tristate "Virtio-based remote processor messaging bus" + default y + select VIRTIO + select VIRTIO_RING + depends on OMAP_RPMSG + ---help--- + This virtio driver provides support for shared-memory-based + remote processor messaging, by registering the RPMSG bus which + in turn enables a handful of IPC drivers. + + Such support is usually required to offload cpu-intensive + or latency-sensitive tasks to specialized remote processors with + dedicated hardware accelerators and/or real-time properties. + + If unsure, say N. + +config RPMSG_OMX + tristate "rpmsg OMX driver" + default y + depends on RPMSG + depends on TI_TILER + ---help--- + An rpmsg driver that exposes OMX API to user space, in order to + allow multimedia applications to offload OMX processing to + remote processors. + + If unsure, say N. + +config RPMSG_RESMGR + tristate "rpmsg resource manager" + default y + depends on RPMSG + depends on OMAP_RPRES + ---help--- + Add Framework base on RPMSG to request and release resources + from a remote Processor. + Say either Y or M. You know you want to. + +config RPMSG_CLIENT_SAMPLE + tristate "An rpmsg client sample" + default m + depends on RPMSG + ---help--- + This is just a sample client driver for the rpmsg bus. + Say either Y or M. You know you want to. + +config RPMSG_SERVER_SAMPLE + tristate "An rpmsg server sample" + default m + depends on RPMSG + ---help--- + This is just a sample server driver for the rpmsg bus. + Say either Y or M. You know you want to. diff --git a/drivers/rpmsg/Makefile b/drivers/rpmsg/Makefile new file mode 100644 index 0000000..1b0e04b --- /dev/null +++ b/drivers/rpmsg/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_RPMSG) += virtio_rpmsg_bus.o + +obj-$(CONFIG_RPMSG_OMX) += rpmsg_omx.o +obj-$(CONFIG_RPMSG_CLIENT_SAMPLE) += rpmsg_client_sample.o +obj-$(CONFIG_RPMSG_SERVER_SAMPLE) += rpmsg_server_sample.o +obj-$(CONFIG_RPMSG_RESMGR) += rpmsg_resmgr.o diff --git a/drivers/rpmsg/rpmsg_client_sample.c b/drivers/rpmsg/rpmsg_client_sample.c new file mode 100644 index 0000000..a3f5013 --- /dev/null +++ b/drivers/rpmsg/rpmsg_client_sample.c @@ -0,0 +1,100 @@ +/* + * Remote processor messaging transport - sample client driver + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/virtio.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/rpmsg.h> + +#define MSG ("hello world!") +#define MSG_LIMIT 100 + +static void rpmsg_sample_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + int err; + static int rx_count; + + dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n", ++rx_count, src); + + print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, + data, len, true); + + /* samples should not live forever */ + if (rx_count >= MSG_LIMIT) { + dev_info(&rpdev->dev, "goodbye!\n"); + return; + } + + /* send a new message now */ + err = rpmsg_send(rpdev, MSG, strlen(MSG)); + if (err) + pr_err("rpmsg_send failed: %d\n", err); +} + +static int rpmsg_sample_probe(struct rpmsg_channel *rpdev) +{ + int err; + + dev_info(&rpdev->dev, "new channel: 0x%x <-> 0x%x!\n", + rpdev->src, rpdev->dst); + + /* send a message to our remote processor */ + err = rpmsg_send(rpdev, MSG, strlen(MSG)); + if (err) { + pr_err("rpmsg_send failed: %d\n", err); + return err; + } + + return 0; +} + +static void __devexit rpmsg_sample_remove(struct rpmsg_channel *rpdev) +{ + dev_info(&rpdev->dev, "rpmsg sample client driver is removed\n"); +} + +static struct rpmsg_device_id rpmsg_driver_sample_id_table[] = { + { .name = "rpmsg-client-sample" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, rpmsg_driver_sample_id_table); + +static struct rpmsg_driver rpmsg_sample_client_driver = { + .drv.name = KBUILD_MODNAME, + .drv.owner = THIS_MODULE, + .id_table = rpmsg_driver_sample_id_table, + .probe = rpmsg_sample_probe, + .callback = rpmsg_sample_cb, + .remove = __devexit_p(rpmsg_sample_remove), +}; + +static int __init init(void) +{ + return register_rpmsg_driver(&rpmsg_sample_client_driver); +} + +static void __exit fini(void) +{ + unregister_rpmsg_driver(&rpmsg_sample_client_driver); +} +module_init(init); +module_exit(fini); + +MODULE_DESCRIPTION("Virtio remote processor messaging sample client driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rpmsg/rpmsg_omx.c b/drivers/rpmsg/rpmsg_omx.c new file mode 100644 index 0000000..972a918 --- /dev/null +++ b/drivers/rpmsg/rpmsg_omx.c @@ -0,0 +1,833 @@ +/* + * OMX offloading remote processor driver + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/idr.h> +#include <linux/fs.h> +#include <linux/poll.h> +#include <linux/cdev.h> +#include <linux/jiffies.h> +#include <linux/mutex.h> +#include <linux/wait.h> +#include <linux/skbuff.h> +#include <linux/sched.h> +#include <linux/rpmsg.h> +#include <linux/rpmsg_omx.h> +#include <linux/completion.h> + +#include <mach/tiler.h> + +#ifdef CONFIG_ION_OMAP +#include <linux/ion.h> +#include <linux/omap_ion.h> + +extern struct ion_device *omap_ion_device; +#endif + +/* maximum OMX devices this driver can handle */ +#define MAX_OMX_DEVICES 8 + +enum rpc_omx_map_info_type { + RPC_OMX_MAP_INFO_NONE = 0, + RPC_OMX_MAP_INFO_ONE_BUF = 1, + RPC_OMX_MAP_INFO_TWO_BUF = 2, + RPC_OMX_MAP_INFO_THREE_BUF = 3, + RPC_OMX_MAP_INFO_MAX = 0x7FFFFFFF +}; + +enum { + OMX_SERVICE_DOWN, + OMX_SERVICE_UP +}; + +struct rpmsg_omx_service { + struct list_head next; + struct cdev cdev; + struct device *dev; + struct rpmsg_channel *rpdev; + int minor; + struct list_head list; + struct mutex lock; + struct completion comp; + int state; +#ifdef CONFIG_ION_OMAP + struct ion_client *ion_client; +#endif +}; + +struct rpmsg_omx_instance { + struct list_head next; + struct rpmsg_omx_service *omxserv; + struct sk_buff_head queue; + struct mutex lock; + wait_queue_head_t readq; + struct completion reply_arrived; + struct rpmsg_endpoint *ept; + u32 dst; + int state; +#ifdef CONFIG_ION_OMAP + struct ion_client *ion_client; +#endif +}; + +static struct class *rpmsg_omx_class; +static dev_t rpmsg_omx_dev; + +/* store all remote omx connection services (usually one per remoteproc) */ +static DEFINE_IDR(rpmsg_omx_services); +static DEFINE_SPINLOCK(rpmsg_omx_services_lock); +static LIST_HEAD(rpmsg_omx_services_list); + +#ifdef CONFIG_ION_OMAP +#ifdef CONFIG_PVR_SGX +#include "../gpu/pvr/ion.h" +#endif +#endif + +/* + * TODO: Need to do this using lookup with rproc, but rproc is not + * visible to rpmsg_omx + */ +#define TILER_START 0x60000000 +#define TILER_END 0x80000000 +#define ION_1D_START 0xBA300000 +#define ION_1D_END 0xBFD00000 +#define ION_1D_VA 0x88000000 +static u32 _rpmsg_pa_to_da(u32 pa) +{ + if (pa >= TILER_START && pa < TILER_END) + return pa; + else if (pa >= ION_1D_START && pa < ION_1D_END) + return (pa - ION_1D_START + ION_1D_VA); + else + return 0; +} + +static u32 _rpmsg_omx_buffer_lookup(struct rpmsg_omx_instance *omx, long buffer) +{ + phys_addr_t pa; + u32 va; +#ifdef CONFIG_ION_OMAP + struct ion_handle *handle; + ion_phys_addr_t paddr; + size_t unused; + int fd; + + /* is it an ion handle? */ + handle = (struct ion_handle *)buffer; + if (!ion_phys(omx->ion_client, handle, &paddr, &unused)) { + pa = (phys_addr_t) paddr; + goto to_va; + } + +#ifdef CONFIG_PVR_SGX + /* how about an sgx buffer wrapping an ion handle? */ + { + struct ion_client *pvr_ion_client; + fd = buffer; + handle = PVRSRVExportFDToIONHandle(fd, &pvr_ion_client); + if (handle && + !ion_phys(pvr_ion_client, handle, &paddr, &unused)) { + pa = (phys_addr_t)paddr; + goto to_va; + } + } +#endif +#endif + pa = (phys_addr_t) tiler_virt2phys(buffer); + +#ifdef CONFIG_ION_OMAP +to_va: +#endif + va = _rpmsg_pa_to_da(pa); + return va; +} + +static int _rpmsg_omx_map_buf(struct rpmsg_omx_instance *omx, char *packet) +{ + int ret = -EINVAL, offset = 0; + long *buffer; + char *data; + enum rpc_omx_map_info_type maptype; + u32 da = 0; + + data = (char *)((struct omx_packet *)packet)->data; + maptype = *((enum rpc_omx_map_info_type *)data); + + /*Nothing to map*/ + if (maptype == RPC_OMX_MAP_INFO_NONE) + return 0; + if ((maptype != RPC_OMX_MAP_INFO_THREE_BUF) && + (maptype != RPC_OMX_MAP_INFO_TWO_BUF) && + (maptype != RPC_OMX_MAP_INFO_ONE_BUF)) + return ret; + + offset = *(int *)((int)data + sizeof(maptype)); + buffer = (long *)((int)data + offset); + + da = _rpmsg_omx_buffer_lookup(omx, *buffer); + if (da) { + *buffer = da; + ret = 0; + } + + if (!ret && (maptype >= RPC_OMX_MAP_INFO_TWO_BUF)) { + buffer = (long *)((int)data + offset + sizeof(*buffer)); + if (*buffer != 0) { + ret = -EIO; + da = _rpmsg_omx_buffer_lookup(omx, *buffer); + if (da) { + *buffer = da; + ret = 0; + } + } + } + + if (!ret && maptype >= RPC_OMX_MAP_INFO_THREE_BUF) { + buffer = (long *)((int)data + offset + 2*sizeof(*buffer)); + if (*buffer != 0) { + ret = -EIO; + da = _rpmsg_omx_buffer_lookup(omx, *buffer); + if (da) { + *buffer = da; + ret = 0; + } + } + } + return ret; +} + +static void rpmsg_omx_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct omx_msg_hdr *hdr = data; + struct rpmsg_omx_instance *omx = priv; + struct omx_conn_rsp *rsp; + struct sk_buff *skb; + char *skbdata; + + if (len < sizeof(*hdr) || hdr->len < len - sizeof(*hdr)) { + dev_warn(&rpdev->dev, "%s: truncated message\n", __func__); + return; + } + + dev_dbg(&rpdev->dev, "%s: incoming msg src 0x%x type %d len %d\n", + __func__, src, hdr->type, hdr->len); + print_hex_dump(KERN_DEBUG, "rpmsg_omx RX: ", DUMP_PREFIX_NONE, 16, 1, + data, len, true); + + switch (hdr->type) { + case OMX_CONN_RSP: + if (hdr->len < sizeof(*rsp)) { + dev_warn(&rpdev->dev, "incoming empty response msg\n"); + break; + } + rsp = (struct omx_conn_rsp *) hdr->data; + dev_info(&rpdev->dev, "conn rsp: status %d addr %d\n", + rsp->status, rsp->addr); + omx->dst = rsp->addr; + if (rsp->status) + omx->state = OMX_FAIL; + else + omx->state = OMX_CONNECTED; + complete(&omx->reply_arrived); + break; + case OMX_RAW_MSG: + skb = alloc_skb(hdr->len, GFP_KERNEL); + if (!skb) { + dev_err(&rpdev->dev, "alloc_skb err: %u\n", hdr->len); + break; + } + skbdata = skb_put(skb, hdr->len); + memcpy(skbdata, hdr->data, hdr->len); + + mutex_lock(&omx->lock); + skb_queue_tail(&omx->queue, skb); + mutex_unlock(&omx->lock); + /* wake up any blocking processes, waiting for new data */ + wake_up_interruptible(&omx->readq); + break; + default: + dev_warn(&rpdev->dev, "unexpected msg type: %d\n", hdr->type); + break; + } +} + +static int rpmsg_omx_connect(struct rpmsg_omx_instance *omx, char *omxname) +{ + struct omx_msg_hdr *hdr; + struct omx_conn_req *payload; + struct rpmsg_omx_service *omxserv = omx->omxserv; + char connect_msg[sizeof(*hdr) + sizeof(*payload)] = { 0 }; + int ret; + + if (omx->state == OMX_CONNECTED) { + dev_dbg(omxserv->dev, "endpoint already connected\n"); + return -EISCONN; + } + + hdr = (struct omx_msg_hdr *)connect_msg; + hdr->type = OMX_CONN_REQ; + hdr->flags = 0; + hdr->len = strlen(omxname) + 1; + payload = (struct omx_conn_req *)hdr->data; + strcpy(payload->name, omxname); + + init_completion(&omx->reply_arrived); + + /* send a conn req to the remote OMX connection service. use + * the new local address that was just allocated by ->open */ + ret = rpmsg_send_offchannel(omxserv->rpdev, omx->ept->addr, + omxserv->rpdev->dst, connect_msg, sizeof(connect_msg)); + if (ret) { + dev_err(omxserv->dev, "rpmsg_send failed: %d\n", ret); + return ret; + } + + /* wait until a connection reply arrives or 5 seconds elapse */ + ret = wait_for_completion_interruptible_timeout(&omx->reply_arrived, + msecs_to_jiffies(5000)); + if (omx->state == OMX_CONNECTED) + return 0; + + if (omx->state == OMX_FAIL) + return -ENXIO; + + if (ret) { + dev_err(omxserv->dev, "premature wakeup: %d\n", ret); + return -EIO; + } + + return -ETIMEDOUT; +} + +static +long rpmsg_omx_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + struct rpmsg_omx_instance *omx = filp->private_data; + struct rpmsg_omx_service *omxserv = omx->omxserv; + char buf[48]; + int ret = 0; + + dev_dbg(omxserv->dev, "%s: cmd %d, arg 0x%lx\n", __func__, cmd, arg); + + if (_IOC_TYPE(cmd) != OMX_IOC_MAGIC) + return -ENOTTY; + if (_IOC_NR(cmd) > OMX_IOC_MAXNR) + return -ENOTTY; + + switch (cmd) { + case OMX_IOCCONNECT: + ret = copy_from_user(buf, (char __user *) arg, sizeof(buf)); + if (ret) { + dev_err(omxserv->dev, + "%s: %d: copy_from_user fail: %d\n", __func__, + _IOC_NR(cmd), ret); + ret = -EFAULT; + break; + } + /* make sure user input is null terminated */ + buf[sizeof(buf) - 1] = '\0'; + ret = rpmsg_omx_connect(omx, buf); + break; +#ifdef CONFIG_ION_OMAP + case OMX_IOCIONREGISTER: + { + struct ion_fd_data data; + if (copy_from_user(&data, (char __user *) arg, sizeof(data))) { + dev_err(omxserv->dev, + "%s: %d: copy_from_user fail: %d\n", __func__, + _IOC_NR(cmd), ret); + return -EFAULT; + } + data.handle = ion_import_fd(omx->ion_client, data.fd); + if (IS_ERR(data.handle)) + data.handle = NULL; + if (copy_to_user(&data, (char __user *) arg, sizeof(data))) { + dev_err(omxserv->dev, + "%s: %d: copy_to_user fail: %d\n", __func__, + _IOC_NR(cmd), ret); + return -EFAULT; + } + break; + } + case OMX_IOCIONUNREGISTER: + { + struct ion_fd_data data; + if (copy_from_user(&data, (char __user *) arg, sizeof(data))) { + dev_err(omxserv->dev, + "%s: %d: copy_from_user fail: %d\n", __func__, + _IOC_NR(cmd), ret); + return -EFAULT; + } + ion_free(omx->ion_client, data.handle); + if (copy_to_user(&data, (char __user *) arg, sizeof(data))) { + dev_err(omxserv->dev, + "%s: %d: copy_to_user fail: %d\n", __func__, + _IOC_NR(cmd), ret); + return -EFAULT; + } + break; + } +#endif + default: + dev_warn(omxserv->dev, "unhandled ioctl cmd: %d\n", cmd); + break; + } + + return ret; +} + +static int rpmsg_omx_open(struct inode *inode, struct file *filp) +{ + struct rpmsg_omx_service *omxserv; + struct rpmsg_omx_instance *omx; + + omxserv = container_of(inode->i_cdev, struct rpmsg_omx_service, cdev); + + if (omxserv->state == OMX_SERVICE_DOWN) + if (filp->f_flags & O_NONBLOCK || + wait_for_completion_interruptible(&omxserv->comp)) + return -EBUSY; + + omx = kzalloc(sizeof(*omx), GFP_KERNEL); + if (!omx) + return -ENOMEM; + + mutex_init(&omx->lock); + skb_queue_head_init(&omx->queue); + init_waitqueue_head(&omx->readq); + omx->omxserv = omxserv; + omx->state = OMX_UNCONNECTED; + + /* assign a new, unique, local address and associate omx with it */ + omx->ept = rpmsg_create_ept(omxserv->rpdev, rpmsg_omx_cb, omx, + RPMSG_ADDR_ANY); + if (!omx->ept) { + dev_err(omxserv->dev, "create ept failed\n"); + kfree(omx); + return -ENOMEM; + } +#ifdef CONFIG_ION_OMAP + omx->ion_client = ion_client_create(omap_ion_device, + (1<< ION_HEAP_TYPE_CARVEOUT) | + (1 << OMAP_ION_HEAP_TYPE_TILER), + "rpmsg-omx"); +#endif + + /* associate filp with the new omx instance */ + filp->private_data = omx; + mutex_lock(&omxserv->lock); + list_add(&omx->next, &omxserv->list); + mutex_unlock(&omxserv->lock); + + dev_info(omxserv->dev, "local addr assigned: 0x%x\n", omx->ept->addr); + + return 0; +} + +static int rpmsg_omx_release(struct inode *inode, struct file *filp) +{ + struct rpmsg_omx_instance *omx = filp->private_data; + struct rpmsg_omx_service *omxserv = omx->omxserv; + char kbuf[512]; + struct omx_msg_hdr *hdr = (struct omx_msg_hdr *) kbuf; + struct omx_disc_req *disc_req = (struct omx_disc_req *)hdr->data; + int use, ret; + + /* todo: release resources here */ + /* + * If state == fail, remote processor crashed, so don't send it + * any message. + */ + if (omx->state == OMX_FAIL) + goto out; + + /* send a disconnect msg with the OMX instance addr */ + hdr->type = OMX_DISCONNECT; + hdr->flags = 0; + hdr->len = sizeof(struct omx_disc_req); + disc_req->addr = omx->dst; + use = sizeof(*hdr) + hdr->len; + + dev_info(omxserv->dev, "Disconnecting from OMX service at %d\n", + omx->dst); + + /* send the msg to the remote OMX connection service */ + ret = rpmsg_send_offchannel(omxserv->rpdev, omx->ept->addr, + omxserv->rpdev->dst, kbuf, use); + if (ret) { + dev_err(omxserv->dev, "rpmsg_send failed: %d\n", ret); + return ret; + } + rpmsg_destroy_ept(omx->ept); +out: +#ifdef CONFIG_ION_OMAP + ion_client_destroy(omx->ion_client); +#endif + mutex_lock(&omxserv->lock); + list_del(&omx->next); + mutex_unlock(&omxserv->lock); + kfree(omx); + + return 0; +} + +static ssize_t rpmsg_omx_read(struct file *filp, char __user *buf, + size_t len, loff_t *offp) +{ + struct rpmsg_omx_instance *omx = filp->private_data; + struct sk_buff *skb; + int use; + + if (mutex_lock_interruptible(&omx->lock)) + return -ERESTARTSYS; + + if (omx->state == OMX_FAIL) { + mutex_unlock(&omx->lock); + return -ENXIO; + } + + if (omx->state != OMX_CONNECTED) { + mutex_unlock(&omx->lock); + return -ENOTCONN; + } + + /* nothing to read ? */ + if (skb_queue_empty(&omx->queue)) { + mutex_unlock(&omx->lock); + /* non-blocking requested ? return now */ + if (filp->f_flags & O_NONBLOCK) + return -EAGAIN; + /* otherwise block, and wait for data */ + if (wait_event_interruptible(omx->readq, + (!skb_queue_empty(&omx->queue) || + omx->state == OMX_FAIL))) + return -ERESTARTSYS; + if (mutex_lock_interruptible(&omx->lock)) + return -ERESTARTSYS; + } + + if (omx->state == OMX_FAIL) { + mutex_unlock(&omx->lock); + return -ENXIO; + } + + skb = skb_dequeue(&omx->queue); + if (!skb) { + mutex_unlock(&omx->lock); + dev_err(omx->omxserv->dev, "err is rmpsg_omx racy ?\n"); + return -EIO; + } + + mutex_unlock(&omx->lock); + + use = min(len, skb->len); + + if (copy_to_user(buf, skb->data, use)) { + dev_err(omx->omxserv->dev, "%s: copy_to_user fail\n", __func__); + use = -EFAULT; + } + + kfree_skb(skb); + return use; +} + +static ssize_t rpmsg_omx_write(struct file *filp, const char __user *ubuf, + size_t len, loff_t *offp) +{ + struct rpmsg_omx_instance *omx = filp->private_data; + struct rpmsg_omx_service *omxserv = omx->omxserv; + char kbuf[512]; + struct omx_msg_hdr *hdr = (struct omx_msg_hdr *) kbuf; + int use, ret; + + if (omx->state != OMX_CONNECTED) + return -ENOTCONN; + + /* + * for now, limit msg size to 512 bytes (incl. header). + * (note: rpmsg's limit is even tighter. this whole thing needs fixing) + */ + use = min(sizeof(kbuf) - sizeof(*hdr), len); + + /* + * copy the data. Later, number of copies can be optimized if found to + * be significant in real use cases + */ + if (copy_from_user(hdr->data, ubuf, use)) + return -EMSGSIZE; + + ret = _rpmsg_omx_map_buf(omx, hdr->data); + if (ret < 0) + return ret; + + hdr->type = OMX_RAW_MSG; + hdr->flags = 0; + hdr->len = use; + + use += sizeof(*hdr); + + ret = rpmsg_send_offchannel(omxserv->rpdev, omx->ept->addr, + omx->dst, kbuf, use); + if (ret) { + dev_err(omxserv->dev, "rpmsg_send failed: %d\n", ret); + return ret; + } + + return use; +} + +static +unsigned int rpmsg_poll(struct file *filp, struct poll_table_struct *wait) +{ + struct rpmsg_omx_instance *omx = filp->private_data; + unsigned int mask = 0; + + if (mutex_lock_interruptible(&omx->lock)) + return -ERESTARTSYS; + + poll_wait(filp, &omx->readq, wait); + if (omx->state == OMX_FAIL) { + mutex_unlock(&omx->lock); + return -ENXIO; + } + + if (!skb_queue_empty(&omx->queue)) + mask |= POLLIN | POLLRDNORM; + + /* implement missing rpmsg virtio functionality here */ + if (true) + mask |= POLLOUT | POLLWRNORM; + + mutex_unlock(&omx->lock); + + return mask; +} + +static const struct file_operations rpmsg_omx_fops = { + .open = rpmsg_omx_open, + .release = rpmsg_omx_release, + .unlocked_ioctl = rpmsg_omx_ioctl, + .read = rpmsg_omx_read, + .write = rpmsg_omx_write, + .poll = rpmsg_poll, + .owner = THIS_MODULE, +}; + +static int rpmsg_omx_probe(struct rpmsg_channel *rpdev) +{ + int ret, major, minor; + struct rpmsg_omx_service *omxserv = NULL, *tmp; + + if (!idr_pre_get(&rpmsg_omx_services, GFP_KERNEL)) { + dev_err(&rpdev->dev, "idr_pre_get failes\n"); + return -ENOMEM; + } + + /* dynamically assign a new minor number */ + spin_lock(&rpmsg_omx_services_lock); + ret = idr_get_new(&rpmsg_omx_services, omxserv, &minor); + if (ret) { + spin_unlock(&rpmsg_omx_services_lock); + dev_err(&rpdev->dev, "failed to idr_get_new: %d\n", ret); + return ret; + } + + /* look for an already created omx service */ + list_for_each_entry(tmp, &rpmsg_omx_services_list, next) { + if (tmp->minor == minor) { + omxserv = tmp; + idr_replace(&rpmsg_omx_services, omxserv, minor); + break; + } + } + spin_unlock(&rpmsg_omx_services_lock); + if (omxserv) + goto serv_up; + + omxserv = kzalloc(sizeof(*omxserv), GFP_KERNEL); + if (!omxserv) { + dev_err(&rpdev->dev, "kzalloc failed\n"); + ret = -ENOMEM; + goto rem_idr; + } + + spin_lock(&rpmsg_omx_services_lock); + idr_replace(&rpmsg_omx_services, omxserv, minor); + spin_unlock(&rpmsg_omx_services_lock); + INIT_LIST_HEAD(&omxserv->list); + mutex_init(&omxserv->lock); + init_completion(&omxserv->comp); + + list_add(&omxserv->next, &rpmsg_omx_services_list); + + major = MAJOR(rpmsg_omx_dev); + + cdev_init(&omxserv->cdev, &rpmsg_omx_fops); + omxserv->cdev.owner = THIS_MODULE; + ret = cdev_add(&omxserv->cdev, MKDEV(major, minor), 1); + if (ret) { + dev_err(&rpdev->dev, "cdev_add failed: %d\n", ret); + goto free_omx; + } + + omxserv->dev = device_create(rpmsg_omx_class, &rpdev->dev, + MKDEV(major, minor), NULL, + "rpmsg-omx%d", minor); + if (IS_ERR(omxserv->dev)) { + ret = PTR_ERR(omxserv->dev); + dev_err(&rpdev->dev, "device_create failed: %d\n", ret); + goto clean_cdev; + } +serv_up: + omxserv->rpdev = rpdev; + omxserv->minor = minor; + omxserv->state = OMX_SERVICE_UP; + dev_set_drvdata(&rpdev->dev, omxserv); + complete_all(&omxserv->comp); + + dev_info(omxserv->dev, "new OMX connection srv channel: %u -> %u!\n", + rpdev->src, rpdev->dst); + return 0; + +clean_cdev: + cdev_del(&omxserv->cdev); +free_omx: + kfree(omxserv); +rem_idr: + spin_lock(&rpmsg_omx_services_lock); + idr_remove(&rpmsg_omx_services, minor); + spin_unlock(&rpmsg_omx_services_lock); + return ret; +} + +static void __devexit rpmsg_omx_remove(struct rpmsg_channel *rpdev) +{ + struct rpmsg_omx_service *omxserv = dev_get_drvdata(&rpdev->dev); + int major = MAJOR(rpmsg_omx_dev); + struct rpmsg_omx_instance *omx; + + dev_info(omxserv->dev, "rpmsg omx driver is removed\n"); + + spin_lock(&rpmsg_omx_services_lock); + idr_remove(&rpmsg_omx_services, omxserv->minor); + spin_unlock(&rpmsg_omx_services_lock); + + mutex_lock(&omxserv->lock); + /* + * If there is omx instrances that means it is a revovery. + * TODO: make sure it is a recovery. + */ + if (list_empty(&omxserv->list)) { + device_destroy(rpmsg_omx_class, MKDEV(major, omxserv->minor)); + cdev_del(&omxserv->cdev); + list_del(&omxserv->next); + mutex_unlock(&omxserv->lock); + kfree(omxserv); + return; + } + /* If it is a recovery, don't clean the omxserv */ + init_completion(&omxserv->comp); + omxserv->state = OMX_SERVICE_DOWN; + list_for_each_entry(omx, &omxserv->list, next) { + /* set omx instance to fail state */ + omx->state = OMX_FAIL; + /* unblock any pending omx thread*/ + complete_all(&omx->reply_arrived); + wake_up_interruptible(&omx->readq); + } + mutex_unlock(&omxserv->lock); +} + +static void rpmsg_omx_driver_cb(struct rpmsg_channel *rpdev, void *data, + int len, void *priv, u32 src) +{ + dev_warn(&rpdev->dev, "uhm, unexpected message\n"); + + print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, + data, len, true); +} + +static struct rpmsg_device_id rpmsg_omx_id_table[] = { + { .name = "rpmsg-omx" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, rpmsg_omx_id_table); + +static struct rpmsg_driver rpmsg_omx_driver = { + .drv.name = KBUILD_MODNAME, + .drv.owner = THIS_MODULE, + .id_table = rpmsg_omx_id_table, + .probe = rpmsg_omx_probe, + .callback = rpmsg_omx_driver_cb, + .remove = __devexit_p(rpmsg_omx_remove), +}; + +static int __init init(void) +{ + int ret; + + ret = alloc_chrdev_region(&rpmsg_omx_dev, 0, MAX_OMX_DEVICES, + KBUILD_MODNAME); + if (ret) { + pr_err("alloc_chrdev_region failed: %d\n", ret); + goto out; + } + + rpmsg_omx_class = class_create(THIS_MODULE, KBUILD_MODNAME); + if (IS_ERR(rpmsg_omx_class)) { + ret = PTR_ERR(rpmsg_omx_class); + pr_err("class_create failed: %d\n", ret); + goto unreg_region; + } + + return register_rpmsg_driver(&rpmsg_omx_driver); + +unreg_region: + unregister_chrdev_region(rpmsg_omx_dev, MAX_OMX_DEVICES); +out: + return ret; +} +module_init(init); + +static void __exit fini(void) +{ + struct rpmsg_omx_service *omxserv, *tmp; + int major = MAJOR(rpmsg_omx_dev); + + unregister_rpmsg_driver(&rpmsg_omx_driver); + list_for_each_entry_safe(omxserv, tmp, &rpmsg_omx_services_list, next) { + device_destroy(rpmsg_omx_class, MKDEV(major, omxserv->minor)); + cdev_del(&omxserv->cdev); + list_del(&omxserv->next); + kfree(omxserv); + } + class_destroy(rpmsg_omx_class); + unregister_chrdev_region(rpmsg_omx_dev, MAX_OMX_DEVICES); +} +module_exit(fini); + +MODULE_DESCRIPTION("OMX offloading rpmsg driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rpmsg/rpmsg_resmgr.c b/drivers/rpmsg/rpmsg_resmgr.c new file mode 100644 index 0000000..c98a1b9 --- /dev/null +++ b/drivers/rpmsg/rpmsg_resmgr.c @@ -0,0 +1,1211 @@ +/* + * Remote processor resource manager + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Fernando Guzman Lugo <fernando.lugo@ti.com> + * Miguel Vadillo <vadillo@ti.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. + */ + + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/virtio.h> +#include <linux/slab.h> +#include <linux/rpmsg.h> +#include <linux/delay.h> +#include <linux/idr.h> +#include <linux/remoteproc.h> +#include <linux/clk.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/machine.h> +#include <linux/gpio.h> +#include <linux/err.h> +#include <linux/list.h> +#include <linux/debugfs.h> +#include <linux/rpmsg_resmgr.h> +#include <linux/pm_runtime.h> +#include <plat/dmtimer.h> +#include <plat/rpres.h> +#include <plat/clock.h> +#include <plat/dma.h> +#include <plat/i2c.h> +#include <plat/omap_hwmod.h> + +#define NAME_SIZE 50 +#define REGULATOR_MAX 1 +#define NUM_SRC_CLK 3 +#define AUX_CLK_MIN 0 +#define AUX_CLK_MAX 5 +#define GPTIMERS_MAX 11 +#define MHZ 1000000 +#define MAX_MSG (sizeof(struct rprm_ack) + sizeof(struct rprm_sdma)) + +static struct dentry *rprm_dbg; + +static char *regulator_name[] = { + "cam2pwr" +}; + +static char *clk_src_name[] = { + "sys_clkin_ck", + "dpll_core_m3x2_ck", + "dpll_per_m3x2_ck", +}; + +static const char const *rnames[] = { + [RPRM_GPTIMER] = "GP Timer", + [RPRM_L3BUS] = "L3 bus", + [RPRM_IVAHD] = "IVA HD", + [RPRM_IVASEQ0] = "IVA SEQ0", + [RPRM_IVASEQ1] = "IVA SEQ1", + [RPRM_ISS] = "ISS", + [RPRM_SL2IF] = "SL2IF", + [RPRM_FDIF] = "FDIF", + [RPRM_AUXCLK] = "AUXCLK", + [RPRM_REGULATOR] = "REGULATOR", + [RPRM_GPIO] = "GPIO", + [RPRM_SDMA] = "SDMA", + [RPRM_IPU] = "IPU", + [RPRM_DSP] = "DSP", + [RPRM_I2C] = "I2C", +}; + +static const char *rname(u32 type) { + if (type >= RPRM_MAX) + return "(invalid)"; + return rnames[type]; +} + +struct rprm_elem { + struct list_head next; + u32 src; + u32 type; + u32 id; + void *handle; + u32 base; + struct rprm_constraints_data *constraints; + char res[]; +}; + +struct rprm { + struct list_head res_list; + struct idr conn_list; + struct idr id_list; + struct mutex lock; + struct dentry *dbg_dir; +}; + +struct rprm_auxclk_depot { + struct clk *aux_clk; + struct clk *src; +}; + +struct rprm_regulator_depot { + struct regulator *reg_p; + u32 orig_uv; +}; + +static struct rprm_constraints_data def_data = { + .frequency = 0, + .bandwidth = -1, + .latency = -1, +}; + +static int _get_rprm_size(u32 type) +{ + switch (type) { + case RPRM_GPTIMER: + return sizeof(struct rprm_gpt); + case RPRM_AUXCLK: + return sizeof(struct rprm_auxclk); + case RPRM_REGULATOR: + return sizeof(struct rprm_regulator); + case RPRM_GPIO: + return sizeof(struct rprm_gpio); + case RPRM_SDMA: + return sizeof(struct rprm_sdma); + case RPRM_I2C: + return sizeof(struct rprm_i2c); + } + return 0; +} + +static int rprm_gptimer_request(struct rprm_elem *e, struct rprm_gpt *obj) +{ + int ret; + struct omap_dm_timer *gpt; + + if (obj->id > GPTIMERS_MAX) { + pr_err("Invalid gptimer %u\n", obj->id); + return -EINVAL; + } + + gpt = omap_dm_timer_request_specific(obj->id); + if (!gpt) + return -EBUSY; + + ret = omap_dm_timer_set_source(gpt, obj->src_clk); + if (!ret) + e->handle = gpt; + else + omap_dm_timer_free(gpt); + + return ret; +} + +static void rprm_gptimer_release(struct omap_dm_timer *obj) +{ + omap_dm_timer_free(obj); +} + +static int rprm_auxclk_request(struct rprm_elem *e, struct rprm_auxclk *obj) +{ + int ret; + char clk_name[NAME_SIZE]; + char src_clk_name[NAME_SIZE]; + struct rprm_auxclk_depot *acd; + struct clk *src_parent; + + if ((obj->id < AUX_CLK_MIN) || (obj->id > AUX_CLK_MAX)) { + pr_err("Invalid aux_clk %d\n", obj->id); + return -EINVAL; + } + + /* Create auxclks depot */ + acd = kmalloc(sizeof(*acd), GFP_KERNEL); + if (!acd) + return -ENOMEM; + + sprintf(clk_name, "auxclk%d_ck", obj->id); + acd->aux_clk = clk_get(NULL, clk_name); + if (!acd->aux_clk) { + pr_err("%s: unable to get clock %s\n", __func__, clk_name); + ret = -EIO; + goto error; + } + + if (unlikely(acd->aux_clk->usecount)) + pr_warn("There are other users of %d clk\n", obj->id); + + sprintf(src_clk_name, "auxclk%d_src_ck", obj->id); + acd->src = clk_get(NULL, src_clk_name); + if (!acd->src) { + pr_err("%s: unable to get clock %s\n", __func__, src_clk_name); + ret = -EIO; + goto error_aux; + } + + src_parent = clk_get(NULL, clk_src_name[obj->parent_src_clk]); + if (!src_parent) { + pr_err("%s: unable to get parent clock %s\n", __func__, + clk_src_name[obj->parent_src_clk]); + ret = -EIO; + goto error_aux_src; + } + + ret = clk_set_rate(src_parent, (obj->parent_src_clk_rate * MHZ)); + if (ret) { + pr_err("%s: rate not supported by %s\n", __func__, + clk_src_name[obj->parent_src_clk]); + goto error_aux_src_parent; + } + + ret = clk_set_parent(acd->src, src_parent); + if (ret) { + pr_err("%s: unable to set clk %s as parent of aux_clk %s\n", + __func__, + clk_src_name[obj->parent_src_clk], + src_clk_name); + goto error_aux_src_parent; + } + + ret = clk_enable(acd->src); + if (ret) { + pr_err("%s: error enabling %s\n", __func__, src_clk_name); + goto error_aux_src_parent; + } + + ret = clk_set_rate(acd->aux_clk, (obj->clk_rate * MHZ)); + if (ret) { + pr_err("%s: rate not supported by %s\n", __func__, clk_name); + goto error_aux_enable; + } + + ret = clk_enable(acd->aux_clk); + if (ret) { + pr_err("%s: error enabling %s\n", __func__, clk_name); + goto error_aux_enable; + } + clk_put(src_parent); + + e->handle = acd; + + return 0; +error_aux_enable: + clk_disable(acd->src); +error_aux_src_parent: + clk_put(src_parent); +error_aux_src: + clk_put(acd->src); +error_aux: + clk_put(acd->aux_clk); +error: + kfree(acd); + + return ret; +} + +static void rprm_auxclk_release(struct rprm_auxclk_depot *obj) +{ + clk_disable((struct clk *)obj->aux_clk); + clk_put((struct clk *)obj->aux_clk); + clk_disable((struct clk *)obj->src); + clk_put((struct clk *)obj->src); + + kfree(obj); +} + +static +int rprm_regulator_request(struct rprm_elem *e, struct rprm_regulator *obj) +{ + int ret; + struct rprm_regulator_depot *rd; + char *reg_name; + + if (obj->id > REGULATOR_MAX) { + pr_err("Invalid regulator %d\n", obj->id); + return -EINVAL; + } + + /* Create regulator depot */ + rd = kmalloc(sizeof(*rd), GFP_KERNEL); + if (!rd) + return -ENOMEM; + + reg_name = regulator_name[obj->id - 1]; + rd->reg_p = regulator_get_exclusive(NULL, reg_name); + if (IS_ERR_OR_NULL(rd->reg_p)) { + pr_err("%s: error providing regulator %s\n", __func__, reg_name); + ret = -EINVAL; + goto error; + } + + rd->orig_uv = regulator_get_voltage(rd->reg_p); + + ret = regulator_set_voltage(rd->reg_p, obj->min_uv, obj->max_uv); + if (ret) { + pr_err("%s: error setting %s voltage\n", __func__, reg_name); + goto error_reg; + } + + ret = regulator_enable(rd->reg_p); + if (ret) { + pr_err("%s: error enabling %s ldo\n", __func__, reg_name); + goto error_reg; + } + + e->handle = rd; + + return 0; + +error_reg: + regulator_put(rd->reg_p); +error: + kfree(rd); + + return ret; +} + +static void rprm_regulator_release(struct rprm_regulator_depot *obj) +{ + int ret; + + ret = regulator_disable(obj->reg_p); + if (ret) { + pr_err("%s: error disabling ldo\n", __func__); + return; + } + + /* Restore orginal voltage */ + ret = regulator_set_voltage(obj->reg_p, obj->orig_uv, obj->orig_uv); + if (ret) { + pr_err("%s: error restoring voltage\n", __func__); + return; + } + + regulator_put(obj->reg_p); + kfree(obj); +} + +static int rprm_gpio_request(struct rprm_elem *e, struct rprm_gpio *obj) +{ + int ret; + struct rprm_gpio *gd; + + /* Create gpio depot */ + gd = kmalloc(sizeof(*gd), GFP_KERNEL); + if (!gd) + return -ENOMEM; + + ret = gpio_request(obj->id , "rpmsg_resmgr"); + if (ret) { + pr_err("%s: error providing gpio %d\n", __func__, obj->id); + return ret; + } + + e->handle = memcpy(gd, obj, sizeof(*obj)); + + return ret; +} + +static void rprm_gpio_release(struct rprm_gpio *obj) +{ + gpio_free(obj->id); + kfree(obj); +} + +static int rprm_sdma_request(struct rprm_elem *e, struct rprm_sdma *obj) +{ + int ret; + int sdma; + int i; + struct rprm_sdma *sd; + + /* Create sdma depot */ + sd = kmalloc(sizeof(*sd), GFP_KERNEL); + if (!sd) + return -ENOMEM; + + if (obj->num_chs > MAX_NUM_SDMA_CHANNELS) { + pr_err("Not able to provide %u channels\n", obj->num_chs); + return -EINVAL; + } + + for (i = 0; i < obj->num_chs; i++) { + ret = omap_request_dma(0, "rpmsg_resmgr", NULL, NULL, &sdma); + if (ret) { + pr_err("Error providing sdma channel %d\n", ret); + goto err; + } + obj->channels[i] = sdma; + pr_debug("Providing sdma ch %d\n", sdma); + } + + e->handle = memcpy(sd, obj, sizeof(*obj)); + + return 0; +err: + while (i--) + omap_free_dma(obj->channels[i]); + kfree(sd); + return ret; +} + +static void rprm_sdma_release(struct rprm_sdma *obj) +{ + int i = obj->num_chs; + + while (i--) { + omap_free_dma(obj->channels[i]); + pr_debug("Releasing sdma ch %d\n", obj->channels[i]); + } + kfree(obj); +} + +static int rprm_i2c_request(struct rprm_elem *e, struct rprm_i2c *obj) +{ + struct device *i2c_dev; + struct i2c_adapter *adapter; + char i2c_name[NAME_SIZE]; + int ret = -EINVAL; + + sprintf(i2c_name, "i2c%d", obj->id); + i2c_dev = omap_hwmod_name_get_dev(i2c_name); + if (IS_ERR_OR_NULL(i2c_dev)) { + pr_err("%s: unable to lookup %s\n", __func__, i2c_name); + return ret; + } + + adapter = i2c_get_adapter(obj->id); + if (!adapter) { + pr_err("%s: could not get i2c%d adapter\n", __func__, obj->id); + return -EINVAL; + } + i2c_detect_ext_master(adapter); + i2c_put_adapter(adapter); + + ret = pm_runtime_get_sync(i2c_dev); + /* + * pm_runtime_get_sync can return 1 in case it is already active, + * change it to 0 to indicate success. + */ + ret -= ret == 1; + if (!ret) + e->handle = i2c_dev; + else + dev_warn(i2c_dev, "%s: failed get sync %d\n", __func__, ret); + + return ret; +} + +static int rprm_i2c_release(struct device *i2c_dev) +{ + int ret = -EINVAL; + + if (IS_ERR_OR_NULL(i2c_dev)) { + pr_err("%s: invalid device passed\n", __func__); + return ret; + } + + ret = pm_runtime_put_sync(i2c_dev); + if (ret) + dev_warn(i2c_dev, "%s: failed put sync %d\n", __func__, ret); + + return ret; + +} + +static const char *_get_rpres_name(int type) +{ + switch (type) { + case RPRM_IVAHD: + return "rpres_iva"; + case RPRM_IVASEQ0: + return "rpres_iva_seq0"; + case RPRM_IVASEQ1: + return "rpres_iva_seq1"; + case RPRM_ISS: + return "rpres_iss"; + case RPRM_FDIF: + return "rpres_fdif"; + case RPRM_SL2IF: + return "rpres_sl2if"; + } + return ""; +} + +static int _rpres_set_constraints(struct rprm_elem *e, u32 type, long val) +{ + switch (type) { + case RPRM_SCALE: + return rpres_set_constraints(e->handle, + RPRES_CONSTRAINT_SCALE, + val); + case RPRM_LATENCY: + return rpres_set_constraints(e->handle, + RPRES_CONSTRAINT_LATENCY, + val); + case RPRM_BANDWIDTH: + return rpres_set_constraints(e->handle, + RPRES_CONSTRAINT_BANDWIDTH, + val); + } + pr_err("Invalid constraint\n"); + return -EINVAL; +} + +static int _rproc_set_constraints(struct rprm_elem *e, u32 type, long val) +{ + switch (type) { + case RPRM_SCALE: + return rproc_set_constraints(e->handle, + RPROC_CONSTRAINT_SCALE, + val); + case RPRM_LATENCY: + return rproc_set_constraints(e->handle, + RPROC_CONSTRAINT_LATENCY, + val); + case RPRM_BANDWIDTH: + return rproc_set_constraints(e->handle, + RPROC_CONSTRAINT_BANDWIDTH, + val); + } + pr_err("Invalid constraint\n"); + return -EINVAL; +} + +static +int _set_constraints(struct rprm_elem *e, struct rprm_constraints_data *c) +{ + int ret = -EINVAL; + u32 mask = 0; + int (*_set_constraints_func)(struct rprm_elem *, u32 type, long val); + + switch (e->type) { + case RPRM_IVAHD: + case RPRM_ISS: + case RPRM_FDIF: + _set_constraints_func = _rpres_set_constraints; + break; + case RPRM_IPU: + _set_constraints_func = _rproc_set_constraints; + break; + default: + return -EINVAL; + } + + if (c->mask & RPRM_SCALE) { + ret = _set_constraints_func(e, RPRM_SCALE, c->frequency); + if (ret) + goto err; + mask |= RPRM_SCALE; + e->constraints->frequency = c->frequency; + } + + if (c->mask & RPRM_LATENCY) { + ret = _set_constraints_func(e, RPRM_LATENCY, c->latency); + if (ret) + goto err; + mask |= RPRM_LATENCY; + e->constraints->latency = c->latency; + } + + if (c->mask & RPRM_BANDWIDTH) { + ret = _set_constraints_func(e, RPRM_BANDWIDTH, c->bandwidth); + if (ret) + goto err; + mask |= RPRM_BANDWIDTH; + e->constraints->bandwidth = c->bandwidth; + } +err: + c->mask = mask; + return ret; +} + +static int rprm_set_constraints(struct rprm *rprm, u32 addr, int res_id, + void *data, bool set) +{ + int ret = 0; + struct rprm_elem *e; + + mutex_lock(&rprm->lock); + if (!idr_find(&rprm->conn_list, addr)) { + ret = -ENOTCONN; + goto out; + } + + e = idr_find(&rprm->id_list, res_id); + if (!e || e->src != addr) { + ret = -ENOENT; + goto out; + } + + if (!e->constraints) { + pr_warn("No constraints\n"); + ret = -EINVAL; + goto out; + } + + if (set) { + ret = _set_constraints(e, data); + if (!ret) { + e->constraints->mask |= + ((struct rprm_constraints_data *)data)->mask; + goto out; + } + } + def_data.mask = ((struct rprm_constraints_data *)data)->mask; + if (def_data.mask) { + _set_constraints(e, &def_data); + e->constraints->mask &= + ~((struct rprm_constraints_data *)data)->mask; + } +out: + mutex_unlock(&rprm->lock); + return ret; +} + + +static int rprm_rpres_request(struct rprm_elem *e, int type) +{ + const char *res_name = _get_rpres_name(type); + struct rpres *res; + + e->constraints = kzalloc(sizeof(*(e->constraints)), GFP_KERNEL); + if (!(e->constraints)) + return -ENOMEM; + + res = rpres_get(res_name); + + if (IS_ERR(res)) { + pr_err("%s: error requesting %s\n", __func__, res_name); + kfree(e->constraints); + return PTR_ERR(res); + } + e->handle = res; + + return 0; +} + +static void rprm_rpres_release(struct rpres *res) +{ + rpres_put(res); +} + +static int rprm_rproc_request(struct rprm_elem *e, char *name) +{ + struct rproc *rp; + + e->constraints = kzalloc(sizeof(*(e->constraints)), GFP_KERNEL); + if (!(e->constraints)) + return -ENOMEM; + + rp = rproc_get(name); + if (IS_ERR(rp)) { + pr_debug("Error requesting %s\n", name); + kfree(e->constraints); + return PTR_ERR(rp); + } + e->handle = rp; + + return 0; +} + +static void rprm_rproc_release(struct rproc *rp) +{ + rproc_put(rp); +} + +static int _resource_free(struct rprm_elem *e) +{ + int ret = 0; + if (e->constraints && e->constraints->mask) { + def_data.mask = e->constraints->mask; + _set_constraints(e, &def_data); + } + kfree(e->constraints); + + switch (e->type) { + case RPRM_GPTIMER: + rprm_gptimer_release(e->handle); + break; + case RPRM_IVAHD: + case RPRM_IVASEQ0: + case RPRM_IVASEQ1: + case RPRM_ISS: + case RPRM_SL2IF: + case RPRM_FDIF: + rprm_rpres_release(e->handle); + break; + case RPRM_IPU: + case RPRM_DSP: + rprm_rproc_release(e->handle); + break; + case RPRM_AUXCLK: + rprm_auxclk_release(e->handle); + break; + case RPRM_I2C: + ret = rprm_i2c_release(e->handle); + break; + case RPRM_REGULATOR: + rprm_regulator_release(e->handle); + break; + case RPRM_GPIO: + rprm_gpio_release(e->handle); + break; + case RPRM_SDMA: + rprm_sdma_release(e->handle); + break; + case RPRM_L3BUS: + /* ignore silently */ + break; + default: + return -EINVAL; + } + + return ret; +} + +static int rprm_resource_free(struct rprm *rprm, u32 addr, int res_id) +{ + int ret = 0; + struct rprm_elem *e; + + mutex_lock(&rprm->lock); + if (!idr_find(&rprm->conn_list, addr)) { + ret = -ENOTCONN; + goto out; + } + + e = idr_find(&rprm->id_list, res_id); + if (!e || e->src != addr) { + ret = -ENOENT; + goto out; + } + idr_remove(&rprm->id_list, res_id); + list_del(&e->next); +out: + mutex_unlock(&rprm->lock); + + if (!ret) { + ret = _resource_free(e); + kfree(e); + } + + return ret; +} + +static int _resource_alloc(struct rprm_elem *e, int type, void *data) +{ + int ret = 0; + + switch (type) { + case RPRM_GPTIMER: + ret = rprm_gptimer_request(e, data); + break; + case RPRM_IVAHD: + case RPRM_IVASEQ0: + case RPRM_IVASEQ1: + case RPRM_ISS: + case RPRM_SL2IF: + case RPRM_FDIF: + ret = rprm_rpres_request(e, type); + break; + case RPRM_IPU: + ret = rprm_rproc_request(e, "ipu"); + break; + case RPRM_DSP: + ret = rprm_rproc_request(e, "dsp"); + break; + case RPRM_AUXCLK: + ret = rprm_auxclk_request(e, data); + break; + case RPRM_I2C: + ret = rprm_i2c_request(e, data); + break; + case RPRM_REGULATOR: + ret = rprm_regulator_request(e, data); + break; + case RPRM_GPIO: + ret = rprm_gpio_request(e, data); + break; + case RPRM_SDMA: + ret = rprm_sdma_request(e, data); + break; + case RPRM_L3BUS: + /* ignore silently; */ + break; + default: + pr_err("%s: invalid source %d!\n", __func__, type); + ret = -EINVAL; + } + + return ret; +} + +static int rprm_resource_alloc(struct rprm *rprm, u32 addr, int *res_id, + int type, void *data) +{ + struct rprm_elem *e; + int ret; + int rlen = _get_rprm_size(type); + + e = kzalloc(sizeof(*e) + rlen, GFP_KERNEL); + if (!e) + return -ENOMEM; + + ret = _resource_alloc(e, type, data); + if (ret) { + pr_err("%s: request for %d (%s) failed: %d\n", __func__, + type, rname(type), ret); + goto err_res_alloc; + } + + mutex_lock(&rprm->lock); + if (!idr_find(&rprm->conn_list, addr)) { + pr_err("%s: addr %d not connected!\n", __func__, addr); + ret = -ENOTCONN; + goto err; + } + /* + * Create a resource id to avoid sending kernel address to the + * remote processor. + */ + if (!idr_pre_get(&rprm->id_list, GFP_KERNEL)) { + ret = -ENOMEM; + goto err; + } + ret = idr_get_new(&rprm->id_list, e, res_id); + if (ret) + goto err; + + e->type = type; + e->src = addr; + e->id = *res_id; + memcpy(e->res, data, rlen); + list_add(&e->next, &rprm->res_list); + mutex_unlock(&rprm->lock); + + return 0; +err: + mutex_unlock(&rprm->lock); + _resource_free(e); +err_res_alloc: + kfree(e); + + return ret; +} + +static int rprm_disconnect_client(struct rprm *rprm, u32 addr) +{ + struct rprm_elem *e, *tmp; + int ret; + + mutex_lock(&rprm->lock); + if (!idr_find(&rprm->conn_list, addr)) { + ret = -ENOTCONN; + goto out; + } + list_for_each_entry_safe(e, tmp, &rprm->res_list, next) { + if (e->src == addr) { + _resource_free(e); + idr_remove(&rprm->id_list, e->id); + list_del(&e->next); + kfree(e); + } + } + + idr_remove(&rprm->conn_list, addr); +out: + mutex_unlock(&rprm->lock); + + return 0; +} + +static int rpmsg_connect_client(struct rprm *rprm, u32 addr) +{ + int ret; + int tid; + + mutex_lock(&rprm->lock); + if (idr_find(&rprm->conn_list, addr)) { + pr_err("Connection already opened\n"); + ret = -EISCONN; + goto out; + } + if (!idr_pre_get(&rprm->conn_list, GFP_KERNEL)) { + ret = -ENOMEM; + goto out; + } + ret = idr_get_new_above(&rprm->conn_list, &rprm->res_list, addr, &tid); + BUG_ON(addr != tid); +out: + mutex_unlock(&rprm->lock); + + return ret; +} + +static void rprm_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + int ret; + struct device *dev = &rpdev->dev; + struct rprm *rprm = dev_get_drvdata(dev); + struct rprm_request *req = data; + char ack_msg[MAX_MSG]; + struct rprm_ack *ack = (void *)ack_msg; + int r_sz = 0; + + if (len < sizeof(*req)) { + dev_err(dev, "Bad message\n"); + return; + } + + dev_dbg(dev, "resource type %d\n" + "request type %d\n" + "res_id %d", + req->res_type, req->acquire, req->res_id); + + switch (req->acquire) { + case RPRM_CONNECT: + ret = rpmsg_connect_client(rprm, src); + if (ret) + dev_err(dev, "connection failed! ret %d\n", ret); + break; + case RPRM_REQ_ALLOC: + r_sz = len - sizeof(*req); + if (r_sz != _get_rprm_size(req->res_type)) { + r_sz = 0; + ret = -EINVAL; + break; + } + ret = rprm_resource_alloc(rprm, src, &req->res_id, + req->res_type, req->data); + if (ret) + dev_err(dev, "resource allocation failed! ret %d\n", + ret); + break; + case RPRM_REQ_FREE: + ret = rprm_resource_free(rprm, src, req->res_id); + if (ret) + dev_err(dev, "resource release failed! ret %d\n", ret); + return; + case RPRM_DISCONNECT: + ret = rprm_disconnect_client(rprm, src); + if (ret) + dev_err(dev, "disconnection failed ret %d\n", ret); + return; + case RPRM_REQ_CONSTRAINTS: + r_sz = len - sizeof(*req); + if (r_sz != sizeof(struct rprm_constraints_data)) { + r_sz = 0; + ret = -EINVAL; + break; + } + ret = rprm_set_constraints(rprm, src, req->res_id, + req->data, true); + if (ret) + dev_err(dev, "set constraints failed! ret %d\n", ret); + break; + case RPRM_REL_CONSTRAINTS: + ret = rprm_set_constraints(rprm, src, req->res_id, + req->data, false); + if (ret) + dev_err(dev, "rel constraints failed! ret %d\n", ret); + return; + default: + dev_err(dev, "Unknow request\n"); + ret = -EINVAL; + } + + ack->ret = ret; + ack->res_type = req->res_type; + ack->res_id = req->res_id; + memcpy(ack->data, req->data, r_sz); + + ret = rpmsg_sendto(rpdev, ack, sizeof(*ack) + r_sz, src); + if (ret) + dev_err(dev, "rprm ack failed: %d\n", ret); +} + +static int _printf_gptimer_args(char *buf, struct rprm_gpt *obj) +{ + return sprintf(buf, + "Id:%d\n" + "Source:%d\n", + obj->id, obj->src_clk); +} + +static int _printf_auxclk_args(char *buf, struct rprm_auxclk *obj) +{ + return sprintf(buf, + "Id:%d\n" + "Rate:%2d\n" + "ParentSrc:%d\n" + "ParentSrcRate:%d\n", + obj->id, obj->clk_rate, obj->parent_src_clk, + obj->parent_src_clk_rate); +} + +static int _printf_regulator_args(char *buf, struct rprm_regulator *obj) +{ + return sprintf(buf, + "Id:%d\n" + "min_uV:%d\n" + "max_uV:%d\n", + obj->id, obj->min_uv, obj->max_uv); +} + +static int _printf_gpio_args(char *buf, struct rprm_gpio *obj) +{ + return sprintf(buf, "Id:%d\n", obj->id); +} + +static int _printf_i2c_args(char *buf, struct rprm_i2c *obj) +{ + return sprintf(buf, "Id:%d\n", obj->id); +} + +static int _printf_sdma_args(char *buf, struct rprm_sdma *obj) +{ + int i, ret = 0; + ret += sprintf(buf, "NumChannels:%d\n", obj->num_chs); + for (i = 0 ; i < obj->num_chs; i++) + ret += sprintf(buf + ret, "Channel[%d]:%d\n", i, + obj->channels[i]); + return ret; +} + +static int _print_res_args(char *buf, struct rprm_elem *e) +{ + void *res = (void *)e->res; + + switch (e->type) { + case RPRM_GPTIMER: + return _printf_gptimer_args(buf, res); + case RPRM_AUXCLK: + return _printf_auxclk_args(buf, res); + case RPRM_I2C: + return _printf_i2c_args(buf, res); + case RPRM_REGULATOR: + return _printf_regulator_args(buf, res); + case RPRM_GPIO: + return _printf_gpio_args(buf, res); + case RPRM_SDMA: + return _printf_sdma_args(buf, res); + } + return 0; +} + +static int _printf_constraints_args(char *buf, struct rprm_elem *e) +{ + return sprintf(buf, + "Mask:0x%x\n" + "Frequency:%ld\n" + "Latency:%ld\n" + "Bandwidth:%ld\n", + e->constraints->mask, e->constraints->frequency, + e->constraints->latency, e->constraints->bandwidth); +} + +static ssize_t rprm_dbg_read(struct file *filp, char __user *userbuf, + size_t count, loff_t *ppos) +{ + struct rprm *rprm = filp->private_data; + struct rprm_elem *e; + char res[512]; + int total = 0, c, tmp; + loff_t p = 0, pt; + + list_for_each_entry(e, &rprm->res_list, next) { + c = sprintf(res, + "\nResource Name:%s\n" + "Source address:%d\n", + rnames[e->type], e->src); + + if (_get_rprm_size(e->type)) + c += _print_res_args(res + c, e); + + if (e->constraints && e->constraints->mask) + c += _printf_constraints_args(res + c, e); + + p += c; + if (*ppos >= p) + continue; + pt = c - p + *ppos; + tmp = simple_read_from_buffer(userbuf + total, count, &pt, + res, c); + total += tmp; + *ppos += tmp; + if (tmp - c) + break; + } + + return total; +} + +static int rprm_dbg_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + return 0; +} + +static const struct file_operations rprm_dbg_ops = { + .read = rprm_dbg_read, + .open = rprm_dbg_open, + .llseek = generic_file_llseek, +}; + +static int rprm_probe(struct rpmsg_channel *rpdev) +{ + struct rprm *rprm; + + rprm = kmalloc(sizeof(*rprm), GFP_KERNEL); + if (!rprm) + return -ENOMEM; + + mutex_init(&rprm->lock); + INIT_LIST_HEAD(&rprm->res_list); + idr_init(&rprm->conn_list); + idr_init(&rprm->id_list); + dev_set_drvdata(&rpdev->dev, rprm); + + rprm->dbg_dir = debugfs_create_dir(dev_name(&rpdev->dev), rprm_dbg); + if (!rprm->dbg_dir) + dev_err(&rpdev->dev, "can't create debugfs dir\n"); + + debugfs_create_file("resources", 0400, rprm->dbg_dir, rprm, + &rprm_dbg_ops); + + return 0; +} + +static void __devexit rprm_remove(struct rpmsg_channel *rpdev) +{ + struct rprm *rprm = dev_get_drvdata(&rpdev->dev); + struct rprm_elem *e, *tmp; + + dev_info(&rpdev->dev, "Enter %s\n", __func__); + + if (rprm->dbg_dir) + debugfs_remove_recursive(rprm->dbg_dir); + + mutex_lock(&rprm->lock); + + /* clean up remaining resources */ + list_for_each_entry_safe(e, tmp, &rprm->res_list, next) { + _resource_free(e); + list_del(&e->next); + kfree(e); + } + idr_remove_all(&rprm->id_list); + idr_destroy(&rprm->id_list); + idr_remove_all(&rprm->conn_list); + idr_destroy(&rprm->conn_list); + + mutex_unlock(&rprm->lock); + + kfree(rprm); +} + +static struct rpmsg_device_id rprm_id_table[] = { + { + .name = "rpmsg-resmgr", + }, + { }, +}; +MODULE_DEVICE_TABLE(platform, rprm_id_table); + +static struct rpmsg_driver rprm_driver = { + .drv.name = KBUILD_MODNAME, + .drv.owner = THIS_MODULE, + .id_table = rprm_id_table, + .probe = rprm_probe, + .callback = rprm_cb, + .remove = __devexit_p(rprm_remove), +}; + +static int __init init(void) +{ + int r; + + if (debugfs_initialized()) { + rprm_dbg = debugfs_create_dir(KBUILD_MODNAME, NULL); + if (!rprm_dbg) + pr_err("Error creating rprm debug directory\n"); + } + r = register_rpmsg_driver(&rprm_driver); + if (r && rprm_dbg) + debugfs_remove_recursive(rprm_dbg); + + return r; +} + +static void __exit fini(void) +{ + if (rprm_dbg) + debugfs_remove_recursive(rprm_dbg); + unregister_rpmsg_driver(&rprm_driver); +} +module_init(init); +module_exit(fini); + +MODULE_DESCRIPTION("Remote Processor Resource Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rpmsg/rpmsg_server_sample.c b/drivers/rpmsg/rpmsg_server_sample.c new file mode 100644 index 0000000..67da012 --- /dev/null +++ b/drivers/rpmsg/rpmsg_server_sample.c @@ -0,0 +1,99 @@ +/* + * Remote processor messaging transport - sample server driver + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/virtio.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/rpmsg.h> + +#define MSG ("hello world!") +#define MSG_LIMIT 100 + +static void rpmsg_sample_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + int err; + static int rx_count; + + dev_info(&rpdev->dev, "incoming msg %d (src: 0x%x)\n", ++rx_count, src); + + print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, + data, len, true); + + /* samples should not live forever */ + if (rx_count >= MSG_LIMIT) { + dev_info(&rpdev->dev, "goodbye!\n"); + return; + } + + /* reply */ + err = rpmsg_sendto(rpdev, MSG, strlen(MSG), src); + if (err) + pr_err("rpmsg_send failed: %d\n", err); +} + +static int rpmsg_sample_probe(struct rpmsg_channel *rpdev) +{ + int err; + + dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", + rpdev->src, rpdev->dst); + + err = rpmsg_sendto(rpdev, MSG, strlen(MSG), 50); + if (err) { + pr_err("rpmsg_send failed: %d\n", err); + return err; + } + + return 0; +} + +static void __devexit rpmsg_sample_remove(struct rpmsg_channel *rpdev) +{ + dev_info(&rpdev->dev, "rpmsg sample driver is removed\n"); +} + +static struct rpmsg_device_id rpmsg_driver_sample_id_table[] = { + { .name = "rpmsg-server-sample" }, + { }, +}; +MODULE_DEVICE_TABLE(platform, rpmsg_driver_sample_id_table); + +static struct rpmsg_driver rpmsg_sample_server_driver = { + .drv.name = KBUILD_MODNAME, + .drv.owner = THIS_MODULE, + .id_table = rpmsg_driver_sample_id_table, + .probe = rpmsg_sample_probe, + .callback = rpmsg_sample_cb, + .remove = __devexit_p(rpmsg_sample_remove), +}; + +static int __init init(void) +{ + return register_rpmsg_driver(&rpmsg_sample_server_driver); +} + +static void __exit fini(void) +{ + unregister_rpmsg_driver(&rpmsg_sample_server_driver); +} +module_init(init); +module_exit(fini); + +MODULE_DESCRIPTION("Virtio remote processor messaging sample driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/rpmsg/virtio_rpmsg_bus.c b/drivers/rpmsg/virtio_rpmsg_bus.c new file mode 100644 index 0000000..247e887 --- /dev/null +++ b/drivers/rpmsg/virtio_rpmsg_bus.c @@ -0,0 +1,818 @@ +/* + * Virtio-based remote processor messaging bus + * + * Copyright (C) 2011 Texas Instruments, Inc. + * Copyright (C) 2011 Google, Inc. + * + * Ohad Ben-Cohen <ohad@wizery.com> + * Brian Swetland <swetland@google.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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/virtio.h> +#include <linux/virtio_ids.h> +#include <linux/virtio_config.h> +#include <linux/scatterlist.h> +#include <linux/slab.h> +#include <linux/idr.h> +#include <linux/jiffies.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/rpmsg.h> + +/** + * struct virtproc_info - virtual remote processor info + * + * @vdev: the virtio device + * @rvq: rx virtqueue (from pov of local processor) + * @svq: tx virtqueue (from pov of local processor) + * @rbufs: address of rx buffers + * @sbufs: address of tx buffers + * @last_rbuf: index of last rx buffer used + * @last_sbuf: index of last tx buffer used + * @sim_base: simulated base addr base to make virtio's virt_to_page happy + * @svq_lock: protects the tx virtqueue, to allow several concurrent senders + * @num_bufs: total number of buffers allocated for communicating with this + * virtual remote processor. half is used for rx and half for tx. + * @buf_size: size of buffers allocated for communications + * @endpoints: the set of local endpoints + * @endpoints_lock: lock of the endpoints set + * @sendq: wait queue of sending contexts waiting for free rpmsg buffer + * @ns_ept: the bus's name service endpoint + * + * This structure stores the rpmsg state of a given virtio remote processor + * device (there might be several virtio rproc devices for each physical + * remote processor). + */ +struct virtproc_info { + struct virtio_device *vdev; + struct virtqueue *rvq, *svq; + void *rbufs, *sbufs; + int last_rbuf, last_sbuf; + void *sim_base; + struct mutex svq_lock; + int num_bufs; + int buf_size; + struct idr endpoints; + spinlock_t endpoints_lock; + wait_queue_head_t sendq; + struct rpmsg_endpoint *ns_ept; +}; + +#define to_rpmsg_channel(d) container_of(d, struct rpmsg_channel, dev) +#define to_rpmsg_driver(d) container_of(d, struct rpmsg_driver, drv) + +/* + * Local addresses are dynamically allocated on-demand. + * We do not dynamically assign addresses from the low 1024 range, + * in order to reserve that address range for predefined services. + */ +#define RPMSG_RESERVED_ADDRESSES (1024) + +/* Address 53 is reserved for advertising remote services */ +#define RPMSG_NS_ADDR (53) + +/* show configuration fields */ +#define rpmsg_show_attr(field, path, format_string) \ +static ssize_t \ +field##_show(struct device *dev, \ + struct device_attribute *attr, char *buf) \ +{ \ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); \ + \ + return sprintf(buf, format_string, rpdev->path); \ +} + +rpmsg_show_attr(name, id.name, "%s\n"); +rpmsg_show_attr(dst, dst, "0x%x\n"); +rpmsg_show_attr(src, src, "0x%x\n"); +rpmsg_show_attr(announce, announce ? "true" : "false", "%s\n"); + +/* unique (free running) numbering for rpmsg devices */ +static unsigned int rpmsg_dev_index; + +static ssize_t modalias_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + return sprintf(buf, RPMSG_DEVICE_MODALIAS_FMT "\n", rpdev->id.name); +} + +static struct device_attribute rpmsg_dev_attrs[] = { + __ATTR_RO(name), + __ATTR_RO(modalias), + __ATTR_RO(dst), + __ATTR_RO(src), + __ATTR_RO(announce), + __ATTR_NULL +}; + +static inline int rpmsg_id_match(const struct rpmsg_channel *rpdev, + const struct rpmsg_device_id *id) +{ + if (strncmp(id->name, rpdev->id.name, RPMSG_NAME_SIZE)) + return 0; + + return 1; +} + +/* match rpmsg channel and rpmsg driver */ +static int rpmsg_dev_match(struct device *dev, struct device_driver *drv) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + struct rpmsg_driver *rpdrv = to_rpmsg_driver(drv); + const struct rpmsg_device_id *ids = rpdrv->id_table; + unsigned int i; + + for (i = 0; ids[i].name[0]; i++) { + if (rpmsg_id_match(rpdev, &ids[i])) + return 1; + } + + return 0; +} + +static int rpmsg_uevent(struct device *dev, struct kobj_uevent_env *env) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + return add_uevent_var(env, "MODALIAS=" RPMSG_DEVICE_MODALIAS_FMT, + rpdev->id.name); +} + +static struct rpmsg_endpoint *__rpmsg_create_ept(struct virtproc_info *vrp, + struct rpmsg_channel *rpdev, + void (*cb)(struct rpmsg_channel *, void *, int, void *, u32), + void *priv, u32 addr) +{ + int err, tmpaddr, request; + struct rpmsg_endpoint *ept; + struct device *dev = rpdev ? &rpdev->dev : &vrp->vdev->dev; + + if (!idr_pre_get(&vrp->endpoints, GFP_KERNEL)) + return NULL; + + ept = kzalloc(sizeof(*ept), GFP_KERNEL); + if (!ept) { + dev_err(dev, "failed to kzalloc a new ept\n"); + return NULL; + } + + ept->rpdev = rpdev; + ept->cb = cb; + ept->priv = priv; + + /* do we need to allocate a local address ? */ + request = addr == RPMSG_ADDR_ANY ? RPMSG_RESERVED_ADDRESSES : addr; + + spin_lock(&vrp->endpoints_lock); + + /* bind the endpoint to an rpmsg address (and allocate one if needed) */ + err = idr_get_new_above(&vrp->endpoints, ept, request, &tmpaddr); + if (err) { + dev_err(dev, "idr_get_new_above failed: %d\n", err); + goto free_ept; + } + + if (addr != RPMSG_ADDR_ANY && tmpaddr != addr) { + dev_err(dev, "address 0x%x already in use\n", addr); + goto rem_idr; + } + + ept->addr = tmpaddr; + + spin_unlock(&vrp->endpoints_lock); + + return ept; + +rem_idr: + idr_remove(&vrp->endpoints, request); +free_ept: + spin_unlock(&vrp->endpoints_lock); + kfree(ept); + return NULL; +} + +struct rpmsg_endpoint *rpmsg_create_ept(struct rpmsg_channel *rpdev, + void (*cb)(struct rpmsg_channel *, void *, int, void *, u32), + void *priv, u32 addr) +{ + return __rpmsg_create_ept(rpdev->vrp, rpdev, cb, priv, addr); +} +EXPORT_SYMBOL(rpmsg_create_ept); + +void rpmsg_destroy_ept(struct rpmsg_endpoint *ept) +{ + struct virtproc_info *vrp = ept->rpdev->vrp; + + spin_lock(&vrp->endpoints_lock); + idr_remove(&vrp->endpoints, ept->addr); + spin_unlock(&vrp->endpoints_lock); + + kfree(ept); +} +EXPORT_SYMBOL(rpmsg_destroy_ept); + +static int rpmsg_dev_probe(struct device *dev) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver); + struct virtproc_info *vrp = rpdev->vrp; + struct rpmsg_endpoint *ept; + int err; + + ept = rpmsg_create_ept(rpdev, rpdrv->callback, NULL, rpdev->src); + if (!ept) { + dev_err(dev, "failed to create endpoint\n"); + err = -ENOMEM; + goto out; + } + + rpdev->ept = ept; + rpdev->src = ept->addr; + + err = rpdrv->probe(rpdev); + if (err) { + dev_err(dev, "%s: failed: %d\n", __func__, err); + rpmsg_destroy_ept(ept); + goto out; + } + + /* need to tell remote processor's name service about this channel ? */ + if (rpdev->announce && + virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) { + struct rpmsg_ns_msg nsm; + + strncpy(nsm.name, rpdev->id.name, RPMSG_NAME_SIZE); + nsm.addr = rpdev->src; + nsm.flags = RPMSG_NS_CREATE; + + err = rpmsg_sendto(rpdev, &nsm, sizeof(nsm), RPMSG_NS_ADDR); + if (err) + dev_err(dev, "failed to announce service %d\n", err); + } + +out: + return err; +} + +static int rpmsg_dev_remove(struct device *dev) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + struct rpmsg_driver *rpdrv = to_rpmsg_driver(rpdev->dev.driver); + struct virtproc_info *vrp = rpdev->vrp; + int err = 0; + + /* tell remote processor's name service we're removing this channel */ + if (rpdev->announce && + virtio_has_feature(vrp->vdev, VIRTIO_RPMSG_F_NS)) { + struct rpmsg_ns_msg nsm; + + strncpy(nsm.name, rpdev->id.name, RPMSG_NAME_SIZE); + nsm.addr = rpdev->src; + nsm.flags = RPMSG_NS_DESTROY; + + err = rpmsg_sendto(rpdev, &nsm, sizeof(nsm), RPMSG_NS_ADDR); + if (err) + dev_err(dev, "failed to announce service %d\n", err); + } + + rpdrv->remove(rpdev); + + rpmsg_destroy_ept(rpdev->ept); + + return err; +} + +static struct bus_type rpmsg_bus = { + .name = "rpmsg", + .match = rpmsg_dev_match, + .dev_attrs = rpmsg_dev_attrs, + .uevent = rpmsg_uevent, + .probe = rpmsg_dev_probe, + .remove = rpmsg_dev_remove, +}; + +int register_rpmsg_driver(struct rpmsg_driver *rpdrv) +{ + rpdrv->drv.bus = &rpmsg_bus; + return driver_register(&rpdrv->drv); +} +EXPORT_SYMBOL(register_rpmsg_driver); + +void unregister_rpmsg_driver(struct rpmsg_driver *rpdrv) +{ + driver_unregister(&rpdrv->drv); +} +EXPORT_SYMBOL(unregister_rpmsg_driver); + +static void rpmsg_release_device(struct device *dev) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + kfree(rpdev); +} + +/* match an rpmsg channel with channel info structs */ +static int rpmsg_channel_match(struct device *dev, void *data) +{ + struct rpmsg_channel_info *chinfo = data; + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + if (chinfo->src != RPMSG_ADDR_ANY && chinfo->src != rpdev->src) + return 0; + + if (chinfo->dst != RPMSG_ADDR_ANY && chinfo->dst != rpdev->dst) + return 0; + + if (strncmp(chinfo->name, rpdev->id.name, RPMSG_NAME_SIZE)) + return 0; + + return 1; +} + +static struct rpmsg_channel *rpmsg_create_channel(struct virtproc_info *vrp, + struct rpmsg_channel_info *chinfo) +{ + struct rpmsg_channel *rpdev; + struct device *tmp, *dev = &vrp->vdev->dev; + int ret; + + /* make sure a similar channel doesn't already exist */ + tmp = device_find_child(dev, chinfo, rpmsg_channel_match); + if (tmp) { + dev_err(dev, "channel %s:%x:%x already exist\n", + chinfo->name, chinfo->src, chinfo->dst); + return NULL; + } + + rpdev = kzalloc(sizeof(struct rpmsg_channel), GFP_KERNEL); + if (!rpdev) { + pr_err("kzalloc failed\n"); + return NULL; + } + + rpdev->vrp = vrp; + rpdev->src = chinfo->src; + rpdev->dst = chinfo->dst; + + /* + * rpmsg server channels has predefined local address, and their + * existence needs to be announced remotely + */ + rpdev->announce = rpdev->src != RPMSG_ADDR_ANY ? true : false; + + strncpy(rpdev->id.name, chinfo->name, RPMSG_NAME_SIZE); + + /* very simple device indexing plumbing which just works (for now) */ + dev_set_name(&rpdev->dev, "rpmsg%d", rpmsg_dev_index++); + + rpdev->dev.parent = &vrp->vdev->dev; + rpdev->dev.bus = &rpmsg_bus; + rpdev->dev.release = rpmsg_release_device; + + ret = device_register(&rpdev->dev); + if (ret) { + dev_err(dev, "device_register failed: %d\n", ret); + kfree(rpdev); + return NULL; + } + + return rpdev; +} + +static void rpmsg_destroy_channel(struct rpmsg_channel *rpdev) +{ + device_unregister(&rpdev->dev); +} + +static int rpmsg_destroy_channel_by_info(struct virtproc_info *vrp, + struct rpmsg_channel_info *chinfo) +{ + struct virtio_device *vdev = vrp->vdev; + struct device *dev; + + dev = device_find_child(&vdev->dev, chinfo, rpmsg_channel_match); + if (!dev) + return -EINVAL; + + rpmsg_destroy_channel(to_rpmsg_channel(dev)); + + return 0; +} + +/* minimal buf "allocator" that is just enough for now */ +static void *get_a_buf(struct virtproc_info *vrp) +{ + unsigned int len; + void *buf = NULL; + + /* make sure the descriptors are updated before reading */ + rmb(); + /* either pick the next unused buffer */ + if (vrp->last_sbuf < vrp->num_bufs / 2) + buf = vrp->sbufs + vrp->buf_size * vrp->last_sbuf++; + /* or recycle a used one */ + else + buf = virtqueue_get_buf(vrp->svq, &len); + + return buf; +} + +/* XXX: the blocking 'wait' mechanism hasn't been tested yet */ +int rpmsg_send_offchannel_raw(struct rpmsg_channel *rpdev, u32 src, u32 dst, + void *data, int len, bool wait) +{ + struct virtproc_info *vrp = rpdev->vrp; + struct device *dev = &rpdev->dev; + struct scatterlist sg; + struct rpmsg_hdr *msg; + int err; + unsigned long offset; + void *sim_addr; + + if (src == RPMSG_ADDR_ANY || dst == RPMSG_ADDR_ANY) { + dev_err(dev, "invalid addr (src 0x%x, dst 0x%x)\n", src, dst); + return -EINVAL; + } + + /* the payload's size is currently limited */ + if (len > vrp->buf_size - sizeof(struct rpmsg_hdr)) { + dev_err(dev, "message is too big (%d)\n", len); + return -EMSGSIZE; + } + + /* + * protect svq from simultaneous concurrent manipulations, + * and serialize the sending of messages + */ + if (mutex_lock_interruptible(&vrp->svq_lock)) + return -ERESTARTSYS; + /* grab a buffer */ + msg = get_a_buf(vrp); + if (!msg && !wait) { + err = -ENOMEM; + goto out; + } + + /* no free buffer ? wait for one (but bail after 15 seconds) */ + if (!msg) { + /* enable "tx-complete" interrupts before dozing off */ + virtqueue_enable_cb(vrp->svq); + + /* + * sleep until a free buffer is available or 15 secs elapse. + * the timeout period is not configurable because frankly + * i don't see why drivers need to deal with that. + * if later this happens to be required, it'd be easy to add. + */ + err = wait_event_interruptible_timeout(vrp->sendq, + (msg = get_a_buf(vrp)), + msecs_to_jiffies(15000)); + + /* on success, suppress "tx-complete" interrupts again */ + virtqueue_disable_cb(vrp->svq); + + if (err < 0) { + err = -ERESTARTSYS; + goto out; + } + + if (!msg) { + dev_err(dev, "timeout waiting for buffer\n"); + err = -ETIMEDOUT; + goto out; + } + } + + msg->len = len; + msg->flags = 0; + msg->src = src; + msg->dst = dst; + msg->unused = 0; + memcpy(msg->data, data, len); + + dev_dbg(dev, "TX From 0x%x, To 0x%x, Len %d, Flags %d, Unused %d\n", + msg->src, msg->dst, msg->len, + msg->flags, msg->unused); + print_hex_dump(KERN_DEBUG, "rpmsg_virtio TX: ", DUMP_PREFIX_NONE, 16, 1, + msg, sizeof(*msg) + msg->len, true); + + offset = ((unsigned long) msg) - ((unsigned long) vrp->rbufs); + sim_addr = vrp->sim_base + offset; + sg_init_one(&sg, sim_addr, sizeof(*msg) + len); + + /* add message to the remote processor's virtqueue */ + err = virtqueue_add_buf_gfp(vrp->svq, &sg, 1, 0, msg, GFP_KERNEL); + if (err < 0) { + dev_err(dev, "virtqueue_add_buf_gfp failed: %d\n", err); + goto out; + } + /* descriptors must be written before kicking remote processor */ + wmb(); + + /* tell the remote processor it has a pending message to read */ + virtqueue_kick(vrp->svq); + + err = 0; +out: + mutex_unlock(&vrp->svq_lock); + return err; +} +EXPORT_SYMBOL(rpmsg_send_offchannel_raw); + +static void rpmsg_recv_done(struct virtqueue *rvq) +{ + struct rpmsg_hdr *msg; + unsigned int len; + struct rpmsg_endpoint *ept; + struct scatterlist sg; + unsigned long offset; + void *sim_addr; + struct virtproc_info *vrp = rvq->vdev->priv; + struct device *dev = &rvq->vdev->dev; + int err; + + /* make sure the descriptors are updated before reading */ + rmb(); + msg = virtqueue_get_buf(rvq, &len); + if (!msg) { + dev_err(dev, "uhm, incoming signal, but no used buffer ?\n"); + return; + } + + dev_dbg(dev, "From: 0x%x, To: 0x%x, Len: %d, Flags: %d, Unused: %d\n", + msg->src, msg->dst, msg->len, + msg->flags, msg->unused); + print_hex_dump(KERN_DEBUG, "rpmsg_virtio RX: ", DUMP_PREFIX_NONE, 16, 1, + msg, sizeof(*msg) + msg->len, true); + + /* fetch the callback of the appropriate user */ + spin_lock(&vrp->endpoints_lock); + ept = idr_find(&vrp->endpoints, msg->dst); + spin_unlock(&vrp->endpoints_lock); + + if (ept && ept->cb) + ept->cb(ept->rpdev, msg->data, msg->len, ept->priv, msg->src); + else + dev_warn(dev, "msg received with no recepient\n"); + + /* add the buffer back to the remote processor's virtqueue */ + offset = ((unsigned long) msg) - ((unsigned long) vrp->rbufs); + sim_addr = vrp->sim_base + offset; + sg_init_one(&sg, sim_addr, sizeof(*msg) + len); + + err = virtqueue_add_buf_gfp(vrp->rvq, &sg, 0, 1, msg, GFP_KERNEL); + if (err < 0) { + dev_err(dev, "failed to add a virtqueue buffer: %d\n", err); + return; + } + /* descriptors must be written before kicking remote processor */ + wmb(); + + /* tell the remote processor we added another available rx buffer */ + virtqueue_kick(vrp->rvq); +} + +static void rpmsg_xmit_done(struct virtqueue *svq) +{ + struct virtproc_info *vrp = svq->vdev->priv; + + dev_dbg(&svq->vdev->dev, "%s\n", __func__); + + /* wake up potential processes that are waiting for a buffer */ + wake_up_interruptible(&vrp->sendq); +} + +static void rpmsg_ns_cb(struct rpmsg_channel *rpdev, void *data, int len, + void *priv, u32 src) +{ + struct rpmsg_ns_msg *msg = data; + struct rpmsg_channel *newch; + struct rpmsg_channel_info chinfo; + struct virtproc_info *vrp = priv; + struct device *dev = &vrp->vdev->dev; + int ret; + +#if 0 + print_hex_dump(KERN_DEBUG, __func__, DUMP_PREFIX_NONE, 16, 1, + data, len, true); +#endif + + if (len != sizeof(*msg)) { + dev_err(dev, "malformed ns msg (%d)\n", len); + return; + } + + /* + * the name service ept does _not_ belong to a real rpmsg channel, + * and is handled by the rpmsg bus itself. + * for sanity reasons, make sure a valid rpdev has _not_ sneaked + * in somehow. + */ + if (rpdev) { + dev_err(dev, "anomaly: ns ept has an rpdev handle\n"); + return; + } + + /* don't trust the remote processor for null terminating the name */ + msg->name[RPMSG_NAME_SIZE - 1] = '\0'; + + dev_info(dev, "%sing channel %s addr 0x%x\n", + msg->flags & RPMSG_NS_DESTROY ? "destroy" : "creat", + msg->name, msg->addr); + + strncpy(chinfo.name, msg->name, sizeof(chinfo.name)); + chinfo.src = RPMSG_ADDR_ANY; + chinfo.dst = msg->addr; + + if (msg->flags & RPMSG_NS_DESTROY) { + ret = rpmsg_destroy_channel_by_info(vrp, &chinfo); + if (ret) + dev_err(dev, "rpmsg_destroy_channel failed: %d\n", ret); + } else { + newch = rpmsg_create_channel(vrp, &chinfo); + if (!newch) + dev_err(dev, "rpmsg_create_channel failed\n"); + } +} + +static int rpmsg_probe(struct virtio_device *vdev) +{ + vq_callback_t *vq_cbs[] = { rpmsg_recv_done, rpmsg_xmit_done }; + const char *names[] = { "input", "output" }; + struct virtqueue *vqs[2]; + struct virtproc_info *vrp; + void *addr; + int err, i, num_bufs, buf_size, total_buf_size; + struct rpmsg_channel_info *ch; + + vrp = kzalloc(sizeof(*vrp), GFP_KERNEL); + if (!vrp) + return -ENOMEM; + + vrp->vdev = vdev; + + idr_init(&vrp->endpoints); + spin_lock_init(&vrp->endpoints_lock); + mutex_init(&vrp->svq_lock); + init_waitqueue_head(&vrp->sendq); + + /* We expect two virtqueues, rx and tx (in this order) */ + err = vdev->config->find_vqs(vdev, 2, vqs, vq_cbs, names); + if (err) + goto free_vi; + + vrp->rvq = vqs[0]; + vrp->svq = vqs[1]; + + /* Platform must supply pre-allocated uncached buffers for now */ + vdev->config->get(vdev, VPROC_BUF_ADDR, &addr, sizeof(addr)); + vdev->config->get(vdev, VPROC_BUF_NUM, &num_bufs, + sizeof(num_bufs)); + vdev->config->get(vdev, VPROC_BUF_SZ, &buf_size, sizeof(buf_size)); + + total_buf_size = num_bufs * buf_size; + + dev_dbg(&vdev->dev, "%d buffers, size %d, addr 0x%x, total 0x%x\n", + num_bufs, buf_size, (unsigned int) addr, total_buf_size); + + vrp->num_bufs = num_bufs; + vrp->buf_size = buf_size; + vrp->rbufs = addr; + vrp->sbufs = addr + total_buf_size / 2; + + /* simulated addr base to make virt_to_page happy */ + vdev->config->get(vdev, VPROC_SIM_BASE, &vrp->sim_base, + sizeof(vrp->sim_base)); + + /* set up the receive buffers */ + for (i = 0; i < num_bufs / 2; i++) { + struct scatterlist sg; + void *tmpaddr = vrp->rbufs + i * buf_size; + void *simaddr = vrp->sim_base + i * buf_size; + + sg_init_one(&sg, simaddr, buf_size); + err = virtqueue_add_buf_gfp(vrp->rvq, &sg, 0, 1, tmpaddr, + GFP_KERNEL); + WARN_ON(err < 0); /* sanity check; this can't really happen */ + } + + /* tell the remote processor it can start sending data */ + virtqueue_kick(vrp->rvq); + + /* suppress "tx-complete" interrupts */ + virtqueue_disable_cb(vrp->svq); + + vdev->priv = vrp; + + dev_info(&vdev->dev, "rpmsg backend virtproc probed successfully\n"); + + /* if supported by the remote processor, enable the name service */ + if (virtio_has_feature(vdev, VIRTIO_RPMSG_F_NS)) { + vrp->ns_ept = __rpmsg_create_ept(vrp, NULL, rpmsg_ns_cb, + vrp, RPMSG_NS_ADDR); + if (!vrp->ns_ept) { + dev_err(&vdev->dev, "failed to create the ns ept\n"); + err = -ENOMEM; + goto vqs_del; + } + } + + /* look for platform-specific static channels */ + vdev->config->get(vdev, VPROC_STATIC_CHANNELS, &ch, sizeof(ch)); + + for (i = 0; ch && ch[i].name[0]; i++) + rpmsg_create_channel(vrp, &ch[i]); + + return 0; + +vqs_del: + vdev->config->del_vqs(vrp->vdev); +free_vi: + kfree(vrp); + return err; +} + +static int rpmsg_remove_device(struct device *dev, void *data) +{ + struct rpmsg_channel *rpdev = to_rpmsg_channel(dev); + + rpmsg_destroy_channel(rpdev); + + return 0; +} + +static void __devexit rpmsg_remove(struct virtio_device *vdev) +{ + struct virtproc_info *vrp = vdev->priv; + int ret; + + ret = device_for_each_child(&vdev->dev, NULL, rpmsg_remove_device); + if (ret) + dev_warn(&vdev->dev, "can't remove rpmsg device: %d\n", ret); + + idr_remove_all(&vrp->endpoints); + idr_destroy(&vrp->endpoints); + + vdev->config->del_vqs(vrp->vdev); + + kfree(vrp); +} + +static struct virtio_device_id id_table[] = { + { VIRTIO_ID_RPMSG, VIRTIO_DEV_ANY_ID }, + { 0 }, +}; + +static unsigned int features[] = { + VIRTIO_RPMSG_F_NS, +}; + +static struct virtio_driver virtio_ipc_driver = { + .feature_table = features, + .feature_table_size = ARRAY_SIZE(features), + .driver.name = KBUILD_MODNAME, + .driver.owner = THIS_MODULE, + .id_table = id_table, + .probe = rpmsg_probe, + .remove = __devexit_p(rpmsg_remove), +}; + +static int __init init(void) +{ + int ret; + + ret = bus_register(&rpmsg_bus); + if (ret) { + pr_err("failed to register rpmsg bus: %d\n", ret); + return ret; + } + + return register_virtio_driver(&virtio_ipc_driver); +} +module_init(init); + +static void __exit fini(void) +{ + unregister_virtio_driver(&virtio_ipc_driver); + bus_unregister(&rpmsg_bus); +} +module_exit(fini); + +MODULE_DEVICE_TABLE(virtio, id_table); +MODULE_DESCRIPTION("Virtio-based remote processor messaging bus"); +MODULE_LICENSE("GPL v2"); |