summaryrefslogtreecommitdiff
path: root/arch/arm/mach-msm/dma_test.c
diff options
context:
space:
mode:
Diffstat (limited to 'arch/arm/mach-msm/dma_test.c')
-rw-r--r--arch/arm/mach-msm/dma_test.c404
1 files changed, 404 insertions, 0 deletions
diff --git a/arch/arm/mach-msm/dma_test.c b/arch/arm/mach-msm/dma_test.c
new file mode 100644
index 000000000000..c4967f97f832
--- /dev/null
+++ b/arch/arm/mach-msm/dma_test.c
@@ -0,0 +1,404 @@
+/* Copyright (c) 2008-2009, Code Aurora Forum. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of Code Aurora Forum nor
+ * the names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior written
+ * permission.
+ *
+ * Alternatively, provided that this notice is retained in full, this software
+ * may be relicensed by the recipient under the terms of the GNU General Public
+ * License version 2 ("GPL") and only version 2, in which case the provisions of
+ * the GPL apply INSTEAD OF those given above. If the recipient relicenses the
+ * software under the GPL, then the identification text in the MODULE_LICENSE
+ * macro must be changed to reflect "GPLv2" instead of "Dual BSD/GPL". Once a
+ * recipient changes the license terms to the GPL, subsequent recipients shall
+ * not relicense under alternate licensing terms, including the BSD or dual
+ * BSD/GPL terms. In addition, the following license statement immediately
+ * below and between the words START and END shall also then apply when this
+ * software is relicensed under the GPL:
+ *
+ * START
+ *
+ * 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.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * END
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/fs.h>
+#include <linux/miscdevice.h>
+#include <linux/uaccess.h>
+
+#include <mach/dma.h>
+#include <mach/dma_test.h>
+
+
+/**********************************************************************
+ * User-space testing of the DMA driver.
+ * Intended to be loaded as a module. We have a bunch of static
+ * buffers that the user-side can refer to. The main DMA is simply
+ * used memory-to-memory. Device DMA is best tested with the specific
+ * device driver in question.
+ */
+#define MAX_TEST_BUFFERS 40
+#define MAX_TEST_BUFFER_SIZE 65536
+static void *(buffers[MAX_TEST_BUFFERS]);
+static int sizes[MAX_TEST_BUFFERS];
+
+/* Anything that allocates or deallocates buffers must lock with this
+ * mutex. */
+static DECLARE_MUTEX(buffer_lock);
+
+/* Each buffer has a semaphore associated with it that will be held
+ * for the duration of any operations on that buffer. It also must be
+ * available to free the given buffer. */
+static struct semaphore buffer_sems[MAX_TEST_BUFFERS];
+
+#define buffer_up(num) up(&buffer_sems[num])
+#define buffer_down(num) down(&buffer_sems[num])
+
+/* Use the General Purpose DMA channel as our test channel. This channel
+ * should be available on any target. */
+#define TEST_CHANNEL DMOV_GP_CHAN
+
+struct private {
+ /* Each open instance is allowed a single pending
+ * operation. */
+ struct semaphore sem;
+
+ /* Simple command buffer. Allocated and freed by driver. */
+ /* TODO: Allocate these together. */
+ dmov_s *command_ptr;
+
+ /* Indirect. */
+ u32 *command_ptr_ptr;
+
+ /* Indicates completion with pending request. */
+ struct completion complete;
+};
+
+static void free_buffers(void)
+{
+ int i;
+
+ for (i = 0; i < MAX_TEST_BUFFERS; i++) {
+ if (sizes[i] > 0) {
+ kfree(buffers[i]);
+ sizes[i] = 0;
+ }
+ }
+}
+
+/* Copy between two buffers, using the DMA. */
+
+/* Allocate a buffer of a requested size. */
+static int buffer_req(struct msm_dma_alloc_req *req)
+{
+ int i;
+
+ if (req->size <= 0 || req->size > MAX_TEST_BUFFER_SIZE)
+ return -EINVAL;
+
+ down(&buffer_lock);
+
+ /* Find a free buffer. */
+ for (i = 0; i < MAX_TEST_BUFFERS; i++)
+ if (sizes[i] == 0)
+ break;
+
+ if (i >= MAX_TEST_BUFFERS)
+ goto error;
+
+ buffers[i] = kmalloc(req->size, GFP_KERNEL | __GFP_DMA);
+ if (buffers[i] == 0)
+ goto error;
+ sizes[i] = req->size;
+
+ req->bufnum = i;
+
+ up(&buffer_lock);
+ return 0;
+
+error:
+ up(&buffer_lock);
+ return -ENOSPC;
+}
+
+static int dma_scopy(struct msm_dma_scopy *scopy, struct private *priv)
+{
+ int err = 0;
+ dma_addr_t mapped_cmd;
+ dma_addr_t mapped_cmd_ptr;
+
+ buffer_down(scopy->srcbuf);
+ if (scopy->srcbuf != scopy->destbuf)
+ buffer_down(scopy->destbuf);
+
+ priv->command_ptr->cmd = CMD_PTR_LP | CMD_MODE_SINGLE;
+ priv->command_ptr->src = dma_map_single(NULL, buffers[scopy->srcbuf],
+ scopy->size, DMA_TO_DEVICE);
+ priv->command_ptr->dst = dma_map_single(NULL, buffers[scopy->destbuf],
+ scopy->size, DMA_FROM_DEVICE);
+ priv->command_ptr->len = scopy->size;
+
+ mapped_cmd =
+ dma_map_single(NULL, priv->command_ptr, sizeof(*priv->command_ptr),
+ DMA_TO_DEVICE);
+ *(priv->command_ptr_ptr) = CMD_PTR_ADDR(mapped_cmd) | CMD_PTR_LP;
+
+ mapped_cmd_ptr = dma_map_single(NULL, priv->command_ptr_ptr,
+ sizeof(*priv->command_ptr_ptr),
+ DMA_TO_DEVICE);
+
+ msm_dmov_exec_cmd(TEST_CHANNEL,
+ DMOV_CMD_PTR_LIST | DMOV_CMD_ADDR(mapped_cmd_ptr));
+
+ dma_unmap_single(NULL, (dma_addr_t) mapped_cmd_ptr,
+ sizeof(*priv->command_ptr_ptr), DMA_TO_DEVICE);
+ dma_unmap_single(NULL, (dma_addr_t) mapped_cmd,
+ sizeof(*priv->command_ptr), DMA_TO_DEVICE);
+ dma_unmap_single(NULL, (dma_addr_t) priv->command_ptr->dst,
+ scopy->size, DMA_FROM_DEVICE);
+ dma_unmap_single(NULL, (dma_addr_t) priv->command_ptr->src,
+ scopy->size, DMA_TO_DEVICE);
+
+ if (scopy->srcbuf != scopy->destbuf)
+ buffer_up(scopy->destbuf);
+ buffer_up(scopy->srcbuf);
+
+ return err;
+}
+
+static int dma_test_open(struct inode *inode, struct file *file)
+{
+ struct private *priv;
+
+ printk(KERN_ALERT "%s\n", __func__);
+
+ priv = kmalloc(sizeof(struct private), GFP_KERNEL);
+ if (priv == NULL)
+ return -ENOMEM;
+ file->private_data = priv;
+
+ init_MUTEX(&priv->sem);
+
+ /* Note, that these should be allocated together so we don't
+ * waste 32 bytes for each. */
+
+ /* Allocate the command pointer. */
+ priv->command_ptr = kmalloc(sizeof(&priv->command_ptr),
+ GFP_KERNEL | __GFP_DMA);
+ if (priv->command_ptr == NULL) {
+ kfree(priv);
+ return -ENOSPC;
+ }
+
+ /* And the indirect pointer. */
+ priv->command_ptr_ptr = kmalloc(sizeof(u32), GFP_KERNEL | __GFP_DMA);
+ if (priv->command_ptr_ptr == NULL) {
+ kfree(priv->command_ptr);
+ kfree(priv);
+ return -ENOSPC;
+ }
+
+ return 0;
+}
+
+static int dma_test_release(struct inode *inode, struct file *file)
+{
+ struct private *priv;
+
+ printk(KERN_ALERT "%s\n", __func__);
+
+ if (file->private_data != NULL) {
+ priv = file->private_data;
+ kfree(priv->command_ptr_ptr);
+ kfree(priv->command_ptr);
+ }
+ kfree(file->private_data);
+ file->private_data = NULL;
+
+ return 0;
+}
+
+static int dma_test_ioctl(struct inode *inode, struct file *file,
+ unsigned cmd, unsigned long arg)
+{
+ int err = 0;
+ int tmp;
+ struct msm_dma_alloc_req alloc_req;
+ struct msm_dma_bufxfer xfer;
+ struct msm_dma_scopy scopy;
+ struct private *priv = file->private_data;
+
+ /* Verify user arguments. */
+ if (_IOC_TYPE(cmd) != MSM_DMA_IOC_MAGIC)
+ return -ENOTTY;
+
+ switch (cmd) {
+ case MSM_DMA_IOALLOC:
+ if (!access_ok(VERIFY_WRITE, (void __user *)arg,
+ sizeof(alloc_req)))
+ return -EFAULT;
+ if (__copy_from_user(&alloc_req, (void __user *)arg,
+ sizeof(alloc_req)))
+ return -EFAULT;
+ err = buffer_req(&alloc_req);
+ if (err < 0)
+ return err;
+ if (__copy_to_user((void __user *)arg, &alloc_req,
+ sizeof(alloc_req)))
+ return -EFAULT;
+ break;
+
+ case MSM_DMA_IOFREEALL:
+ down(&buffer_lock);
+ for (tmp = 0; tmp < MAX_TEST_BUFFERS; tmp++) {
+ buffer_down(tmp);
+ if (sizes[tmp] > 0) {
+ kfree(buffers[tmp]);
+ sizes[tmp] = 0;
+ }
+ buffer_up(tmp);
+ }
+ up(&buffer_lock);
+ break;
+
+ case MSM_DMA_IOWBUF:
+ if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer)))
+ return -EFAULT;
+ if (xfer.bufnum < 0 || xfer.bufnum >= MAX_TEST_BUFFERS)
+ return -EINVAL;
+ buffer_down(xfer.bufnum);
+ if (sizes[xfer.bufnum] == 0 ||
+ xfer.size <= 0 || xfer.size > sizes[xfer.bufnum]) {
+ buffer_up(xfer.bufnum);
+ return -EINVAL;
+ }
+ if (copy_from_user(buffers[xfer.bufnum],
+ (void __user *)xfer.data, xfer.size))
+ err = -EFAULT;
+ buffer_up(xfer.bufnum);
+ break;
+
+ case MSM_DMA_IORBUF:
+ if (copy_from_user(&xfer, (void __user *)arg, sizeof(xfer)))
+ return -EFAULT;
+ if (xfer.bufnum < 0 || xfer.bufnum >= MAX_TEST_BUFFERS)
+ return -EINVAL;
+ buffer_down(xfer.bufnum);
+ if (sizes[xfer.bufnum] == 0 ||
+ xfer.size <= 0 || xfer.size > sizes[xfer.bufnum]) {
+ buffer_up(xfer.bufnum);
+ return -EINVAL;
+ }
+ if (copy_to_user((void __user *)xfer.data, buffers[xfer.bufnum],
+ xfer.size))
+ err = -EFAULT;
+ buffer_up(xfer.bufnum);
+ break;
+
+ case MSM_DMA_IOSCOPY:
+ if (copy_from_user(&scopy, (void __user *)arg, sizeof(scopy)))
+ return -EFAULT;
+ if (scopy.srcbuf < 0 || scopy.srcbuf >= MAX_TEST_BUFFERS ||
+ sizes[scopy.srcbuf] == 0 ||
+ scopy.destbuf < 0 || scopy.destbuf >= MAX_TEST_BUFFERS ||
+ sizes[scopy.destbuf] == 0 ||
+ scopy.size > sizes[scopy.destbuf] ||
+ scopy.size > sizes[scopy.srcbuf])
+ return -EINVAL;
+#if 0
+ /* Test interface using memcpy. */
+ memcpy(buffers[scopy.destbuf],
+ buffers[scopy.srcbuf], scopy.size);
+#else
+ err = dma_scopy(&scopy, priv);
+#endif
+ break;
+
+ default:
+ return -ENOTTY;
+ }
+
+ return err;
+}
+
+/**********************************************************************
+ * Register ourselves as a misc device to be able to test the DMA code
+ * from userspace. */
+
+static const struct file_operations dma_test_fops = {
+ .owner = THIS_MODULE,
+ .ioctl = dma_test_ioctl,
+ .open = dma_test_open,
+ .release = dma_test_release,
+};
+
+static struct miscdevice dma_test_dev = {
+ .minor = MISC_DYNAMIC_MINOR,
+ .name = "msmdma",
+ .fops = &dma_test_fops,
+};
+static int dma_test_init(void)
+{
+ int ret, i;
+
+ ret = misc_register(&dma_test_dev);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < MAX_TEST_BUFFERS; i++)
+ init_MUTEX(&buffer_sems[i]);
+
+ printk(KERN_ALERT "%s\n", __func__);
+ return 0;
+}
+
+static void dma_test_exit(void)
+{
+ free_buffers();
+ misc_deregister(&dma_test_dev);
+ printk(KERN_ALERT "%s\n", __func__);
+}
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("David Brown, Qualcomm, Incorporated");
+MODULE_DESCRIPTION("Test for MSM DMA driver");
+MODULE_VERSION("1.01");
+
+module_init(dma_test_init);
+module_exit(dma_test_exit);