dahdi-linux/drivers/dahdi/dahdi_transcode.c

481 lines
13 KiB
C

/*
* 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, &registration_list));
list_add_tail(&tc->registration_list_node, &registration_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, &registration_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, &registration_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);