diff options
author | kpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2007-01-31 17:27:30 +0000 |
---|---|---|
committer | kpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb> | 2007-01-31 17:27:30 +0000 |
commit | 92e6d005b1107055cdd62f41fc06bda8e98b14b3 (patch) | |
tree | 530476168181ed67fa632f008cca8ac1040eef0a /zttranscode.c | |
parent | ec54d1fb2b09fc3a4027a58317d190a403b148eb (diff) |
merge support for the Digium TC400B hardware transcoder
git-svn-id: http://svn.digium.com/svn/zaptel/branches/1.2@2057 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'zttranscode.c')
-rw-r--r-- | zttranscode.c | 489 |
1 files changed, 489 insertions, 0 deletions
diff --git a/zttranscode.c b/zttranscode.c new file mode 100644 index 0000000..60ff0ca --- /dev/null +++ b/zttranscode.c @@ -0,0 +1,489 @@ +/* + * Transcoder Interface for Zaptel + * + * Written by Mark Spencer <markster@digium.com> + * + * Copyright (C) 2006, Digium, Inc. + * + * 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 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., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/spinlock.h> +#include <linux/slab.h> +#include <linux/kmod.h> +#include <linux/sched.h> +#include <linux/interrupt.h> +#include <linux/vmalloc.h> +#include <asm/io.h> +#include <linux/types.h> +#ifdef CONFIG_DEVFS_FS +#include <linux/devfs_fs_kernel.h> +#endif +#ifdef STANDALONE_ZAPATA +#include "zaptel.h" +#else +#include <linux/zaptel.h> +#endif +#ifdef LINUX26 +#include <linux/moduleparam.h> +#endif + +static int debug = 0; +static LIST_HEAD(trans); +static spinlock_t translock = SPIN_LOCK_UNLOCKED; + +EXPORT_SYMBOL(zt_transcoder_register); +EXPORT_SYMBOL(zt_transcoder_unregister); +EXPORT_SYMBOL(zt_transcoder_alert); +EXPORT_SYMBOL(zt_transcoder_alloc); +EXPORT_SYMBOL(zt_transcoder_free); + +struct zt_transcoder *zt_transcoder_alloc(int numchans) +{ + struct zt_transcoder *ztc; + unsigned int x; + size_t size = sizeof(*ztc) + (sizeof(ztc->channels[0]) * numchans); + + if (!(ztc = kmalloc(size, GFP_KERNEL))) + return NULL; + + memset(ztc, 0, size); + strcpy(ztc->name, "<unspecified>"); + ztc->numchannels = numchans; + INIT_LIST_HEAD(&ztc->list); + for (x=0;x<ztc->numchannels;x++) { + init_waitqueue_head(&ztc->channels[x].ready); + ztc->channels[x].parent = ztc; + ztc->channels[x].offset = x; + ztc->channels[x].chan_built = 0; + ztc->channels[x].built_fmts = 0; + } + + return ztc; +} + +static int schluffen(wait_queue_head_t *q) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(q, &wait); + current->state = TASK_INTERRUPTIBLE; + + if (!signal_pending(current)) + schedule(); + + current->state = TASK_RUNNING; + remove_wait_queue(q, &wait); + + if (signal_pending(current)) + return -ERESTARTSYS; + + return 0; +} + +void zt_transcoder_free(struct zt_transcoder *ztc) +{ + kfree(ztc); +} + +int zt_transcoder_register(struct zt_transcoder *tc, struct module *owner) +{ + struct zt_transcoder *cur; + + spin_lock(&translock); + list_for_each_entry(cur, &trans, list) { + if (cur == tc) { + spin_unlock(&translock); + return -EBUSY; + } + } + + tc->owner = owner; + list_add(&tc->list, &trans); + printk("Registered codec translator '%s' with %d transcoders (srcs=%08x, dsts=%08x)\n", + tc->name, tc->numchannels, tc->srcfmts, tc->dstfmts); + spin_unlock(&translock); + + return 0; +} + +/* Unregister a transcoder */ +int zt_transcoder_unregister(struct zt_transcoder *tc) +{ + struct zt_transcoder *cur, *next; + + spin_lock(&translock); + list_for_each_entry_safe(cur, next, &trans, list) { + if (cur == tc) { + list_del_init(&cur->list); + break; + } + } + + if (!cur) { + spin_unlock(&translock); + return -EINVAL; + } + + printk("Unregistered codec translator '%s' with %d transcoders (srcs=%08x, dsts=%08x)\n", + tc->name, tc->numchannels, tc->srcfmts, tc->dstfmts); + spin_unlock(&translock); + + return 0; +} + +/* Alert a transcoder */ +int zt_transcoder_alert(struct zt_transcoder_channel *ztc) +{ + if (debug) + printk("ZT Transcoder Alert!\n"); + if (ztc->tch) + ztc->tch->busy = 0; + wake_up_interruptible(&ztc->ready); + + return 0; +} + +static int zt_tc_open(struct inode *inode, struct file *file) +{ + struct zt_transcoder_channel *ztc; + struct zt_transcode_header *zth; + struct page *page; + + if (!(ztc = kmalloc(sizeof(*ztc), GFP_KERNEL))) + return -ENOMEM; + + if (!(zth = kmalloc(sizeof(*zth), GFP_KERNEL))) { + kfree(ztc); + return -ENOMEM; + } + + memset(ztc, 0, sizeof(*ztc)); + memset(zth, 0, sizeof(*zth)); + ztc->transient = 1; + ztc->busy = 1; + ztc->tch = zth; + if (debug) + printk("Allocated Transcoder Channel, header is at %p!\n", zth); + zth->magic = ZT_TRANSCODE_MAGIC; + file->private_data = ztc; + for (page = virt_to_page(zth); + page < virt_to_page((unsigned long) zth + sizeof(*zth)); + page++) + SetPageReserved(page); + + return 0; +} + +static void ztc_release(struct zt_transcoder_channel *ztc) +{ + struct zt_transcode_header *zth = ztc->tch; + struct page *page; + + if (!ztc) + return; + + ztc->busy = 0; + + if (ztc->tch) { + for (page = virt_to_page(zth); + page < virt_to_page((unsigned long) zth + sizeof(*zth)); + page++) + ClearPageReserved(page); + kfree(ztc->tch); + } + + ztc->tch = NULL; + + if (ztc->have_reference) { + module_put(ztc->parent->owner); + ztc->have_reference = 0; + } + + if (ztc->transient); + kfree(ztc); + + if (debug) + printk("Released Transcoder!\n"); +} + +static int zt_tc_release(struct inode *inode, struct file *file) +{ + ztc_release(file->private_data); + + return 0; +} + +static int do_allocate(struct zt_transcoder_channel **ztc) +{ + struct zt_transcoder_channel *newztc = NULL, *origztc = NULL; + struct zt_transcode_header *zth = (*ztc)->tch; + struct zt_transcoder *tc; + unsigned int x; + unsigned int match = 0; + + if (((*ztc)->srcfmt != zth->srcfmt) || + ((*ztc)->dstfmt != zth->dstfmt)) { + /* Find new transcoder */ + spin_lock(&translock); + list_for_each_entry(tc, &trans, list) { + if (!(tc->srcfmts & zth->srcfmt)) + continue; + + if (!(tc->dstfmts & zth->dstfmt)) + continue; + + match = 1; + for (x = 0; x < tc->numchannels; x++) { + if (tc->channels[x].busy) + continue; + + if (tc->channels[x].chan_built && + ((zth->srcfmt | zth->dstfmt) != tc->channels[x].built_fmts)) + continue; + + if (!try_module_get(tc->owner)) + continue; + + newztc = &tc->channels[x]; + newztc->busy = 1; + newztc->have_reference = 1; + break; + } + } + spin_unlock(&translock); + + if (!newztc) + return match ? -EBUSY : -ENOSYS; + + /* Move transcoder header over */ + origztc = (*ztc); + (*ztc) = newztc; + (*ztc)->tch = origztc->tch; + origztc->tch = NULL; + (*ztc)->transient = 0; + ztc_release(origztc); + } + + /* Actually reset the transcoder channel */ + if ((*ztc)->parent && ((*ztc)->parent->operation)) + return (*ztc)->parent->operation((*ztc), ZT_TCOP_ALLOCATE); + + return -EINVAL; +} + +static int wait_busy(struct zt_transcoder_channel *ztc) +{ + int ret; + + for (;;) { + if (!(ztc->tch->busy)) + return 0; + if ((ret = schluffen(&ztc->ready))) + return ret; + } +} + +static int zt_tc_getinfo(unsigned long data) +{ + struct zt_transcode_info info; + unsigned int x; + struct zt_transcoder *tc; + + if (copy_from_user(&info, (struct zt_transcode_info *) data, sizeof(info))) + return -EFAULT; + + spin_lock(&translock); + x = info.tcnum; + list_for_each_entry(tc, &trans, list) { + if (!x) + break; + else + x--; + } + spin_unlock(&translock); + + if (x || !tc) + return -ENOSYS; + + strncpy(info.name, tc->name, sizeof(info.name) - 1); + info.numchannels = tc->numchannels; + info.srcfmts = tc->srcfmts; + info.dstfmts = tc->dstfmts; + + return copy_to_user((struct zt_transcode_info *) data, &info, sizeof(info)) ? -EFAULT : 0; +} + +static int zt_tc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data) +{ + int op; + int ret; + struct zt_transcoder_channel *ztc = file->private_data; + + if (cmd != ZT_TRANSCODE_OP) + return -ENOSYS; + + if (get_user(op, (int *) data)) + return -EFAULT; + + if (debug) + printk("ZT Transcode ioctl op = %d!\n", op); + + switch(op) { + case ZT_TCOP_GETINFO: + ret = zt_tc_getinfo(data); + break; + case ZT_TCOP_ALLOCATE: + ret = do_allocate(&ztc); + file->private_data = ztc; + break; + case ZT_TCOP_RELEASE: + ret = ztc->parent->operation(ztc, ZT_TCOP_RELEASE); + if (ztc->have_reference) { + module_put(ztc->parent->owner); + ztc->have_reference = 0; + } + break; + case ZT_TCOP_TRANSCODE: + if (!ztc->parent->operation) + return -EINVAL; + + ztc->tch->busy = 1; + if (!(ret = ztc->parent->operation(ztc, ZT_TCOP_TRANSCODE))) { + /* Wait for busy to go away if we are in blocking mode */ + if (!(file->f_flags & O_NONBLOCK)) { + if (!(ret = wait_busy(ztc))) + ret = ztc->errorstatus; + } + } else + ztc->tch->busy = 0; + break; + default: + ret = -ENOSYS; + } + + return ret; +} + +static int zt_tc_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct zt_transcoder_channel *ztc = file->private_data; + unsigned long physical; + int res; + + if (!ztc) + return -EINVAL; + + /* Do not allow an offset */ + if (vma->vm_pgoff) { + if (debug) + printk("zttranscode: Attempted to mmap with offset!\n"); + return -EINVAL; + } + + if ((vma->vm_end - vma->vm_start) != sizeof(struct zt_transcode_header)) { + if (debug) + printk("zttranscode: Attempted to mmap with size %d != %zd!\n", (int) (vma->vm_end - vma->vm_start), sizeof(struct zt_transcode_header)); + return -EINVAL; + } + + physical = (unsigned long) virt_to_phys(ztc->tch); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,10) + res = remap_pfn_range(vma, vma->vm_start, physical >> PAGE_SHIFT, sizeof(struct zt_transcode_header), PAGE_SHARED); +#else + #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + res = remap_page_range(vma->vm_start, physical, sizeof(struct zt_transcode_header), PAGE_SHARED); + #else + res = remap_page_range(vma, vma->vm_start, physical, sizeof(struct zt_transcode_header), PAGE_SHARED); + #endif +#endif + if (res) { + if (debug) + printk("zttranscode: remap failed!\n"); + return -EAGAIN; + } + + if (debug) + printk("zttranscode: successfully mapped transcoder!\n"); + + return 0; +} + +static unsigned int zt_tc_poll(struct file *file, struct poll_table_struct *wait_table) +{ + struct zt_transcoder_channel *ztc = file->private_data; + + if (!ztc) + return -EINVAL; + + poll_wait(file, &ztc->ready, wait_table); + + return ztc->tch->busy ? 0 : POLLPRI; +} + +struct file_operations __zt_transcode_fops = { + owner: THIS_MODULE, + llseek: NULL, + open: zt_tc_open, + release: zt_tc_release, + ioctl: zt_tc_ioctl, + read: NULL, + write: NULL, + poll: zt_tc_poll, + mmap: zt_tc_mmap, + flush: NULL, + fsync: NULL, + fasync: NULL, +}; + +int zttranscode_init(void) +{ + if (zt_transcode_fops) { + printk("Whoa, zt_transcode_fops already set?!\n"); + return -EBUSY; + } + + zt_transcode_fops = &__zt_transcode_fops; + printk("Zaptel Transcoder support loaded\n"); + + return 0; +} + +void zttranscode_cleanup(void) +{ + zt_transcode_fops = NULL; + printk("Zaptel Transcoder support unloaded\n"); +} + +#ifdef LINUX26 +module_param(debug, int, S_IRUGO | S_IWUSR); +#else +MODULE_PARM(debug, "i"); +#endif +MODULE_DESCRIPTION("Zaptel Transcoder Support"); +MODULE_AUTHOR("Mark Spencer <markster@digium.com>"); +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif + +module_init(zttranscode_init); +module_exit(zttranscode_cleanup); |