diff options
Diffstat (limited to 'drivers/sipc/spool.c')
| -rw-r--r-- | drivers/sipc/spool.c | 260 |
1 files changed, 260 insertions, 0 deletions
diff --git a/drivers/sipc/spool.c b/drivers/sipc/spool.c new file mode 100644 index 00000000..3db07e2e --- /dev/null +++ b/drivers/sipc/spool.c @@ -0,0 +1,260 @@ +/* + * Copyright (C) 2012 Spreadtrum Communications Inc. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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/kernel.h> +#include <linux/module.h> +#include <linux/cdev.h> +#include <linux/poll.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +#include <linux/spool.h> +#include <linux/sipc.h> + +struct spool_device { + struct spool_init_data *init; + int major; + int minor; + struct cdev cdev; +}; + +struct spool_sblock { + uint8_t dst; + uint8_t channel; +}; + +static struct class *spool_class; + +static int spool_open(struct inode *inode, struct file *filp) +{ + static struct spool_device *spool; + struct spool_sblock *sblock; + + spool = container_of(inode->i_cdev, struct spool_device, cdev); + sblock = kmalloc(sizeof(struct spool_sblock), GFP_KERNEL); + if (!sblock) { + return -ENOMEM; + } + filp->private_data = sblock; + + sblock->dst = spool->init->dst; + sblock->channel = spool->init->channel; + + return 0; +} + +static int spool_release(struct inode *inode, struct file *filp) +{ + struct spool_sblock *sblock = filp->private_data; + + kfree(sblock); + + return 0; +} + +static ssize_t spool_read(struct file *filp, + char __user *buf, size_t count, loff_t *ppos) +{ + struct spool_sblock *sblock = filp->private_data; + int timeout = -1; + int ret = 0; + int rdsize = 0; + struct sblock blk = {0}; + + if (filp->f_flags & O_NONBLOCK) { + timeout = 0; + } + + if((ret = sblock_receive(sblock->dst, sblock->channel, &blk, timeout)) < 0){ + pr_debug("spool_read: failed to receive block!\n"); + return ret; + } + + rdsize = blk.length > count ? count : blk.length; + + if(copy_to_user(buf, blk.addr, rdsize)){ + pr_debug("spool_read: failed to copy to user!\n"); + ret = -EFAULT; + }else{ + ret = rdsize; + } + + if(sblock_release(sblock->dst, sblock->channel, &blk)){ + pr_debug("failed to release block!\n"); + } + + return ret; +} + +static ssize_t spool_write(struct file *filp, + const char __user *buf, size_t count, loff_t *ppos) +{ + struct spool_sblock *sblock = filp->private_data; + int timeout = -1; + int ret = 0; + int wrsize = 0; + int pos = 0; + struct sblock blk = {0}; + size_t len = count; + + if(filp->f_flags & O_NONBLOCK){ + timeout = 0; + } + + do{ + if((ret = sblock_get(sblock->dst, sblock->channel, &blk, timeout)) < 0){ + printk(KERN_WARNING "spool_write: failed to get block!\n"); + return ret; + } + + wrsize = (blk.length > len ? len : blk.length); + pr_debug("spool_write: blk_len %d, count %d, wsize %d\n", blk.length, len, wrsize); + if(copy_from_user(blk.addr, buf + pos, wrsize)){ + printk(KERN_WARNING "spool_write: failed to copy from user!\n"); + ret = -EFAULT; + }else{ + blk.length = wrsize; + len -= wrsize; + pos += wrsize; + } + + if(sblock_send(sblock->dst, sblock->channel, &blk)){ + pr_debug("spool_write: failed to send block!"); + } + + pr_debug("spool_write len= %u, ret= %d\n", len, ret); + }while(len > 0 && ret == 0); + + return count - len; +} + +static unsigned int spool_poll(struct file *filp, poll_table *wait) +{ + return 0; +} + +static long spool_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) +{ + return 0; +} + +static const struct file_operations spool_fops = { + .open = spool_open, + .release = spool_release, + .read = spool_read, + .write = spool_write, + .poll = spool_poll, + .unlocked_ioctl = spool_ioctl, + .owner = THIS_MODULE, + .llseek = default_llseek, +}; + +static int __devinit spool_probe(struct platform_device *pdev) +{ + struct spool_init_data *init = pdev->dev.platform_data; + struct spool_device *spool; + dev_t devid; + int rval; + + rval = sblock_create(init->dst, init->channel, init->txblocknum, + init->txblocksize, init->rxblocknum, init->rxblocksize); + if (rval != 0) { + printk(KERN_ERR "Failed to create sblock: %d\n", rval); + return rval; + } + + spool = kzalloc(sizeof(struct spool_device), GFP_KERNEL); + if (spool == NULL) { + sblock_destroy(init->dst, init->channel); + printk(KERN_ERR "Failed to allocate spool_device\n"); + return -ENOMEM; + } + + rval = alloc_chrdev_region(&devid, 0, 1, init->name); + if (rval != 0) { + sblock_destroy(init->dst, init->channel); + kfree(spool); + printk(KERN_ERR "Failed to alloc spool chrdev\n"); + return rval; + } + cdev_init(&(spool->cdev), &spool_fops); + rval = cdev_add(&(spool->cdev), devid, 1); + if (rval != 0) { + sblock_destroy(init->dst, init->channel); + kfree(spool); + unregister_chrdev_region(devid, 1); + printk(KERN_ERR "Failed to add spool cdev\n"); + return rval; + } + + spool->major = MAJOR(devid); + spool->minor = MINOR(devid); + + device_create(spool_class, NULL, + MKDEV(spool->major, spool->minor), + NULL, "%s", init->name); + + spool->init = init; + + platform_set_drvdata(pdev, spool); + + return 0; +} + +static int __devexit spool_remove(struct platform_device *pdev) +{ + struct spool_device *spool = platform_get_drvdata(pdev); + device_destroy(spool_class, MKDEV(spool->major, spool->minor)); + + cdev_del(&(spool->cdev)); + unregister_chrdev_region( + MKDEV(spool->major, spool->minor), 1); + + sblock_destroy(spool->init->dst, spool->init->channel); + kfree(spool); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver spool_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "spool", + }, + .probe = spool_probe, + .remove = __devexit_p(spool_remove), +}; + +static int __init spool_init(void) +{ + spool_class = class_create(THIS_MODULE, "spool"); + if (IS_ERR(spool_class)) + return PTR_ERR(spool_class); + + return platform_driver_register(&spool_driver); +} + +static void __exit spool_exit(void) +{ + class_destroy(spool_class); + platform_driver_unregister(&spool_driver); +} + +module_init(spool_init); +module_exit(spool_exit); + +MODULE_AUTHOR("Qiu Yi"); +MODULE_DESCRIPTION("SIPC/SPOOL driver"); +MODULE_LICENSE("GPL"); |
