diff options
Diffstat (limited to 'drivers/dahdi/dahdi_transcode.c')
-rw-r--r-- | drivers/dahdi/dahdi_transcode.c | 554 |
1 files changed, 274 insertions, 280 deletions
diff --git a/drivers/dahdi/dahdi_transcode.c b/drivers/dahdi/dahdi_transcode.c index ad222bc..d7c1d66 100644 --- a/drivers/dahdi/dahdi_transcode.c +++ b/drivers/dahdi/dahdi_transcode.c @@ -3,7 +3,7 @@ * * Written by Mark Spencer <markster@digium.com> * - * Copyright (C) 2006-2007, Digium, Inc. + * Copyright (C) 2006-2008, Digium, Inc. * * All rights reserved. * @@ -21,6 +21,7 @@ * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * + * */ #include <linux/kernel.h> @@ -35,13 +36,18 @@ #include <linux/vmalloc.h> #include <linux/mm.h> #include <linux/page-flags.h> -#include <linux/moduleparam.h> #include <asm/io.h> - +#ifdef CONFIG_DEVFS_FS +#include <linux/devfs_fs_kernel.h> +#endif +#ifdef STANDALONE_ZAPATA +#include "dahdi/kernel.h" +#else #include <dahdi/kernel.h> +#endif -static int debug = 0; -static struct dahdi_transcoder *trans; +static int debug; +LIST_HEAD(trans); static spinlock_t translock = SPIN_LOCK_UNLOCKED; EXPORT_SYMBOL(dahdi_transcoder_register); @@ -52,387 +58,375 @@ EXPORT_SYMBOL(dahdi_transcoder_free); struct dahdi_transcoder *dahdi_transcoder_alloc(int numchans) { - struct dahdi_transcoder *ztc; + struct dahdi_transcoder *tc; unsigned int x; - size_t size = sizeof(*ztc) + (sizeof(ztc->channels[0]) * numchans); + size_t size = sizeof(*tc) + (sizeof(tc->channels[0]) * numchans); - if (!(ztc = kmalloc(size, GFP_KERNEL))) + if (!(tc = kmalloc(size, GFP_KERNEL))) return NULL; - memset(ztc, 0, size); - strcpy(ztc->name, "<unspecified>"); - ztc->numchannels = numchans; - 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; + memset(tc, 0, size); + strcpy(tc->name, "<unspecified>"); + tc->numchannels = numchans; + for (x=0;x<tc->numchannels;x++) { + init_waitqueue_head(&tc->channels[x].ready); + INIT_LIST_HEAD(&tc->node); + tc->channels[x].parent = tc; } - return ztc; + WARN_ON(!dahdi_transcode_fops); + /* Individual transcoders should supply their own file_operations for + * write and read. But they will by default use the file_operations + * provided by the dahdi_transcode layer. */ + memcpy(&tc->fops, dahdi_transcode_fops, sizeof(*dahdi_transcode_fops)); + return tc; } -static int schluffen(wait_queue_head_t *q) +void dahdi_transcoder_free(struct dahdi_transcoder *tc) { - 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; + kfree(tc); } -void dahdi_transcoder_free(struct dahdi_transcoder *ztc) +/* Returns 1 if the item is on the list pointed to by head, otherwise, returns + * 0 */ +static int is_on_list(struct list_head *entry, struct list_head *head) { - kfree(ztc); + struct list_head *cur; + list_for_each(cur, head) { + if (cur == entry) return 1; + } + return 0; } /* Register a transcoder */ int dahdi_transcoder_register(struct dahdi_transcoder *tc) { - struct dahdi_transcoder *cur; - int res = -EBUSY; - spin_lock(&translock); - for (cur = trans; cur; cur = cur->next) { - if (cur == tc) { - spin_unlock(&translock); - return res; - } - } - - tc->next = trans; - trans = tc; - printk(KERN_INFO "Registered codec translator '%s' with %d transcoders (srcs=%08x, dsts=%08x)\n", - tc->name, tc->numchannels, tc->srcfmts, tc->dstfmts); - res = 0; + BUG_ON(is_on_list(&tc->node, &trans)); + list_add_tail(&tc->node, &trans); spin_unlock(&translock); - return res; + printk(KERN_INFO "%s: Registered codec translator '%s' " \ + "with %d transcoders (srcs=%08x, dsts=%08x)\n", + THIS_MODULE->name, tc->name, tc->numchannels, + tc->srcfmts, tc->dstfmts); + + return 0; } /* Unregister a transcoder */ int dahdi_transcoder_unregister(struct dahdi_transcoder *tc) { - struct dahdi_transcoder *cur, *prev; int res = -EINVAL; - spin_lock(&translock); - for (cur = trans, prev = NULL; cur; prev = cur, cur = cur->next) { - if (cur == tc) - break; - } + /* \todo Perhaps we should check to make sure there isn't a channel + * that is still in use? */ - if (!cur) { + spin_lock(&translock); + if (!is_on_list(&tc->node, &trans)) { spin_unlock(&translock); - return res; + printk(KERN_WARNING "%s: Failed to unregister %s, which is " \ + "not currently registerd.\n", THIS_MODULE->name, tc->name); + return -EINVAL; } + list_del_init(&tc->node); + spin_unlock(&translock); - if (prev) - prev->next = tc->next; - else - trans = tc->next; - tc->next = NULL; - printk(KERN_INFO "Unregistered codec translator '%s' with %d transcoders (srcs=%08x, dsts=%08x)\n", + printk(KERN_INFO "Unregistered codec translator '%s' with %d " \ + "transcoders (srcs=%08x, dsts=%08x)\n", tc->name, tc->numchannels, tc->srcfmts, tc->dstfmts); res = 0; - spin_unlock(&translock); return res; } /* Alert a transcoder */ -int dahdi_transcoder_alert(struct dahdi_transcoder_channel *ztc) +int dahdi_transcoder_alert(struct dahdi_transcoder_channel *chan) { - if (debug) - printk(KERN_DEBUG "DAHDI Transcoder Alert!\n"); - if (ztc->tch) - ztc->tch->status &= ~DAHDI_TC_FLAG_BUSY; - wake_up_interruptible(&ztc->ready); - + wake_up_interruptible(&chan->ready); return 0; } static int dahdi_tc_open(struct inode *inode, struct file *file) { - struct dahdi_transcoder_channel *ztc; - struct dahdi_transcode_header *zth; - struct page *page; - - if (!(ztc = kmalloc(sizeof(*ztc), GFP_KERNEL))) - return -ENOMEM; - - if (!(zth = kmalloc(sizeof(*zth), GFP_KERNEL | GFP_DMA))) { - kfree(ztc); - return -ENOMEM; - } - - memset(ztc, 0, sizeof(*ztc)); - memset(zth, 0, sizeof(*zth)); - ztc->flags = DAHDI_TC_FLAG_TRANSIENT | DAHDI_TC_FLAG_BUSY; - ztc->tch = zth; - if (debug) - printk(KERN_DEBUG "Allocated Transcoder Channel, header is at %p!\n", zth); - zth->magic = DAHDI_TRANSCODE_MAGIC; - file->private_data = ztc; - for (page = virt_to_page(zth); - page < virt_to_page((unsigned long) zth + sizeof(*zth)); - page++) - SetPageReserved(page); - + const struct file_operations *original_fops; + BUG_ON(!dahdi_transcode_fops); + original_fops = file->f_op; + file->f_op = dahdi_transcode_fops; + file->private_data = NULL; + /* Under normal operation, this releases the reference on the DAHDI + * module that was created when the file was opened. dahdi_open is + * responsible for taking a reference out on this module before + * calling this function. */ + module_put(original_fops->owner); return 0; } -static void ztc_release(struct dahdi_transcoder_channel *ztc) +static void dtc_release(struct dahdi_transcoder_channel *chan) { - struct dahdi_transcode_header *zth = ztc->tch; - struct page *page; - - if (!ztc) - return; - - ztc->flags &= ~(DAHDI_TC_FLAG_BUSY); - - if(ztc->tch) { - for (page = virt_to_page(zth); - page < virt_to_page((unsigned long) zth + sizeof(*zth)); - page++) - ClearPageReserved(page); - kfree(ztc->tch); + BUG_ON(!chan); + if (chan->parent && chan->parent->release) { + chan->parent->release(chan); } - - ztc->tch = NULL; - /* Actually reset the transcoder channel */ - if (ztc->flags & DAHDI_TC_FLAG_TRANSIENT) - kfree(ztc); - if (debug) - printk(KERN_DEBUG "Released Transcoder!\n"); + dahdi_tc_clear_busy(chan); } static int dahdi_tc_release(struct inode *inode, struct file *file) { - ztc_release(file->private_data); - + struct dahdi_transcoder_channel *chan = file->private_data; + /* There will not be a transcoder channel associated with this file if + * the ALLOCATE ioctl never succeeded. + */ + if (chan) { + dtc_release(chan); + } return 0; } -static int do_reset(struct dahdi_transcoder_channel **ztc) +/* Find a free channel on the transcoder and mark it busy. */ +static inline struct dahdi_transcoder_channel * +get_free_channel(struct dahdi_transcoder *tc) + +{ + struct dahdi_transcoder_channel *chan; + int i; + /* Should be called with the translock held. */ + WARN_ON(!spin_is_locked(&translock)); + + for (i = 0; i < tc->numchannels; i++) { + chan = &tc->channels[i]; + if (!dahdi_tc_is_busy(chan)) { + dahdi_tc_set_busy(chan); + return chan; + } + } + return NULL; +} + +/* Search the list for a transcoder that supports the specified format, and + * allocate and return an available channel on it. + * + * Returns either a pointer to the allocated channel, -EBUSY if the format is + * supported but all the channels are busy, or -ENODEV if there are not any + * transcoders that support the formats. + */ +static struct dahdi_transcoder_channel * +__find_free_channel(struct list_head *list, const struct dahdi_transcoder_formats *fmts) { - struct dahdi_transcoder_channel *newztc = NULL, *origztc = NULL; - struct dahdi_transcode_header *zth = (*ztc)->tch; struct dahdi_transcoder *tc; - unsigned int x; + struct dahdi_transcoder_channel *chan = NULL; unsigned int match = 0; - if (((*ztc)->srcfmt != zth->srcfmt) || - ((*ztc)->dstfmt != zth->dstfmt)) { - /* Find new transcoder */ - spin_lock(&translock); - for (tc = trans; tc && !newztc; tc = tc->next) { - 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].flags & DAHDI_TC_FLAG_BUSY) - continue; - if ((tc->channels[x].chan_built) && ((zth->srcfmt | zth->dstfmt) != tc->channels[x].built_fmts)) - continue; - - newztc = &tc->channels[x]; - newztc->flags = DAHDI_TC_FLAG_BUSY; - break; + list_for_each_entry(tc, list, node) { + if ((tc->dstfmts & fmts->dstfmt)) { + /* We found a transcoder that can handle our formats. + * Now look for an available channel. */ + match = 1; + if ((chan = get_free_channel(tc))) { + /* transcoder tc has a free channel. In order + * to spread the load among available + * transcoders (when there are more than one + * transcoder in the system) we'll move tc + * to the end of the list. */ + list_move_tail(&tc->node, list); + return chan; } } - spin_unlock(&translock); + } + return (void*)((match) ? -EBUSY : -ENODEV); +} + +static long dahdi_tc_allocate(struct file *file, unsigned long data) +{ + struct dahdi_transcoder_channel *chan = NULL; + struct dahdi_transcoder_formats fmts; + + if (copy_from_user(&fmts, + (struct dahdi_transcoder_formats*) data, sizeof(fmts))) { + return -EFAULT; + } - if (!newztc) - return match ? -EBUSY : -ENOSYS; + spin_lock(&translock); + chan = __find_free_channel(&trans, &fmts); + spin_unlock(&translock); - /* Move transcoder header over */ - origztc = (*ztc); - (*ztc) = newztc; - (*ztc)->tch = origztc->tch; - origztc->tch = NULL; - (*ztc)->flags |= (origztc->flags & ~(DAHDI_TC_FLAG_TRANSIENT)); - ztc_release(origztc); + if (IS_ERR(chan)) { + return PTR_ERR(chan); } - /* Actually reset the transcoder channel */ - if ((*ztc)->parent && ((*ztc)->parent->operation)) - return (*ztc)->parent->operation((*ztc), DAHDI_TCOP_ALLOCATE); + /* Every transcoder channel must be associated with a parent + * transcoder. */ + BUG_ON(!chan->parent); - return -EINVAL; -} + chan->srcfmt = fmts.srcfmt; + chan->dstfmt = fmts.dstfmt; -static int wait_busy(struct dahdi_transcoder_channel *ztc) -{ - int ret; + if (file->private_data) { + /* This open file is moving to a new channel. Cleanup and + * close the old channel here. */ + dtc_release(file->private_data); + } + + file->private_data = chan; + if (chan->parent->fops.owner != file->f_op->owner) { + if (!try_module_get(chan->parent->fops.owner)) { + /* Failed to get a reference on the driver for the + * actual transcoding hardware. */ + return -EINVAL; + } + /* Release the reference on the existing driver. */ + module_put(file->f_op->owner); + file->f_op = &chan->parent->fops; + } - for (;;) { - if (!(ztc->tch->status & DAHDI_TC_FLAG_BUSY)) - return 0; - if ((ret = schluffen(&ztc->ready))) - return ret; + if (file->f_flags & O_NONBLOCK) { + dahdi_tc_set_nonblock(chan); + } else { + dahdi_tc_clear_nonblock(chan); } + + /* Actually reset the transcoder channel */ + if (chan->parent->allocate) + return chan->parent->allocate(chan); + + return -EINVAL; } -static int dahdi_tc_getinfo(unsigned long data) +static long dahdi_tc_getinfo(unsigned long data) { - struct dahdi_transcode_info info; + struct dahdi_transcoder_info info; unsigned int x; - struct dahdi_transcoder *tc; + struct dahdi_transcoder *cur; + struct dahdi_transcoder *tc = NULL; - if (copy_from_user(&info, (struct dahdi_transcode_info *) data, sizeof(info))) + if (copy_from_user(&info, (const void *) data, sizeof(info))) { return -EFAULT; + } + x = 0; spin_lock(&translock); - for (tc = trans, x = info.tcnum; tc && x; tc = tc->next, x--); + list_for_each_entry(cur, &trans, node) { + if (x++ == info.tcnum) { + tc = cur; + break; + } + } spin_unlock(&translock); - if (!tc) + if (!tc) { return -ENOSYS; + } dahdi_copy_string(info.name, tc->name, sizeof(info.name)); info.numchannels = tc->numchannels; info.srcfmts = tc->srcfmts; info.dstfmts = tc->dstfmts; - return copy_to_user((struct dahdi_transcode_info *) data, &info, sizeof(info)) ? -EFAULT : 0; + return copy_to_user((void *) data, &info, sizeof(info)) ? -EFAULT : 0; } -static int dahdi_tc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data) +static ssize_t dahdi_tc_write(struct file *file, __user const char *usrbuf, size_t count, loff_t *ppos) { - int op; - int ret; - struct dahdi_transcoder_channel *ztc = file->private_data; - - if (cmd != DAHDI_TRANSCODE_OP) + if (file->private_data) { + /* file->private_data will not be NULL if DAHDI_TC_ALLOCATE was + * called, and therefore indicates that the transcoder driver + * did not export a read function. */ + WARN_ON(1); return -ENOSYS; - - if (get_user(op, (int *) data)) - return -EFAULT; - - if (debug) - printk(KERN_DEBUG "DAHDI Transcode ioctl op = %d!\n", op); - - switch(op) { - case DAHDI_TCOP_GETINFO: - ret = dahdi_tc_getinfo(data); - break; - case DAHDI_TCOP_ALLOCATE: - /* Reset transcoder, possibly changing who we point to */ - ret = do_reset(&ztc); - file->private_data = ztc; - break; - case DAHDI_TCOP_RELEASE: - ret = ztc->parent->operation(ztc, DAHDI_TCOP_RELEASE); - break; - case DAHDI_TCOP_TEST: - ret = ztc->parent->operation(ztc, DAHDI_TCOP_TEST); - break; - case DAHDI_TCOP_TRANSCODE: - if (!ztc->parent->operation) - return -EINVAL; - - ztc->tch->status |= DAHDI_TC_FLAG_BUSY; - if (!(ret = ztc->parent->operation(ztc, DAHDI_TCOP_TRANSCODE))) { - /* Wait for busy to go away if we're not non-blocking */ - if (!(file->f_flags & O_NONBLOCK)) { - if (!(ret = wait_busy(ztc))) - ret = ztc->errorstatus; - } - } else - ztc->tch->status &= ~DAHDI_TC_FLAG_BUSY; - break; - default: - ret = -ENOSYS; + } else { + printk(KERN_INFO "%s: Attempt to write to unallocated " \ + "channel.\n", THIS_MODULE->name); + return -EINVAL; } - - return ret; } -static int dahdi_tc_mmap(struct file *file, struct vm_area_struct *vma) +static ssize_t dahdi_tc_read(struct file *file, __user char *usrbuf, size_t count, loff_t *ppos) { - struct dahdi_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(KERN_DEBUG "zttranscode: Attempted to mmap with offset!\n"); + if (file->private_data) { + /* file->private_data will not be NULL if DAHDI_TC_ALLOCATE was + * called, and therefore indicates that the transcoder driver + * did not export a write function. */ + WARN_ON(1); + return -ENOSYS; + } else { + printk(KERN_INFO "%s: Attempt to read from unallocated " \ + "channel.\n", THIS_MODULE->name); return -EINVAL; } +} - if ((vma->vm_end - vma->vm_start) != sizeof(struct dahdi_transcode_header)) { - if (debug) - printk(KERN_DEBUG "zttranscode: Attempted to mmap with size %d != %zd!\n", (int) (vma->vm_end - vma->vm_start), sizeof(struct dahdi_transcode_header)); +static long dahdi_tc_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long data) +{ + switch (cmd) { + case DAHDI_TC_ALLOCATE: + return dahdi_tc_allocate(file, data); + case DAHDI_TC_GETINFO: + return dahdi_tc_getinfo(data); + case DAHDI_TRANSCODE_OP: + /* This is a deprecated call from the previous transcoder + * interface, which was all routed through the dahdi_ioctl in + * dahdi-base.c, and this ioctl request was used to indicate + * that the call should be forwarded to this function. Now + * when the file is opened, the f_ops pointer is updated to + * point directly to this function, and we don't need a + * general indication that the ioctl is destined for the + * transcoder. + * + * I'm keeping this ioctl here in order to explain why there + * might be a hole in the ioctl numbering scheme in the header + * files. + */ + printk(KERN_WARNING "%s: DAHDI_TRANSCODE_OP is no longer " \ + "supported. Please call DAHDI_TC ioctls directly.\n", + THIS_MODULE->name); 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 dahdi_transcode_header), PAGE_SHARED); -#else - #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) - res = remap_page_range(vma->vm_start, physical, sizeof(struct dahdi_transcode_header), PAGE_SHARED); - #else - res = remap_page_range(vma, vma->vm_start, physical, sizeof(struct dahdi_transcode_header), PAGE_SHARED); - #endif -#endif - if (res) { - if (debug) - printk(KERN_DEBUG "zttranscode: remap failed!\n"); - return -EAGAIN; - } + default: + return -EINVAL; + }; +} - if (debug) - printk(KERN_DEBUG "zttranscode: successfully mapped transcoder!\n"); +static int dahdi_tc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data) +{ + return (int)dahdi_tc_unlocked_ioctl(file, cmd, data); +} - return 0; +static int dahdi_tc_mmap(struct file *file, struct vm_area_struct *vma) +{ + printk(KERN_ERR "%s: mmap interface deprecated.\n", THIS_MODULE->name); + return -ENOSYS; } static unsigned int dahdi_tc_poll(struct file *file, struct poll_table_struct *wait_table) { - struct dahdi_transcoder_channel *ztc = file->private_data; + int ret; + struct dahdi_transcoder_channel *chan = file->private_data; - if (!ztc) + if (!chan) { + /* This is because the DAHDI_TC_ALLOCATE ioctl was not called + * before calling poll, which is invalid. */ return -EINVAL; + } + + poll_wait(file, &chan->ready, wait_table); - poll_wait(file, &ztc->ready, wait_table); - return ztc->tch->status & DAHDI_TC_FLAG_BUSY ? 0 : POLLPRI; + ret = dahdi_tc_is_busy(chan) ? 0 : POLLPRI; + ret |= dahdi_tc_is_built(chan) ? POLLOUT : 0; + ret |= dahdi_tc_is_data_waiting(chan) ? POLLIN : 0; + return ret; } static struct file_operations __dahdi_transcode_fops = { - owner: THIS_MODULE, - llseek: NULL, - open: dahdi_tc_open, + owner: THIS_MODULE, + open: dahdi_tc_open, release: dahdi_tc_release, - ioctl: dahdi_tc_ioctl, - read: NULL, - write: NULL, - poll: dahdi_tc_poll, - mmap: dahdi_tc_mmap, - flush: NULL, - fsync: NULL, - fasync: NULL, + ioctl: dahdi_tc_ioctl, + read: dahdi_tc_read, + write: dahdi_tc_write, + poll: dahdi_tc_poll, + mmap: dahdi_tc_mmap, +#if HAVE_UNLOCKED_IOCTL + unlocked_ioctl: dahdi_tc_unlocked_ioctl, +#endif }; static struct dahdi_chardev transcode_chardev = { @@ -445,7 +439,7 @@ int dahdi_transcode_init(void) int res; if (dahdi_transcode_fops) { - printk(KERN_NOTICE "Whoa, dahdi_transcode_fops already set?!\n"); + printk(KERN_WARNING "dahdi_transcode_fops already set.\n"); return -EBUSY; } @@ -454,8 +448,7 @@ int dahdi_transcode_init(void) if ((res = dahdi_register_chardev(&transcode_chardev))) return res; - printk(KERN_INFO "DAHDI Transcoder support loaded\n"); - + printk(KERN_INFO "%s: Loaded.\n", THIS_MODULE->name); return 0; } @@ -465,14 +458,15 @@ void dahdi_transcode_cleanup(void) dahdi_transcode_fops = NULL; - printk(KERN_INFO "DAHDI Transcoder support unloaded\n"); + printk(KERN_DEBUG "%s: Unloaded.\n", THIS_MODULE->name); } module_param(debug, int, S_IRUGO | S_IWUSR); - MODULE_DESCRIPTION("DAHDI Transcoder Support"); MODULE_AUTHOR("Mark Spencer <markster@digium.com>"); -MODULE_LICENSE("GPL v2"); +#ifdef MODULE_LICENSE +MODULE_LICENSE("GPL"); +#endif module_init(dahdi_transcode_init); module_exit(dahdi_transcode_cleanup); |