diff options
Diffstat (limited to 'kernel/zttranscode.c')
-rw-r--r-- | kernel/zttranscode.c | 564 |
1 files changed, 269 insertions, 295 deletions
diff --git a/kernel/zttranscode.c b/kernel/zttranscode.c index e44895b..23a5dc8 100644 --- a/kernel/zttranscode.c +++ b/kernel/zttranscode.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. * @@ -35,20 +35,11 @@ #include <linux/mm.h> #include <linux/page-flags.h> #include <asm/io.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 struct zt_transcoder *trans; +#include <zaptel.h> + +static int debug; +LIST_HEAD(trans); static spinlock_t translock = SPIN_LOCK_UNLOCKED; EXPORT_SYMBOL(zt_transcoder_register); @@ -59,387 +50,375 @@ EXPORT_SYMBOL(zt_transcoder_free); struct zt_transcoder *zt_transcoder_alloc(int numchans) { - struct zt_transcoder *ztc; + struct zt_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(!zt_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 zt_transcode layer. */ + memcpy(&tc->fops, zt_transcode_fops, sizeof(*zt_transcode_fops)); + return tc; } -static int schluffen(wait_queue_head_t *q) +void zt_transcoder_free(struct zt_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 zt_transcoder_free(struct zt_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 zt_transcoder_register(struct zt_transcoder *tc) { - struct zt_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("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 zt_transcoder_unregister(struct zt_transcoder *tc) { - struct zt_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("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 zt_transcoder_alert(struct zt_transcoder_channel *ztc) +int zt_transcoder_alert(struct zt_transcoder_channel *chan) { - if (debug) - printk("ZT Transcoder Alert!\n"); - if (ztc->tch) - ztc->tch->status &= ~ZT_TC_FLAG_BUSY; - wake_up_interruptible(&ztc->ready); - + wake_up_interruptible(&chan->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 | GFP_DMA))) { - kfree(ztc); - return -ENOMEM; - } - - memset(ztc, 0, sizeof(*ztc)); - memset(zth, 0, sizeof(*zth)); - ztc->flags = ZT_TC_FLAG_TRANSIENT | ZT_TC_FLAG_BUSY; - 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); - + const struct file_operations *original_fops; + BUG_ON(!zt_transcode_fops); + original_fops = file->f_op; + file->f_op = zt_transcode_fops; + file->private_data = NULL; + /* Under normal operation, this releases the reference on the Zaptel + * module that was created when the file was opened. zt_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 zt_transcoder_channel *ztc) +static void dtc_release(struct zt_transcoder_channel *chan) { - struct zt_transcode_header *zth = ztc->tch; - struct page *page; - - if (!ztc) - return; - - ztc->flags &= ~(ZT_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 & ZT_TC_FLAG_TRANSIENT) - kfree(ztc); - if (debug) - printk("Released Transcoder!\n"); + zt_tc_clear_busy(chan); } static int zt_tc_release(struct inode *inode, struct file *file) { - ztc_release(file->private_data); - + struct zt_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 zt_transcoder_channel **ztc) +/* Find a free channel on the transcoder and mark it busy. */ +static inline struct zt_transcoder_channel * +get_free_channel(struct zt_transcoder *tc) + +{ + struct zt_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 (!zt_tc_is_busy(chan)) { + zt_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 zt_transcoder_channel * +__find_free_channel(struct list_head *list, const struct zt_transcoder_formats *fmts) { - struct zt_transcoder_channel *newztc = NULL, *origztc = NULL; - struct zt_transcode_header *zth = (*ztc)->tch; struct zt_transcoder *tc; - unsigned int x; + struct zt_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 & ZT_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 = ZT_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*)((long)((match) ? -EBUSY : -ENODEV)); +} - if (!newztc) - return match ? -EBUSY : -ENOSYS; +static long zt_tc_allocate(struct file *file, unsigned long data) +{ + struct zt_transcoder_channel *chan = NULL; + struct zt_transcoder_formats fmts; + + if (copy_from_user(&fmts, + (struct zt_transcoder_formats*) data, sizeof(fmts))) { + return -EFAULT; + } + + 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 & ~(ZT_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), ZT_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 zt_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 & ZT_TC_FLAG_BUSY)) - return 0; - if ((ret = schluffen(&ztc->ready))) - return ret; + if (file->f_flags & O_NONBLOCK) { + zt_tc_set_nonblock(chan); + } else { + zt_tc_clear_nonblock(chan); } + + /* Actually reset the transcoder channel */ + if (chan->parent->allocate) + return chan->parent->allocate(chan); + + return -EINVAL; } -static int zt_tc_getinfo(unsigned long data) +static long zt_tc_getinfo(unsigned long data) { - struct zt_transcode_info info; + struct zt_transcoder_info info; unsigned int x; - struct zt_transcoder *tc; + struct zt_transcoder *cur; + struct zt_transcoder *tc = NULL; - if (copy_from_user(&info, (struct zt_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; + } zap_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 zt_transcode_info *) data, &info, sizeof(info)) ? -EFAULT : 0; + return copy_to_user((void *) data, &info, sizeof(info)) ? -EFAULT : 0; } -static int zt_tc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data) +static ssize_t zt_tc_write(struct file *file, __user const char *usrbuf, size_t count, loff_t *ppos) { - int op; - int ret; - struct zt_transcoder_channel *ztc = file->private_data; - - if (cmd != ZT_TRANSCODE_OP) + if (file->private_data) { + /* file->private_data will not be NULL if ZT_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("ZT Transcode ioctl op = %d!\n", op); - - switch(op) { - case ZT_TCOP_GETINFO: - ret = zt_tc_getinfo(data); - break; - case ZT_TCOP_ALLOCATE: - /* Reset transcoder, possibly changing who we point to */ - ret = do_reset(&ztc); - file->private_data = ztc; - break; - case ZT_TCOP_RELEASE: - ret = ztc->parent->operation(ztc, ZT_TCOP_RELEASE); - break; - case ZT_TCOP_TEST: - ret = ztc->parent->operation(ztc, ZT_TCOP_TEST); - break; - case ZT_TCOP_TRANSCODE: - if (!ztc->parent->operation) - return -EINVAL; - - ztc->tch->status |= ZT_TC_FLAG_BUSY; - if (!(ret = ztc->parent->operation(ztc, ZT_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 &= ~ZT_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 zt_tc_mmap(struct file *file, struct vm_area_struct *vma) +static ssize_t zt_tc_read(struct file *file, __user char *usrbuf, size_t count, loff_t *ppos) { - 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"); + if (file->private_data) { + /* file->private_data will not be NULL if ZT_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 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)); +static long zt_tc_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long data) +{ + switch (cmd) { + case ZT_TC_ALLOCATE: + return zt_tc_allocate(file, data); + case ZT_TC_GETINFO: + return zt_tc_getinfo(data); + case ZT_TRANSCODE_OP: + /* This is a deprecated call from the previous transcoder + * interface, which was all routed through the zt_ioctl in + * zaptel-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: ZT_TRANSCODE_OP is no longer " \ + "supported. Please call ZT_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 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; - } + default: + return -EINVAL; + }; +} - if (debug) - printk("zttranscode: successfully mapped transcoder!\n"); +static int zt_tc_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data) +{ + return (int)zt_tc_unlocked_ioctl(file, cmd, data); +} - return 0; +static int zt_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 zt_tc_poll(struct file *file, struct poll_table_struct *wait_table) { - struct zt_transcoder_channel *ztc = file->private_data; + int ret; + struct zt_transcoder_channel *chan = file->private_data; - if (!ztc) + if (!chan) { + /* This is because the ZT_TC_ALLOCATE ioctl was not called + * before calling poll, which is invalid. */ return -EINVAL; + } - poll_wait(file, &ztc->ready, wait_table); - return ztc->tch->status & ZT_TC_FLAG_BUSY ? 0 : POLLPRI; + poll_wait(file, &chan->ready, wait_table); + + ret = zt_tc_is_busy(chan) ? 0 : POLLPRI; + ret |= zt_tc_is_built(chan) ? POLLOUT : 0; + ret |= zt_tc_is_data_waiting(chan) ? POLLIN : 0; + return ret; } static struct file_operations __zt_transcode_fops = { - owner: THIS_MODULE, - llseek: NULL, - open: zt_tc_open, + owner: THIS_MODULE, + 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, + ioctl: zt_tc_ioctl, + read: zt_tc_read, + write: zt_tc_write, + poll: zt_tc_poll, + mmap: zt_tc_mmap, +#if HAVE_UNLOCKED_IOCTL + unlocked_ioctl: zt_tc_unlocked_ioctl, +#endif }; static struct zt_chardev transcode_chardev = { @@ -447,12 +426,12 @@ static struct zt_chardev transcode_chardev = { .minor = 250, }; -int zttranscode_init(void) +int zt_transcode_init(void) { int res; if (zt_transcode_fops) { - printk("Whoa, zt_transcode_fops already set?!\n"); + printk(KERN_WARNING "zt_transcode_fops already set.\n"); return -EBUSY; } @@ -461,30 +440,25 @@ int zttranscode_init(void) if ((res = zt_register_chardev(&transcode_chardev))) return res; - printk("Zaptel Transcoder support loaded\n"); - + printk(KERN_INFO "%s: Loaded.\n", THIS_MODULE->name); return 0; } -void zttranscode_cleanup(void) +void zt_transcode_cleanup(void) { zt_unregister_chardev(&transcode_chardev); zt_transcode_fops = NULL; - printk("Zaptel Transcoder support unloaded\n"); + printk(KERN_DEBUG "%s: Unloaded.\n", THIS_MODULE->name); } -#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); +module_init(zt_transcode_init); +module_exit(zt_transcode_cleanup); |