blob: 7cd3494d78ea9036caa827bac667144157b28958 [file] [log] [blame]
/*
* linux/arch/arm/mach-comcerto/membuf.c
*
* Copyright (C) 2010 Mindspeed Technologies, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <linux/module.h>
#include <linux/version.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>
#include <linux/scatterlist.h>
#include <asm/uaccess.h>
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,33)
#define sg_page(sg) ((sg)->page)
#define sg_virt(sg) (page_address(sg_page(sg)) + (sg)->offset)
#endif
#define MAX_BUFFERS 48
#define MEMBUF_MINOR 0
#define MEMBUF_MINOR_COUNT 1
#define MEMBUF_DEV_COUNT 1
#define MEMBUF_DEFAULT_PG_ORDER 6
#define MEMBUF_DRV_NAME "membuf"
#define MEMBUF_GET_SCATTER _IOR('m', 1, struct usr_scatter_list)
struct usr_scatter_list
{
u8 entries;
u8 pg_order[MAX_BUFFERS];
u32 addr[MAX_BUFFERS];
};
struct membuf
{
struct scatterlist sg[MAX_BUFFERS];
int sg_nr;
int size; /* Total memory in use for the buffer data */
int real_size; /* Total memory allocated for the scatter list */
int pg_order;
char mapped;
};
static struct membuf_dev
{
struct cdev dev;
int devno;
} membuf_dev;
static int membuf_map(struct membuf *buf)
{
int rc;
if (buf->mapped)
{
printk(KERN_ERR "%s: buffer already mapped\n", __func__);
rc = -EINVAL;
goto out;
}
rc = dma_map_sg(NULL, buf->sg, buf->sg_nr, DMA_TO_DEVICE);
if (rc != buf->sg_nr)
printk(KERN_ERR "%s: dma_map_sg() failed\n", __func__);
else
buf->mapped = 1;
out:
return rc;
}
static void membuf_unmap(struct membuf *buf)
{
if (!buf->mapped)
return;
dma_unmap_sg(NULL, buf->sg, buf->sg_nr, DMA_TO_DEVICE);
buf->mapped = 0;
}
static void membuf_free(struct membuf *buf)
{
struct scatterlist *sg;
int i;
for (i = 0, sg = buf->sg; i < buf->sg_nr; i++, sg++) {
__free_pages(sg_page(sg), get_order(sg->length));
}
buf->sg_nr = 0;
buf->size = 0;
buf->real_size = 0;
buf->pg_order = MEMBUF_DEFAULT_PG_ORDER;
}
static void membuf_sync(struct membuf *buf)
{
dma_sync_sg_for_device(NULL, buf->sg, buf->sg_nr, DMA_TO_DEVICE);
}
static int membuf_expand(struct membuf *buf, int size)
{
struct scatterlist *sg;
void *page;
int len;
membuf_unmap(buf);
while (size > 0)
{
if (buf->sg_nr >= MAX_BUFFERS) {
printk(KERN_ERR "%s: too many scatter entries(%d) failed\n", __func__, buf->sg_nr);
return -ENOMEM;
}
retry:
page = (void *)__get_free_pages(GFP_KERNEL | __GFP_NOWARN, buf->pg_order);
if (!page) {
if (buf->pg_order-- > 1)
goto retry;
printk(KERN_ERR "%s: __get_free_pages(%d) failed\n", __func__, buf->pg_order);
return -ENOMEM;
}
sg = &buf->sg[buf->sg_nr];
len = PAGE_SIZE * (1 << buf->pg_order);
sg_set_buf(sg, page, len);
buf->sg_nr++;
buf->real_size += len;
if (size > len)
buf->size += len;
else
buf->size += size;
size -= len;
}
return 0;
}
static int get_sg_index(struct membuf *buf, int *offset)
{
struct scatterlist *sg;
int i;
for (i = 0, sg = buf->sg; i < buf->sg_nr; i++, sg++) {
if (*offset < sg->length)
break;
*offset -= sg->length;
}
// printk(KERN_INFO "%d %d\n", i, *offset);
return i;
}
static ssize_t membuf_write(struct file *file, const char __user *data, size_t count, loff_t *off)
{
struct membuf *buf = (struct membuf *)file->private_data;
struct scatterlist *sg;
int sg_offset, sg_i;
int count_now;
int offset = 0;
int rc;
// printk(KERN_INFO "%d\n", *off);
if ((*off + count) > buf->real_size) {
rc = membuf_expand(buf, (*off + count) - buf->real_size);
if (rc < 0) {
printk(KERN_ERR "%s: membuf_expand() failed\n", __func__);
goto out;
}
}
sg_offset = *off;
sg_i = get_sg_index(buf, &sg_offset);
sg = &buf->sg[sg_i];
while (count) {
count_now = min(sg->length - sg_offset, count);
if (copy_from_user(sg_virt(sg) + sg_offset, data + offset, count_now)) {
printk(KERN_ERR "%s: copy_from_user() failed\n", __func__);
rc = -EFAULT;
goto out;
}
count -= count_now;
offset += count_now;
sg++;
sg_offset = 0;
}
*off += offset;
rc = offset;
out:
return rc;
}
static loff_t membuf_llseek(struct file *file, loff_t offset, int origin)
{
struct membuf *buf = (struct membuf *)file->private_data;
loff_t new_offset;
int rc;
switch (origin) {
case 0: /* SEEK_SET */
new_offset = offset;
break;
case 1: /* SEEK_CUR */
new_offset = file->f_pos + offset;
break;
case 2: /* SEEK_END */
new_offset = buf->size + offset;
break;
default:
rc = -EINVAL;
goto err;
}
if (new_offset > buf->real_size) {
rc = membuf_expand(buf, new_offset - buf->real_size);
if (rc < 0) {
printk(KERN_ERR "%s: membuf_expand() failed\n", __func__);
goto err;
}
}
file->f_pos = new_offset;
return new_offset;
err:
return rc;
}
static long membuf_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
struct membuf *buf = (struct membuf *)file->private_data;
struct usr_scatter_list usr_sg;
struct scatterlist *sg;
int i;
int rc = 0;
switch (cmd) {
case MEMBUF_GET_SCATTER:
if (!buf->mapped)
{
rc = membuf_map(buf);
if (rc < 0)
break;
}
membuf_sync(buf);
usr_sg.entries = 0;
for (i = 0, sg = buf->sg; i < buf->sg_nr; i++, sg++) {
usr_sg.addr[usr_sg.entries] = sg_dma_address(sg);
usr_sg.pg_order[usr_sg.entries] = get_order(sg_dma_len(sg));
/*
printk(KERN_INFO "%d %x %x %x %x %d\n", i, sg_virt(sg), sg_phys(sg),
sg_dma_address(sg), usr_sg.pg_order[usr_sg.entries],
sg->length);
*/
usr_sg.entries++;
}
if (copy_to_user((void __user *)arg, &usr_sg, sizeof(struct usr_scatter_list))) {
printk(KERN_ERR "%s: copy_to_user() failed\n", __func__);
rc = -EFAULT;
}
break;
default:
printk(KERN_ERR "%s: unknown ioctl(%d)\n", __func__, cmd);
rc = -EINVAL;
break;
}
return rc;
}
static int membuf_open(struct inode *in, struct file *file)
{
struct membuf *buf;
buf = kzalloc(sizeof(struct membuf), GFP_KERNEL);
if (!buf) {
printk(KERN_ERR "%s: kmalloc() failed\n", __func__);
return -ENOMEM;
}
buf->pg_order = MEMBUF_DEFAULT_PG_ORDER;
file->private_data = buf;
return 0;
}
static int membuf_release(struct inode *in, struct file *file)
{
struct membuf *buf = (struct membuf *)file->private_data;
membuf_unmap(buf);
membuf_free(buf);
kfree(buf);
return 0;
}
static const struct file_operations membuf_fops = {
.owner = THIS_MODULE,
.write = membuf_write,
.llseek = membuf_llseek,
.unlocked_ioctl = membuf_ioctl,
.open = membuf_open,
.release = membuf_release,
};
static int __init membuf_init(void)
{
int rc = 0;
rc = alloc_chrdev_region(&membuf_dev.devno, MEMBUF_MINOR, MEMBUF_MINOR_COUNT, MEMBUF_DRV_NAME);
if (rc < 0) {
printk(KERN_ERR "%s: alloc_chrdev_region() failed\n", __func__);
goto err0;
}
cdev_init(&membuf_dev.dev, &membuf_fops);
membuf_dev.dev.owner = THIS_MODULE;
rc = cdev_add (&membuf_dev.dev, membuf_dev.devno, MEMBUF_DEV_COUNT);
if (rc < 0) {
printk(KERN_ERR "%s: cdev_add() failed\n", __func__);
goto err1;
}
printk(KERN_INFO "%s: created membuf device(%d, %d)\n", __func__, MAJOR(membuf_dev.devno), MINOR(membuf_dev.devno));
return 0;
err1:
unregister_chrdev_region(membuf_dev.devno, MEMBUF_DEV_COUNT);
err0:
return rc;
}
static void __exit membuf_exit(void)
{
cdev_del(&membuf_dev.dev);
unregister_chrdev_region(membuf_dev.devno, MEMBUF_DEV_COUNT);
}
module_init(membuf_init);
module_exit(membuf_exit);