361 lines
9.0 KiB
C
361 lines
9.0 KiB
C
/* 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);
|