123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- /*
- * Transcoder Interface for DAHDI
- *
- * Written by Mark Spencer <markster@digium.com>
- *
- * 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 <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 <linux/mm.h>
- #include <linux/page-flags.h>
- #include <asm/io.h>
- #include <dahdi/kernel.h>
- 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, "<unspecified>");
- 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 <markster@digium.com>");
- #ifdef MODULE_LICENSE
- MODULE_LICENSE("GPL");
- #endif
- module_init(dahdi_transcode_init);
- module_exit(dahdi_transcode_cleanup);
|