/* * Transcoder Interface for DAHDI * * Written by Mark Spencer * * Copyright (C) 2006-2008, Digium, Inc. * * All rights reserved. * */ /* * See http://www.asterisk.org for more information about * the Asterisk project. Please do not directly contact * any of the maintainers of this project for assistance; * the project provides a web site, mailing lists and IRC * channels for your use. * * This program is free software, distributed under the terms of * the GNU General Public License Version 2 as published by the * Free Software Foundation. See the LICENSE file included with * this program for more details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include static int debug; /* The registration list contains transcoders in the order in which they were * registered. */ static LIST_HEAD(registration_list); /* The active list is sorted by the most recently used transcoder is last. This * is used as a simplistic way to spread the load amongst the different hardware * transcoders in the system. */ static LIST_HEAD(active_list); static DEFINE_SPINLOCK(translock); EXPORT_SYMBOL(dahdi_transcoder_register); EXPORT_SYMBOL(dahdi_transcoder_unregister); EXPORT_SYMBOL(dahdi_transcoder_alert); EXPORT_SYMBOL(dahdi_transcoder_alloc); EXPORT_SYMBOL(dahdi_transcoder_free); struct dahdi_transcoder *dahdi_transcoder_alloc(int numchans) { struct dahdi_transcoder *tc; unsigned int x; size_t size = sizeof(*tc) + (sizeof(tc->channels[0]) * numchans); if (!(tc = kmalloc(size, GFP_KERNEL))) return NULL; memset(tc, 0, size); strcpy(tc->name, ""); INIT_LIST_HEAD(&tc->registration_list_node); INIT_LIST_HEAD(&tc->active_list_node); tc->numchannels = numchans; for (x=0; x < tc->numchannels; x++) { init_waitqueue_head(&tc->channels[x].ready); tc->channels[x].parent = tc; } 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; } void dahdi_transcoder_free(struct dahdi_transcoder *tc) { kfree(tc); } /* 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) { 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) { spin_lock(&translock); BUG_ON(is_on_list(&tc->registration_list_node, ®istration_list)); list_add_tail(&tc->registration_list_node, ®istration_list); list_add_tail(&tc->active_list_node, &active_list); spin_unlock(&translock); 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) { int res = -EINVAL; /* \todo Perhaps we should check to make sure there isn't a channel * that is still in use? */ spin_lock(&translock); if (!is_on_list(&tc->registration_list_node, ®istration_list)) { spin_unlock(&translock); printk(KERN_WARNING "%s: Failed to unregister %s, which is " \ "not currently registered.\n", THIS_MODULE->name, tc->name); return -EINVAL; } list_del_init(&tc->registration_list_node); list_del_init(&tc->active_list_node); spin_unlock(&translock); 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; return res; } /* Alert a transcoder */ int dahdi_transcoder_alert(struct dahdi_transcoder_channel *chan) { wake_up_interruptible(&chan->ready); return 0; } static int dahdi_tc_open(struct inode *inode, struct file *file) { 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 dtc_release(struct dahdi_transcoder_channel *chan) { BUG_ON(!chan); if (chan->parent && chan->parent->release) { chan->parent->release(chan); } else { dahdi_tc_clear_busy(chan); } } static int dahdi_tc_release(struct inode *inode, struct file *file) { 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; } /* Find a free channel on the transcoder and mark it busy. */ static inline struct dahdi_transcoder_channel * get_free_channel(struct dahdi_transcoder *tc, const struct dahdi_transcoder_formats *fmts) { struct dahdi_transcoder_channel *chan; int i; /* Should be called with the translock held. */ #ifdef CONFIG_SMP WARN_ON(!spin_is_locked(&translock)); #endif for (i = 0; i < tc->numchannels; i++) { chan = &tc->channels[i]; if (!dahdi_tc_is_busy(chan)) { if (!dahdi_tc_is_built(chan)) { dahdi_tc_set_busy(chan); return chan; } else { /* If the channel is already built, we must * make sure that it can support the formats * that we're interested in. */ if ((fmts->srcfmt|fmts->dstfmt) == chan->built_fmts) { 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 *tc; struct dahdi_transcoder_channel *chan = NULL; unsigned int match = 0; list_for_each_entry(tc, list, active_list_node) { if ((tc->dstfmts & fmts->dstfmt) && (tc->srcfmts & fmts->srcfmt)) { /* We found a transcoder that can handle our formats. * Now look for an available channel. */ match = 1; if ((chan = get_free_channel(tc, fmts))) { /* 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->active_list_node, list); return chan; } } } return (void*)((long)((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, (__user const void *) data, sizeof(fmts))) { return -EFAULT; } spin_lock(&translock); chan = __find_free_channel(&active_list, &fmts); spin_unlock(&translock); if (IS_ERR(chan)) { return PTR_ERR(chan); } /* Every transcoder channel must be associated with a parent * transcoder. */ BUG_ON(!chan->parent); chan->srcfmt = fmts.srcfmt; chan->dstfmt = fmts.dstfmt; 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; } 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 long dahdi_tc_getinfo(unsigned long data) { struct dahdi_transcoder_info info; struct dahdi_transcoder *cur; struct dahdi_transcoder *tc = NULL; unsigned int count = 0; if (copy_from_user(&info, (__user const void *) data, sizeof(info))) { return -EFAULT; } spin_lock(&translock); list_for_each_entry(cur, ®istration_list, registration_list_node) { if (info.tcnum == count++) { tc = cur; break; } } spin_unlock(&translock); if (!tc) { return -ENOSYS; } strlcpy(info.name, tc->name, sizeof(info.name)); info.numchannels = tc->numchannels; info.srcfmts = tc->srcfmts; info.dstfmts = tc->dstfmts; return copy_to_user((__user void *) data, &info, sizeof(info)) ? -EFAULT : 0; } static ssize_t dahdi_tc_write(struct file *file, __user const char *usrbuf, size_t count, loff_t *ppos) { 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; } else { printk(KERN_INFO "%s: Attempt to write to unallocated " \ "channel.\n", THIS_MODULE->name); return -EINVAL; } } static ssize_t dahdi_tc_read(struct file *file, __user char *usrbuf, size_t count, loff_t *ppos) { 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; } } 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; default: return -EINVAL; }; } #ifndef HAVE_UNLOCKED_IOCTL 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); } #endif static unsigned int dahdi_tc_poll(struct file *file, struct poll_table_struct *wait_table) { int ret; struct dahdi_transcoder_channel *chan = file->private_data; 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); 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, .open = dahdi_tc_open, .release = dahdi_tc_release, #ifdef HAVE_UNLOCKED_IOCTL .unlocked_ioctl = dahdi_tc_unlocked_ioctl, #else .ioctl = dahdi_tc_ioctl, #endif .read = dahdi_tc_read, .write = dahdi_tc_write, .poll = dahdi_tc_poll, }; static struct dahdi_chardev transcode_chardev = { .name = "transcode", .minor = DAHDI_TRANSCODE, }; static int dahdi_transcode_init(void) { int res; if (dahdi_transcode_fops) { printk(KERN_WARNING "dahdi_transcode_fops already set.\n"); return -EBUSY; } dahdi_transcode_fops = &__dahdi_transcode_fops; if ((res = dahdi_register_chardev(&transcode_chardev))) return res; printk(KERN_INFO "%s: Loaded.\n", THIS_MODULE->name); return 0; } static void dahdi_transcode_cleanup(void) { dahdi_unregister_chardev(&transcode_chardev); dahdi_transcode_fops = NULL; 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 "); #ifdef MODULE_LICENSE MODULE_LICENSE("GPL"); #endif module_init(dahdi_transcode_init); module_exit(dahdi_transcode_cleanup);