blob: 3d13e4e3cdc7d8b0c3ff7e3182923f5b0770b1fc [file] [log] [blame]
/* Copyright (c) 2008-2009, 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 <linux/slab.h>
#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 DEFINE_SEMAPHORE(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;
sema_init(&priv->sem, 1);
/* 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 long dma_test_ioctl(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,
.unlocked_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++)
sema_init(&buffer_sems[i], 1);
printk(KERN_ALERT "%s, minor number %d\n", __func__, dma_test_dev.minor);
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);