/* 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 #include #include #include #include #include #include #include #include /********************************************************************** * 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);