summaryrefslogtreecommitdiff
path: root/drivers/dahdi/dahdi_transcode.c
blob: f5e749b46a98c83bc5b26ef9185f746f1ff97c39 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
/*
 * 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 spinlock_t translock = SPIN_LOCK_UNLOCKED;

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);
	}
	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;
	}

	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((__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 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)
{
	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,
	.mmap =    dahdi_tc_mmap,
};

static struct dahdi_chardev transcode_chardev = {
	.name = "transcode",
	.minor = 250,
};

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);