diff --git a/Documentation/arm/msm/adsprpc-drv.txt b/Documentation/arm/msm/adsprpc-drv.txt new file mode 100644 index 000000000000..f468ddb691c3 --- /dev/null +++ b/Documentation/arm/msm/adsprpc-drv.txt @@ -0,0 +1,198 @@ +Introduction +============ + +The MSM ADSPRPC driver implements an IPC (Inter-Processor Communication) +mechanism that allows for clients to transparently make remote method +invocations across processor boundaries. + +The below diagram depicts invocation of a single method where the client +and objects reside on different processors. An object could expose +multiple methods which can be grouped together and referred to as an +interface. + +: ,--------, ,------, ,-----------, ,------, ,--------, +: | | method | | | | | | method | | +: | Client |------->| Stub |->| Transport |->| Skel |------->| Object | +: | | | | | | | | | | +: `--------` `------` `-----------` `------` `--------` + +Client: Linux user mode process that initiates the remote invocation +Stub: Auto generated code linked in with the user mode process that + takes care of marshaling parameters +Transport: Involved in carrying an invocation from a client to an + object. This involves two portions: 1) MSM ADSPRPC Linux + kernel driver that receives the remote invocation, queues + them up and then waits for the response after signaling the + remote side. 2) Service running on the remote side that + dequeues the messages from the queue and dispatches them for + processing. +Skel: Auto generated code that takes care of un-marshaling + parameters +Object: Method implementation + +Hardware description +==================== + +The driver interfaces with the components in the DSP subsystem and does +not drive or manage any hardware resources. + +Software description +==================== + +The MSM ADSPRPC driver uses SMD (Shared Memory Driver) to send and +receive messages with the remote processor. The SMD channel used for +communication is opened during initialization of the driver and is +closed when the driver module is unloaded. The driver does not expose +HLOS memory to the remote processor but rather communication of +invocation parameters happen over ION allocated buffers. + +The driver receives remote call invocations via an ioctl call. When a +remote call invocation is received, the driver does the following: +- Retrieves the invocation parameters +- Copies input buffers in HLOS memory to ION allocated buffers +- Allocates ION buffers for output buffers in HLOS memory as required +- Scatter-gathers list of pages for ION allocated input and output + buffers +- Coalesces information about the contiguous page buffers +- Builds up a message with the received information +- Sends the message to a remote processor through an SMD channel +- Waits for a response from the remote processor through the SMD channel +- Reads the message available from the shared memory SMD channel +- Copies back from ION buffers to HLOS memory for output buffers +- Returns the response of the remote invocation + +Design +====== + +The design goals of this transport mechanism are: +- Fast and efficient ways to transfer huge buffers across + inter-processor boundaries +- Zero copy of ION allocated buffers passed during invocations + +To achieve the zero copy approach of ION allocated user space buffers, +the driver scatter-gathers the list of pages of the buffers being passed +in. This information is then sent over to the remote processor for it +to map into its address space. + +The invocation requests sent over the SMD channel carry context +information as to whom the request is originating from. The responses +received over the SMD channel have context information in the message +which is then used to wake the thread waiting for a response. + +If the remote processor goes down and gets restarted, the SMD channel +is re-initialized when the remote processor comes back up. An +error code would be returned to the client for all invocations that +happen before the SMD channel could get completely re-initialized. + +Power Management +================ + +None + +SMP/multi-core +============== + +The driver uses semaphores to wake up clients waiting for a remote +invocation response. + +Security +======== + +Use of the zero copy approach results in a page-size granularity of +all buffers being passed to the remote processor. The objects that will +be manipulating these buffers on the remote processor will be signed +and trusted entities, thereby alleviating any fear of intentional +scribbling of these buffers. + +Performance +=========== + +In order to minimize latencies across remote invocations: +- messages exchanged between the remote processors are kept short +- zero copy approach for ION allocated user space buffers + +Interface +========= + +The driver exposes a user space interface through /dev/adsprpc-smd and +the user space clients send commands to the driver by using the +following ioctl command: + +- FASTRPC_IOCTL_INVOKE: Parameters passed in includes the buffers and + data related to remote invocation. + + /* + * Information about the input/output buffer or an handle to the + * object being passed in the remote invocation + * + * @pv: Pointer to input/output buffer + * @len: Length of the input/output buffer + * @handle: Handle to the remote object + */ + typedef union { + struct remote_buf { + void *pv; + int len; + } buf; + unsigned int handle; + } remote_arg; + + /* + * Invocation parameters passed via ioctl call by the client + * + * @handle: Handle to the object on which the method is to be + * invoked + * @sc: Scalars detailing the parameters being passed in + * bits 0-3: Number of output handles + * bits 4-7: Number of input handles + * bits 8-15: Number of output buffers + * bits 16-23: Number of input buffers + * bits 24-28: Method to be invoked + * bits 29-31: Method attributes + * @pra: Remote arguments to be passed for method invocation + */ + struct fastrpc_ioctl_invoke { + unsigned int handle; + unsigned int sc; + remote_arg *pra; + }; + +Driver parameters +================= + +None + +Config options +============== + +None + +Dependencies +============ + +The ADSPRPC driver requires that the ADSP RPC SMD channel be created and +the SMD subsystem be initialized. During initialization, the driver +opens an existing SMD edge channel between ADSP and Apps processor. On +success, the driver waits for the "channel opened" event from SMD, +acknowledging the channel availability from the remote SMD driver for +communication to begin. + +User space utilities +==================== + +None + +Other +===== + +None + +Known issues +============ + +None + +To do +===== + +None diff --git a/arch/arm/mach-msm/qdsp6v2/Makefile b/arch/arm/mach-msm/qdsp6v2/Makefile index 0c75f6671077..ed8cb345ca84 100644 --- a/arch/arm/mach-msm/qdsp6v2/Makefile +++ b/arch/arm/mach-msm/qdsp6v2/Makefile @@ -26,3 +26,4 @@ obj-$(CONFIG_MSM_QDSP6V2_CODECS) += rtac_v2.o q6audio_v2.o q6audio_v2_aio.o obj-$(CONFIG_MSM_QDSP6V2_CODECS) += audio_mp3.o audio_amrnb.o audio_amrwb.o audio_evrc.o audio_qcelp.o amrwb_in.o obj-$(CONFIG_MSM_ADSP_LOADER) += adsp-loader.o obj-$(CONFIG_MSM_ULTRASOUND) += ultrasound/ +obj-m += adsprpc.o diff --git a/arch/arm/mach-msm/qdsp6v2/adsprpc.c b/arch/arm/mach-msm/qdsp6v2/adsprpc.c new file mode 100644 index 000000000000..6e6f8e8a06e3 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/adsprpc.c @@ -0,0 +1,706 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 "adsprpc.h" + +struct smq_invoke_ctx { + struct completion work; + int retval; + atomic_t free; +}; + +struct smq_context_list { + struct smq_invoke_ctx *ls; + int size; + int last; +}; + +struct fastrpc_apps { + smd_channel_t *chan; + struct smq_context_list clst; + struct completion work; + struct ion_client *iclient; + struct cdev cdev; + dev_t dev_no; + spinlock_t wrlock; + spinlock_t hlock; + struct hlist_head htbl[RPC_HASH_SZ]; +}; + +struct fastrpc_buf { + struct ion_handle *handle; + void *virt; + ion_phys_addr_t phys; + int size; + int used; +}; + +struct fastrpc_device { + uint32_t tgid; + struct hlist_node hn; + struct fastrpc_buf buf; +}; + +static struct fastrpc_apps gfa; + +static void free_mem(struct fastrpc_buf *buf) +{ + struct fastrpc_apps *me = &gfa; + + if (buf->handle) { + if (buf->virt) { + ion_unmap_kernel(me->iclient, buf->handle); + buf->virt = 0; + } + ion_free(me->iclient, buf->handle); + buf->handle = 0; + } +} + +static int alloc_mem(struct fastrpc_buf *buf) +{ + struct ion_client *clnt = gfa.iclient; + int err = 0; + + buf->handle = ion_alloc(clnt, buf->size, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID)); + VERIFY(0 == IS_ERR_OR_NULL(buf->handle)); + buf->virt = 0; + VERIFY(0 != (buf->virt = ion_map_kernel(clnt, buf->handle, + ION_SET_CACHE(CACHED)))); + VERIFY(0 == ion_phys(clnt, buf->handle, &buf->phys, &buf->size)); + bail: + if (err && !IS_ERR_OR_NULL(buf->handle)) + free_mem(buf); + return err; +} + +static int context_list_ctor(struct smq_context_list *me, int size) +{ + int err = 0; + VERIFY(0 != (me->ls = kzalloc(size, GFP_KERNEL))); + me->size = size / sizeof(*me->ls); + me->last = 0; + bail: + return err; +} + +static void context_list_dtor(struct smq_context_list *me) +{ + kfree(me->ls); + me->ls = 0; +} + +static void context_list_alloc_ctx(struct smq_context_list *me, + struct smq_invoke_ctx **po) +{ + int ii = me->last; + struct smq_invoke_ctx *ctx; + + for (;;) { + ii = ii % me->size; + ctx = &me->ls[ii]; + if (atomic_read(&ctx->free) == 0) + if (0 == atomic_cmpxchg(&ctx->free, 0, 1)) + break; + ii++; + } + me->last = ii; + ctx->retval = -1; + init_completion(&ctx->work); + *po = ctx; +} + +static void context_free(struct smq_invoke_ctx *me) +{ + if (me) + atomic_set(&me->free, 0); +} + +static void context_notify_user(struct smq_invoke_ctx *me, int retval) +{ + me->retval = retval; + complete(&me->work); +} + +static void context_notify_all_users(struct smq_context_list *me) +{ + int ii; + + if (!me->ls) + return; + for (ii = 0; ii < me->size; ++ii) { + if (atomic_read(&me->ls[ii].free) != 0) + complete(&me->ls[ii].work); + } +} + +static int get_page_list(uint32_t kernel, uint32_t sc, remote_arg_t *pra, + struct fastrpc_buf *ibuf, struct fastrpc_buf *obuf) +{ + struct smq_phy_page *pgstart, *pages; + struct smq_invoke_buf *list; + int ii, rlen, err = 0; + int inbufs = REMOTE_SCALARS_INBUFS(sc); + int outbufs = REMOTE_SCALARS_OUTBUFS(sc); + + VERIFY(0 != try_module_get(THIS_MODULE)); + LOCK_MMAP(kernel); + *obuf = *ibuf; + retry: + list = smq_invoke_buf_start((remote_arg_t *)obuf->virt, sc); + pgstart = smq_phy_page_start(sc, list); + pages = pgstart + 1; + rlen = obuf->size - ((uint32_t)pages - (uint32_t)obuf->virt); + if (rlen < 0) { + rlen = ((uint32_t)pages - (uint32_t)obuf->virt) - obuf->size; + obuf->size += buf_page_size(rlen); + obuf->handle = 0; + VERIFY(0 == alloc_mem(obuf)); + goto retry; + } + pgstart->addr = obuf->phys; + pgstart->size = obuf->size; + for (ii = 0; ii < inbufs + outbufs; ++ii) { + void *buf; + int len, num; + + len = pra[ii].buf.len; + if (!len) + continue; + buf = pra[ii].buf.pv; + num = buf_num_pages(buf, len); + if (!kernel) + list[ii].num = buf_get_pages(buf, len, num, + ii >= inbufs, pages, rlen / sizeof(*pages)); + else + list[ii].num = 0; + VERIFY(list[ii].num >= 0); + if (list[ii].num) { + list[ii].pgidx = pages - pgstart; + pages = pages + list[ii].num; + } else if (rlen > sizeof(*pages)) { + list[ii].pgidx = pages - pgstart; + pages = pages + 1; + } else { + if (obuf->handle != ibuf->handle) + free_mem(obuf); + obuf->size += buf_page_size(sizeof(*pages)); + obuf->handle = 0; + VERIFY(0 == alloc_mem(obuf)); + goto retry; + } + rlen = obuf->size - ((uint32_t) pages - (uint32_t) obuf->virt); + } + obuf->used = obuf->size - rlen; + bail: + if (err && (obuf->handle != ibuf->handle)) + free_mem(obuf); + UNLOCK_MMAP(kernel); + module_put(THIS_MODULE); + return err; +} + +static int get_args(uint32_t kernel, uint32_t sc, remote_arg_t *pra, + remote_arg_t *rpra, remote_arg_t *upra, + struct fastrpc_buf *ibuf, struct fastrpc_buf **abufs, + int *nbufs) +{ + struct smq_invoke_buf *list; + struct fastrpc_buf *pbuf = ibuf, *obufs = 0; + struct smq_phy_page *pages; + void *args; + int ii, rlen, size, used, inh, bufs = 0, err = 0; + int inbufs = REMOTE_SCALARS_INBUFS(sc); + int outbufs = REMOTE_SCALARS_OUTBUFS(sc); + + list = smq_invoke_buf_start(rpra, sc); + pages = smq_phy_page_start(sc, list); + used = ALIGN_8(pbuf->used); + args = (void *)((char *)pbuf->virt + used); + rlen = pbuf->size - used; + for (ii = 0; ii < inbufs + outbufs; ++ii) { + int num; + + rpra[ii].buf.len = pra[ii].buf.len; + if (list[ii].num) { + rpra[ii].buf.pv = pra[ii].buf.pv; + continue; + } + if (rlen < pra[ii].buf.len) { + struct fastrpc_buf *b; + pbuf->used = pbuf->size - rlen; + VERIFY(0 != (b = krealloc(obufs, + (bufs + 1) * sizeof(*obufs), GFP_KERNEL))); + obufs = b; + pbuf = obufs + bufs; + pbuf->size = buf_num_pages(0, pra[ii].buf.len) * + PAGE_SIZE; + VERIFY(0 == alloc_mem(pbuf)); + bufs++; + args = pbuf->virt; + rlen = pbuf->size; + } + num = buf_num_pages(args, pra[ii].buf.len); + if (pbuf == ibuf) { + list[ii].num = num; + list[ii].pgidx = 0; + } else { + list[ii].num = 1; + pages[list[ii].pgidx].addr = + buf_page_start((void *)(pbuf->phys + + (pbuf->size - rlen))); + pages[list[ii].pgidx].size = + buf_page_size(pra[ii].buf.len); + } + if (ii < inbufs) { + if (!kernel) + VERIFY(0 == copy_from_user(args, pra[ii].buf.pv, + pra[ii].buf.len)); + else + memmove(args, pra[ii].buf.pv, pra[ii].buf.len); + } + rpra[ii].buf.pv = args; + args = (void *)((char *)args + ALIGN_8(pra[ii].buf.len)); + rlen -= ALIGN_8(pra[ii].buf.len); + } + for (ii = 0; ii < inbufs; ++ii) { + if (rpra[ii].buf.len) + dmac_flush_range(rpra[ii].buf.pv, + (char *)rpra[ii].buf.pv + rpra[ii].buf.len); + } + pbuf->used = pbuf->size - rlen; + size = sizeof(*rpra) * REMOTE_SCALARS_INHANDLES(sc); + if (size) { + inh = inbufs + outbufs; + if (!kernel) + VERIFY(0 == copy_from_user(&rpra[inh], &upra[inh], + size)); + else + memmove(&rpra[inh], &upra[inh], size); + } + dmac_flush_range(rpra, (char *)rpra + used); + bail: + *abufs = obufs; + *nbufs = bufs; + return err; +} + +static int put_args(uint32_t kernel, uint32_t sc, remote_arg_t *pra, + remote_arg_t *rpra, remote_arg_t *upra) +{ + int ii, inbufs, outbufs, outh, size; + int err = 0; + + inbufs = REMOTE_SCALARS_INBUFS(sc); + outbufs = REMOTE_SCALARS_OUTBUFS(sc); + for (ii = inbufs; ii < inbufs + outbufs; ++ii) { + if (rpra[ii].buf.pv != pra[ii].buf.pv) + VERIFY(0 == copy_to_user(pra[ii].buf.pv, + rpra[ii].buf.pv, rpra[ii].buf.len)); + } + size = sizeof(*rpra) * REMOTE_SCALARS_OUTHANDLES(sc); + if (size) { + outh = inbufs + outbufs + REMOTE_SCALARS_INHANDLES(sc); + if (!kernel) + VERIFY(0 == copy_to_user(&upra[outh], &rpra[outh], + size)); + else + memmove(&upra[outh], &rpra[outh], size); + } + bail: + return err; +} + +static void inv_args(uint32_t sc, remote_arg_t *rpra, int used) +{ + int ii, inbufs, outbufs; + int inv = 0; + + inbufs = REMOTE_SCALARS_INBUFS(sc); + outbufs = REMOTE_SCALARS_OUTBUFS(sc); + for (ii = inbufs; ii < inbufs + outbufs; ++ii) { + if (buf_page_start(rpra) == buf_page_start(rpra[ii].buf.pv)) + inv = 1; + else + dmac_inv_range(rpra[ii].buf.pv, + (char *)rpra[ii].buf.pv + rpra[ii].buf.len); + } + + if (inv || REMOTE_SCALARS_OUTHANDLES(sc)) + dmac_inv_range(rpra, (char *)rpra + used); +} + +static int fastrpc_invoke_send(struct fastrpc_apps *me, remote_handle_t handle, + uint32_t sc, struct smq_invoke_ctx *ctx, + struct fastrpc_buf *buf) +{ + struct smq_msg msg; + int err = 0, len; + + msg.pid = current->tgid; + msg.tid = current->pid; + msg.invoke.header.ctx = ctx; + msg.invoke.header.handle = handle; + msg.invoke.header.sc = sc; + msg.invoke.page.addr = buf->phys; + msg.invoke.page.size = buf_page_size(buf->used); + spin_lock(&me->wrlock); + len = smd_write(me->chan, &msg, sizeof(msg)); + spin_unlock(&me->wrlock); + VERIFY(len == sizeof(msg)); + bail: + return err; +} + +static void fastrpc_deinit(void) +{ + struct fastrpc_apps *me = &gfa; + + if (me->chan) + (void)smd_close(me->chan); + context_list_dtor(&me->clst); + ion_client_destroy(me->iclient); + me->iclient = 0; + me->chan = 0; +} + +static void fastrpc_read_handler(void) +{ + struct fastrpc_apps *me = &gfa; + struct smq_invoke_rsp rsp; + int err = 0; + + do { + VERIFY(sizeof(rsp) == + smd_read_from_cb(me->chan, &rsp, sizeof(rsp))); + context_notify_user(rsp.ctx, rsp.retval); + } while (!err); + bail: + return; +} + +static void smd_event_handler(void *priv, unsigned event) +{ + struct fastrpc_apps *me = (struct fastrpc_apps *)priv; + + switch (event) { + case SMD_EVENT_OPEN: + complete(&(me->work)); + break; + case SMD_EVENT_CLOSE: + context_notify_all_users(&me->clst); + break; + case SMD_EVENT_DATA: + fastrpc_read_handler(); + break; + } +} + +static int fastrpc_init(void) +{ + int err = 0; + struct fastrpc_apps *me = &gfa; + + if (me->chan == 0) { + int ii; + spin_lock_init(&me->hlock); + spin_lock_init(&me->wrlock); + init_completion(&me->work); + for (ii = 0; ii < RPC_HASH_SZ; ++ii) + INIT_HLIST_HEAD(&me->htbl[ii]); + VERIFY(0 == context_list_ctor(&me->clst, SZ_4K)); + me->iclient = msm_ion_client_create(ION_HEAP_CARVEOUT_MASK, + DEVICE_NAME); + VERIFY(0 == IS_ERR_OR_NULL(me->iclient)); + VERIFY(0 == smd_named_open_on_edge(FASTRPC_SMD_GUID, + SMD_APPS_QDSP, &me->chan, + me, smd_event_handler)); + VERIFY(0 != wait_for_completion_timeout(&me->work, + RPC_TIMEOUT)); + } + bail: + if (err) + fastrpc_deinit(); + return err; +} + +static void free_dev(struct fastrpc_device *dev) +{ + if (dev) { + module_put(THIS_MODULE); + free_mem(&dev->buf); + kfree(dev); + } +} + +static int alloc_dev(struct fastrpc_device **dev) +{ + int err = 0; + struct fastrpc_device *fd = 0; + + VERIFY(0 != try_module_get(THIS_MODULE)); + VERIFY(0 != (fd = kzalloc(sizeof(*fd), GFP_KERNEL))); + fd->buf.size = PAGE_SIZE; + VERIFY(0 == alloc_mem(&fd->buf)); + fd->tgid = current->tgid; + INIT_HLIST_NODE(&fd->hn); + *dev = fd; + bail: + if (err) + free_dev(fd); + return err; +} + +static int get_dev(struct fastrpc_apps *me, struct fastrpc_device **rdev) +{ + struct hlist_head *head; + struct fastrpc_device *dev = 0; + struct hlist_node *n; + uint32_t h = hash_32(current->tgid, RPC_HASH_BITS); + int err = 0; + + spin_lock(&me->hlock); + head = &me->htbl[h]; + hlist_for_each_entry(dev, n, head, hn) { + if (dev->tgid == current->tgid) { + hlist_del(&dev->hn); + break; + } + } + spin_unlock(&me->hlock); + VERIFY(dev != 0); + *rdev = dev; + bail: + if (err) { + free_dev(dev); + err = alloc_dev(rdev); + } + return err; +} + +static void add_dev(struct fastrpc_apps *me, struct fastrpc_device *dev) +{ + struct hlist_head *head; + uint32_t h = hash_32(current->tgid, RPC_HASH_BITS); + + spin_lock(&me->hlock); + head = &me->htbl[h]; + hlist_add_head(&dev->hn, head); + spin_unlock(&me->hlock); + return; +} + +static int fastrpc_release_current_dsp_process(void); + +static int fastrpc_internal_invoke(struct fastrpc_apps *me, uint32_t kernel, + struct fastrpc_ioctl_invoke *invoke, remote_arg_t *pra) +{ + remote_arg_t *rpra = 0; + struct fastrpc_device *dev = 0; + struct smq_invoke_ctx *ctx = 0; + struct fastrpc_buf obuf, *abufs = 0, *b; + int interrupted = 0; + uint32_t sc; + int ii, nbufs = 0, err = 0; + + sc = invoke->sc; + obuf.handle = 0; + if (REMOTE_SCALARS_LENGTH(sc)) { + VERIFY(0 == get_dev(me, &dev)); + VERIFY(0 == get_page_list(kernel, sc, pra, &dev->buf, &obuf)); + rpra = (remote_arg_t *)obuf.virt; + VERIFY(0 == get_args(kernel, sc, pra, rpra, invoke->pra, &obuf, + &abufs, &nbufs)); + } + + context_list_alloc_ctx(&me->clst, &ctx); + VERIFY(0 == fastrpc_invoke_send(me, invoke->handle, sc, ctx, &obuf)); + inv_args(sc, rpra, obuf.used); + VERIFY(0 == (interrupted = + wait_for_completion_interruptible(&ctx->work))); + VERIFY(0 == (err = ctx->retval)); + VERIFY(0 == put_args(kernel, sc, pra, rpra, invoke->pra)); + bail: + if (interrupted) { + init_completion(&ctx->work); + if (!kernel) + (void)fastrpc_release_current_dsp_process(); + wait_for_completion(&ctx->work); + } + context_free(ctx); + for (ii = 0, b = abufs; ii < nbufs; ++ii, ++b) + free_mem(b); + kfree(abufs); + if (dev) { + add_dev(me, dev); + if (obuf.handle != dev->buf.handle) + free_mem(&obuf); + } + return err; +} + +static int fastrpc_create_current_dsp_process(void) +{ + int err = 0; + struct fastrpc_ioctl_invoke ioctl; + struct fastrpc_apps *me = &gfa; + remote_arg_t ra[1]; + int tgid = 0; + + tgid = current->tgid; + ra[0].buf.pv = &tgid; + ra[0].buf.len = sizeof(tgid); + ioctl.handle = 1; + ioctl.sc = REMOTE_SCALARS_MAKE(0, 1, 0); + ioctl.pra = ra; + VERIFY(0 == fastrpc_internal_invoke(me, 1, &ioctl, ra)); + bail: + return err; +} + +static int fastrpc_release_current_dsp_process(void) +{ + int err = 0; + struct fastrpc_apps *me = &gfa; + struct fastrpc_ioctl_invoke ioctl; + remote_arg_t ra[1]; + int tgid = 0; + + tgid = current->tgid; + ra[0].buf.pv = &tgid; + ra[0].buf.len = sizeof(tgid); + ioctl.handle = 1; + ioctl.sc = REMOTE_SCALARS_MAKE(1, 1, 0); + ioctl.pra = ra; + VERIFY(0 == fastrpc_internal_invoke(me, 1, &ioctl, ra)); + bail: + return err; +} + +static void cleanup_current_dev(void) +{ + struct fastrpc_apps *me = &gfa; + uint32_t h = hash_32(current->tgid, RPC_HASH_BITS); + struct hlist_head *head; + struct hlist_node *pos; + struct fastrpc_device *dev; + + rnext: + dev = 0; + spin_lock(&me->hlock); + head = &me->htbl[h]; + hlist_for_each_entry(dev, pos, head, hn) { + if (dev->tgid == current->tgid) { + hlist_del(&dev->hn); + break; + } + } + spin_unlock(&me->hlock); + if (dev) { + free_dev(dev); + goto rnext; + } + return; +} + +static int fastrpc_device_release(struct inode *inode, struct file *file) +{ + (void)fastrpc_release_current_dsp_process(); + cleanup_current_dev(); + return 0; +} + +static int fastrpc_device_open(struct inode *inode, struct file *filp) +{ + int err = 0; + + if (0 != try_module_get(THIS_MODULE)) { + /* This call will cause a dev to be created + * which will addref this module + */ + VERIFY(0 == fastrpc_create_current_dsp_process()); + bail: + if (err) + cleanup_current_dev(); + module_put(THIS_MODULE); + } + return err; +} + + +static long fastrpc_device_ioctl(struct file *file, unsigned int ioctl_num, + unsigned long ioctl_param) +{ + struct fastrpc_apps *me = &gfa; + struct fastrpc_ioctl_invoke invoke; + remote_arg_t *pra = 0; + void *param = (char *)ioctl_param; + int bufs, err = 0; + + switch (ioctl_num) { + case FASTRPC_IOCTL_INVOKE: + VERIFY(0 == copy_from_user(&invoke, param, sizeof(invoke))); + bufs = REMOTE_SCALARS_INBUFS(invoke.sc) + + REMOTE_SCALARS_OUTBUFS(invoke.sc); + if (bufs) { + bufs = bufs * sizeof(*pra); + VERIFY(0 != (pra = kmalloc(bufs, GFP_KERNEL))); + } + VERIFY(0 == copy_from_user(pra, invoke.pra, bufs)); + VERIFY(0 == (err = fastrpc_internal_invoke(me, 0, &invoke, + pra))); + break; + default: + err = -EINVAL; + break; + } + bail: + kfree(pra); + return err; +} + +static const struct file_operations fops = { + .open = fastrpc_device_open, + .release = fastrpc_device_release, + .unlocked_ioctl = fastrpc_device_ioctl, +}; + +static int __init fastrpc_device_init(void) +{ + struct fastrpc_apps *me = &gfa; + int err = 0; + + VERIFY(0 == fastrpc_init()); + VERIFY(0 == alloc_chrdev_region(&me->dev_no, 0, 1, DEVICE_NAME)); + cdev_init(&me->cdev, &fops); + me->cdev.owner = THIS_MODULE; + VERIFY(0 == cdev_add(&me->cdev, MKDEV(MAJOR(me->dev_no), 0), 1)); + pr_info("'mknod /dev/%s c %d 0'\n", DEVICE_NAME, MAJOR(me->dev_no)); + bail: + return err; +} + +static void __exit fastrpc_device_exit(void) +{ + struct fastrpc_apps *me = &gfa; + + fastrpc_deinit(); + cdev_del(&me->cdev); + unregister_chrdev_region(me->dev_no, 1); +} + +module_init(fastrpc_device_init); +module_exit(fastrpc_device_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/arch/arm/mach-msm/qdsp6v2/adsprpc.h b/arch/arm/mach-msm/qdsp6v2/adsprpc.h new file mode 100644 index 000000000000..368b8e6e460a --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/adsprpc.h @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 ADSPRPC_H +#define ADSPRPC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "adsprpc_shared.h" + +#ifndef ION_ADSPRPC_HEAP_ID +#define ION_ADSPRPC_HEAP_ID ION_AUDIO_HEAP_ID +#endif + +#define RPC_TIMEOUT (5 * HZ) +#define RPC_HASH_BITS 5 +#define RPC_HASH_SZ (1 << RPC_HASH_BITS) + +#define ALIGN_8(a) ALIGN(a, 8) + +#define LOCK_MMAP(kernel)\ + do {\ + if (!kernel)\ + down_read(¤t->mm->mmap_sem);\ + } while (0) + +#define UNLOCK_MMAP(kernel)\ + do {\ + if (!kernel)\ + up_read(¤t->mm->mmap_sem);\ + } while (0) + + +static inline uint32_t buf_page_start(void *buf) +{ + uint32_t start = (uint32_t) buf & PAGE_MASK; + return start; +} + +static inline uint32_t buf_page_offset(void *buf) +{ + uint32_t offset = (uint32_t) buf & (PAGE_SIZE - 1); + return offset; +} + +static inline int buf_num_pages(void *buf, int len) +{ + uint32_t start = buf_page_start(buf) >> PAGE_SHIFT; + uint32_t end = (((uint32_t) buf + len - 1) & PAGE_MASK) >> PAGE_SHIFT; + int nPages = end - start + 1; + return nPages; +} + +static inline uint32_t buf_page_size(uint32_t size) +{ + uint32_t sz = (size + (PAGE_SIZE - 1)) & PAGE_MASK; + return sz > PAGE_SIZE ? sz : PAGE_SIZE; +} + +static inline int buf_get_pages(void *addr, int sz, int nr_pages, int access, + struct smq_phy_page *pages, int nr_elems) +{ + struct vm_area_struct *vma; + uint32_t start = buf_page_start(addr); + uint32_t len = nr_pages << PAGE_SHIFT; + uint32_t pfn; + int n = -1, err = 0; + + VERIFY(0 != access_ok(access ? VERIFY_WRITE : VERIFY_READ, + (void __user *)start, len)); + VERIFY(0 != (vma = find_vma(current->mm, start))); + VERIFY(((uint32_t)addr + sz) <= vma->vm_end); + n = 0; + VERIFY(0 != (vma->vm_flags & VM_PFNMAP)); + VERIFY(0 != (vma->vm_flags & VM_PFN_AT_MMAP)); + VERIFY(nr_elems > 0); + pfn = vma->vm_pgoff + ((start - vma->vm_start) >> PAGE_SHIFT); + pages->addr = __pfn_to_phys(pfn); + pages->size = len; + n++; + bail: + return n; +} + +#endif diff --git a/arch/arm/mach-msm/qdsp6v2/adsprpc_shared.h b/arch/arm/mach-msm/qdsp6v2/adsprpc_shared.h new file mode 100644 index 000000000000..04b1d4a1b2b2 --- /dev/null +++ b/arch/arm/mach-msm/qdsp6v2/adsprpc_shared.h @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2012, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * 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 ADSPRPC_SHARED_H +#define ADSPRPC_SHARED_H + +#define FASTRPC_IOCTL_INVOKE _IOWR('R', 1, struct fastrpc_ioctl_invoke) +#define FASTRPC_SMD_GUID "fastrpcsmd-apps-dsp" +#define DEVICE_NAME "adsprpc-smd" + +/* Retrives number of input buffers from the scalars parameter */ +#define REMOTE_SCALARS_INBUFS(sc) (((sc) >> 16) & 0x0ff) + +/* Retrives number of output buffers from the scalars parameter */ +#define REMOTE_SCALARS_OUTBUFS(sc) (((sc) >> 8) & 0x0ff) + +/* Retrives number of input handles from the scalars parameter */ +#define REMOTE_SCALARS_INHANDLES(sc) (((sc) >> 4) & 0x0f) + +/* Retrives number of output handles from the scalars parameter */ +#define REMOTE_SCALARS_OUTHANDLES(sc) ((sc) & 0x0f) + +#define REMOTE_SCALARS_LENGTH(sc) (REMOTE_SCALARS_INBUFS(sc) +\ + REMOTE_SCALARS_OUTBUFS(sc) +\ + REMOTE_SCALARS_INHANDLES(sc) +\ + REMOTE_SCALARS_OUTHANDLES(sc)) + +#define REMOTE_SCALARS_MAKEX(attr, method, in, out, oin, oout) \ + ((((uint32_t) (attr) & 0x7) << 29) | \ + (((uint32_t) (method) & 0x1f) << 24) | \ + (((uint32_t) (in) & 0xff) << 16) | \ + (((uint32_t) (out) & 0xff) << 8) | \ + (((uint32_t) (oin) & 0x0f) << 4) | \ + ((uint32_t) (oout) & 0x0f)) + +#define REMOTE_SCALARS_MAKE(method, in, out) \ + REMOTE_SCALARS_MAKEX(0, method, in, out, 0, 0) + + +#ifndef VERIFY_PRINT_ERROR +#define VERIFY_EPRINTF(format, args) (void)0 +#endif + +#ifndef VERIFY_PRINT_INFO +#define VERIFY_IPRINTF(args) (void)0 +#endif + +#ifndef VERIFY +#define __STR__(x) #x ":" +#define __TOSTR__(x) __STR__(x) +#define __FILE_LINE__ __FILE__ ":" __TOSTR__(__LINE__) + +#define VERIFY(val) \ +do {\ + VERIFY_IPRINTF(__FILE_LINE__"info: calling: " #val "\n");\ + if (0 == (val)) {\ + err = err == 0 ? -1 : err;\ + VERIFY_EPRINTF(__FILE_LINE__"error: %d: " #val "\n", err);\ + goto bail;\ + } else {\ + VERIFY_IPRINTF(__FILE_LINE__"info: passed: " #val "\n");\ + } \ +} while (0) +#endif + +#define remote_handle_t uint32_t +#define remote_arg_t union remote_arg + +struct remote_buf { + void *pv; /* buffer pointer */ + int len; /* length of buffer */ +}; + +union remote_arg { + struct remote_buf buf; /* buffer info */ + remote_handle_t h; /* remote handle */ +}; + +struct fastrpc_ioctl_invoke { + remote_handle_t handle; /* remote handle */ + uint32_t sc; /* scalars describing the data */ + remote_arg_t *pra; /* remote arguments list */ +}; + +struct smq_null_invoke { + struct smq_invoke_ctx *ctx; /* invoke caller context */ + remote_handle_t handle; /* handle to invoke */ + uint32_t sc; /* scalars structure describing the data */ +}; + +struct smq_phy_page { + uint32_t addr; /* physical address */ + uint32_t size; /* size of contiguous region */ +}; + +struct smq_invoke_buf { + int num; /* number of contiguous regions */ + int pgidx; /* index to start of contiguous region */ +}; + +struct smq_invoke { + struct smq_null_invoke header; + struct smq_phy_page page; /* remote arg and list of pages address */ +}; + +struct smq_msg { + uint32_t pid; /* process group id */ + uint32_t tid; /* thread id */ + struct smq_invoke invoke; +}; + +struct smq_invoke_rsp { + struct smq_invoke_ctx *ctx; /* invoke caller context */ + int retval; /* invoke return value */ +}; + +static inline struct smq_invoke_buf *smq_invoke_buf_start(remote_arg_t *pra, + uint32_t sc) +{ + int len = REMOTE_SCALARS_LENGTH(sc); + return (struct smq_invoke_buf *)(&pra[len]); +} + +static inline struct smq_phy_page *smq_phy_page_start(uint32_t sc, + struct smq_invoke_buf *buf) +{ + int nTotal = REMOTE_SCALARS_INBUFS(sc) + REMOTE_SCALARS_OUTBUFS(sc); + return (struct smq_phy_page *)(&buf[nTotal]); +} + +static inline int smq_invoke_buf_size(uint32_t sc, int nPages) +{ + struct smq_phy_page *start = smq_phy_page_start(sc, 0); + return (int)(&(start[nPages])); +} + +#endif