summaryrefslogtreecommitdiff
path: root/zaptel-base.c
diff options
context:
space:
mode:
authorkpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2007-12-14 23:47:31 +0000
committerkpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2007-12-14 23:47:31 +0000
commite82cf68ec969ba027f671b27261442b54b5f8737 (patch)
tree4a8a03d12bd400e69f53b7fce7f3075a41a407c2 /zaptel-base.c
parentef2eddaeb8ac4e25b283081911a36a642ce5f157 (diff)
(merging dtmf-twister branch plus a few fixes)
move DTMF/MF generation into tonezone.c (libtonezone) so that it can happen at runtime instead of compile time; this allows for DTMF/MF to be different on a zone-by-zone basis without requiring a recompile of Zaptel set DTMF 'twist' for Brazil (zone 'br') to 2dB git-svn-id: http://svn.digium.com/svn/zaptel/branches/1.4@3490 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'zaptel-base.c')
-rw-r--r--zaptel-base.c383
1 files changed, 214 insertions, 169 deletions
diff --git a/zaptel-base.c b/zaptel-base.c
index 278ae29..acd4743 100644
--- a/zaptel-base.c
+++ b/zaptel-base.c
@@ -45,6 +45,7 @@
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/version.h>
+#include <linux/ctype.h>
#include <linux/kmod.h>
#ifdef CONFIG_DEVFS_FS
#include <linux/devfs_fs_kernel.h>
@@ -302,8 +303,10 @@ of the next sample chunk's data (next time around the world).
#include "digits.h"
-static struct zt_tone *dtmf_tones_continuous = NULL;
-static struct zt_tone *mfv1_tones_continuous = NULL;
+static struct zt_dialparams global_dialparams = {
+ .dtmf_tonelen = DEFAULT_DTMF_LENGTH,
+ .mfv1_tonelen = DEFAULT_MFV1_LENGTH,
+};
static int zt_chan_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data, int unit);
@@ -363,6 +366,10 @@ struct zt_zone {
of zt_tones to generate what we
want. Use NULL if the tone is
unavailable */
+ struct zt_tone dtmf[16]; /* DTMF tones for this zone, with desired length */
+ struct zt_tone dtmf_continuous[16]; /* DTMF tones for this zone, continuous play */
+ struct zt_tone mf[15]; /* MF tones for this zone, with desired length */
+ struct zt_tone mf_continuous[15]; /* MF tones for this zone, continuous play */
};
static struct zt_span *spans[ZT_MAX_SPANS];
@@ -1074,13 +1081,11 @@ static void close_channel(struct zt_chan *chan)
static int free_tone_zone(int num)
{
struct zt_zone *z;
- if ((num < 0) || (num >= ZT_TONE_ZONE_MAX))
- return -EINVAL;
- write_lock(&zone_lock);
+
z = tone_zones[num];
tone_zones[num] = NULL;
- write_unlock(&zone_lock);
kfree(z);
+
return 0;
}
@@ -1130,10 +1135,14 @@ static int start_tone(struct zt_chan *chan, int tone)
/* ZT_SENDTONE should never be used on a channel configured for pulse dialing */
chan->dialing = 1;
res = 0;
- if (chan->digitmode == DIGIT_MODE_DTMF)
- chan->curtone = dtmf_tones_continuous + (tone - ZT_TONE_DTMF_BASE);
- else if (chan->digitmode == DIGIT_MODE_MFV1 && tone != ZT_TONE_DTMF_MAX) /* No 'D' */
- chan->curtone = mfv1_tones_continuous + (tone - ZT_TONE_DTMF_BASE);
+ if ((chan->digitmode == DIGIT_MODE_DTMF) &&
+ (tone >= ZT_TONE_DTMF_BASE) &&
+ (tone <= ZT_TONE_DTMF_MAX))
+ chan->curtone = &chan->curzone->dtmf_continuous[tone - ZT_TONE_DTMF_BASE];
+ else if ((chan->digitmode == DIGIT_MODE_MFV1) &&
+ (tone >= ZT_TONE_MF_BASE) &&
+ (tone <= ZT_TONE_MF_MAX))
+ chan->curtone = &chan->curzone->mf_continuous[tone - ZT_TONE_MF_BASE];
else {
chan->dialing = 0;
res = -EINVAL;
@@ -2563,84 +2572,112 @@ static ssize_t zt_write(struct file *file, const char *usrbuf, size_t count, lof
/* No bigger than 32k for everything per tone zone */
#define MAX_SIZE 32768
-/* No more than 64 subtones */
-#define MAX_TONES 64
-
-static int
-ioctl_load_zone(unsigned long data)
+/* No more than 128 subtones */
+#define MAX_TONES 128
+
+/* The tones to be loaded can (will) be a mix of regular tones,
+ DTMF tones and MF tones. We need to load DTMF and MF tones
+ a bit differently than regular tones because their storage
+ format is much simpler (an array structure field of the zone
+ structure, rather an array of pointers).
+*/
+static int ioctl_load_zone(unsigned long data)
{
- struct zt_tone *samples[MAX_TONES];
- short next[MAX_TONES];
+ struct zt_tone *samples[MAX_TONES] = { NULL, };
+ short next[MAX_TONES] = { 0, };
struct zt_tone_def_header th;
- void *slab, *ptr;
- long size;
- struct zt_zone *z;
struct zt_tone_def td;
+ struct zt_zone *z;
struct zt_tone *t;
+ void *slab, *ptr;
int x;
- int space;
+ size_t space;
+ size_t size;
int res;
- /* XXX Unnecessary XXX */
- memset(samples, 0, sizeof(samples));
- /* XXX Unnecessary XXX */
- memset(next, 0, sizeof(next));
- if (copy_from_user(&th, (struct zt_tone_def_header *)data, sizeof(th)))
+ if (copy_from_user(&th, (struct zt_tone_def_header *) data, sizeof(th)))
return -EFAULT;
+
+ data += sizeof(th);
+
if ((th.count < 0) || (th.count > MAX_TONES)) {
printk("Too many tones included\n");
return -EINVAL;
}
- space = size = sizeof(struct zt_zone) +
- th.count * sizeof(struct zt_tone);
- if ((size > MAX_SIZE) || (size < 0))
+
+ space = size = sizeof(*z) + th.count * sizeof(*t);
+
+ if (size > MAX_SIZE)
return -E2BIG;
- ptr = slab = (char *)kmalloc(size, GFP_KERNEL);
- if (!slab)
+
+ if (!(z = ptr = slab = kmalloc(size, GFP_KERNEL)))
return -ENOMEM;
- /* Zero it out for simplicity */
+
memset(slab, 0, size);
- /* Grab the zone */
- z = (struct zt_zone *)slab;
+
+ ptr += sizeof(*z);
+ space -= sizeof(*z);
+
strncpy(z->name, th.name, sizeof(z->name) - 1);
- for (x=0;x<ZT_MAX_CADENCE;x++)
+
+ for (x = 0; x < ZT_MAX_CADENCE; x++)
z->ringcadence[x] = th.ringcadence[x];
- data += sizeof(struct zt_tone_def_header);
- ptr += sizeof(struct zt_zone);
- space -= sizeof(struct zt_zone);
- for (x=0;x<th.count;x++) {
- if (space < sizeof(struct zt_tone)) {
- /* Check space for zt_tone struct */
+
+ for (x = 0; x < th.count; x++) {
+ enum {
+ REGULAR_TONE,
+ DTMF_TONE,
+ MF_TONE,
+ } tone_type;
+
+ if (space < sizeof(*t)) {
kfree(slab);
printk("Insufficient tone zone space\n");
return -EINVAL;
}
- if (copy_from_user(&td, (struct zt_tone_def *)data, sizeof(struct zt_tone_def))) {
+
+ if (copy_from_user(&td, (struct zt_tone_def *) data, sizeof(td))) {
kfree(slab);
return -EFAULT;
}
- /* Index the current sample */
- samples[x] = t = (struct zt_tone *)ptr;
- /* Remember which sample is next */
- next[x] = td.next;
- /* Make sure the "next" one is sane */
- if ((next[x] >= th.count) || (next[x] < 0)) {
- printk("Invalid 'next' pointer: %d\n", next[x]);
- kfree(slab);
- return -EINVAL;
- }
- if (td.tone >= ZT_TONE_MAX) {
- printk("Too many tones defined\n");
- /* Make sure it's sane */
+
+ data += sizeof(td);
+
+ if ((td.tone >= 0) && (td.tone < ZT_TONE_MAX)) {
+ tone_type = REGULAR_TONE;
+
+ t = samples[x] = ptr;
+
+ space -= sizeof(*t);
+ ptr += sizeof(*t);
+
+ /* Remember which sample is next */
+ next[x] = td.next;
+
+ /* Make sure the "next" one is sane */
+ if ((next[x] >= th.count) || (next[x] < 0)) {
+ printk("Invalid 'next' pointer: %d\n", next[x]);
+ kfree(slab);
+ return -EINVAL;
+ }
+ } else if ((td.tone >= ZT_TONE_DTMF_BASE) &&
+ (td.tone <= ZT_TONE_DTMF_MAX)) {
+ tone_type = DTMF_TONE;
+
+ td.tone -= ZT_TONE_DTMF_BASE;
+ t = &z->dtmf[td.tone];
+ } else if ((td.tone >= ZT_TONE_MF_BASE) &&
+ (td.tone <= ZT_TONE_MF_MAX)) {
+ tone_type = MF_TONE;
+
+ td.tone -= ZT_TONE_MF_BASE;
+ t = &z->mf[td.tone];
+ } else {
+ printk("Invalid tone (%d) defined\n", td.tone);
kfree(slab);
return -EINVAL;
}
- /* Update pointers to account for zt_tone header */
- space -= sizeof(struct zt_tone);
- ptr += sizeof(struct zt_tone);
- data += sizeof(struct zt_tone_def);
- /* Fill in tonedata, datalen, and tonesamples fields */
- t->tonesamples = td.samples;
+
t->fac1 = td.fac1;
t->init_v2_1 = td.init_v2_1;
t->init_v3_1 = td.init_v3_1;
@@ -2648,18 +2685,39 @@ ioctl_load_zone(unsigned long data)
t->init_v2_2 = td.init_v2_2;
t->init_v3_2 = td.init_v3_2;
t->modulate = td.modulate;
- t->next = NULL; /* XXX Unnecessary XXX */
- if (!z->tones[td.tone])
- z->tones[td.tone] = t;
+
+ switch (tone_type) {
+ case REGULAR_TONE:
+ t->tonesamples = td.samples;
+ if (!z->tones[td.tone])
+ z->tones[td.tone] = t;
+ break;
+ case DTMF_TONE:
+ t->tonesamples = global_dialparams.dtmf_tonelen;
+ t->next = &dtmf_silence;
+ z->dtmf_continuous[td.tone] = *t;
+ z->dtmf_continuous[td.tone].next = &z->dtmf_continuous[td.tone];
+ break;
+ case MF_TONE:
+ t->tonesamples = global_dialparams.mfv1_tonelen;
+ t->next = &mfv1_silence;
+ /* Special case for K/P tone */
+ if (td.tone == 10)
+ t->tonesamples *= 5 / 3;
+ z->mf_continuous[td.tone] = *t;
+ z->mf_continuous[td.tone].next = &z->mf_continuous[td.tone];
+ break;
+ }
}
- for (x=0;x<th.count;x++)
- /* Set "next" pointers */
- samples[x]->next = samples[next[x]];
- /* Actually register zone */
- res = zt_register_tone_zone(th.zone, z);
- if (res)
+ for (x = 0; x < th.count; x++) {
+ if (samples[x] && next[x])
+ samples[x]->next = samples[next[x]];
+ }
+
+ if ((res = zt_register_tone_zone(th.zone, z)))
kfree(slab);
+
return res;
}
@@ -2674,15 +2732,22 @@ void zt_init_tone_state(struct zt_tone_state *ts, struct zt_tone *zt)
ts->modulate = zt->modulate;
}
-struct zt_tone *zt_dtmf_tone(char digit, int mf)
+struct zt_tone *zt_dtmf_tone(const struct zt_chan *chan, char digit)
{
struct zt_tone *z;
- if (!mf)
- z = dtmf_tones;
- else
- z = mfv1_tones;
- switch(digit) {
+ switch (chan->digitmode) {
+ case DIGIT_MODE_DTMF:
+ z = &chan->curzone->dtmf[0];
+ break;
+ case DIGIT_MODE_MFV1:
+ z = &chan->curzone->mf[0];
+ break;
+ default:
+ z = NULL;
+ }
+
+ switch (digit) {
case '0':
case '1':
case '2':
@@ -2693,7 +2758,7 @@ struct zt_tone *zt_dtmf_tone(char digit, int mf)
case '7':
case '8':
case '9':
- return z + (int)(digit - '0');
+ return z + (digit - '0');
case '*':
return z + 10;
case '#':
@@ -2703,66 +2768,48 @@ struct zt_tone *zt_dtmf_tone(char digit, int mf)
case 'C':
return z + (digit + 12 - 'A');
case 'D':
- if (!mf)
- return z + ( digit + 12 - 'A');
- return NULL;
- case 'a':
- case 'b':
- case 'c':
- return z + (digit + 12 - 'a');
- case 'd':
- if (!mf)
- return z + ( digit + 12 - 'a');
- return NULL;
+ if (chan->digitmode == DIGIT_MODE_MFV1)
+ return NULL;
+ else
+ return z + (digit + 12 - 'A');
case 'W':
- case 'w':
return &tone_pause;
}
+
return NULL;
}
static void __do_dtmf(struct zt_chan *chan)
{
char c;
+
/* Called with chan->lock held */
- while (strlen(chan->txdialbuf)) {
- c = chan->txdialbuf[0];
- /* Skooch */
+ while ((c = chan->txdialbuf[0])) {
memmove(chan->txdialbuf, chan->txdialbuf + 1, sizeof(chan->txdialbuf) - 1);
- switch(c) {
+ switch (c) {
case 'T':
- case 't':
chan->digitmode = DIGIT_MODE_DTMF;
chan->tonep = 0;
break;
case 'M':
- case 'm':
chan->digitmode = DIGIT_MODE_MFV1;
chan->tonep = 0;
break;
case 'P':
- case 'p':
chan->digitmode = DIGIT_MODE_PULSE;
chan->tonep = 0;
break;
default:
- if (chan->digitmode == DIGIT_MODE_PULSE)
- {
- if ((c >= '0') && (c <= '9') && (chan->txhooksig == ZT_TXSIG_OFFHOOK))
- {
- chan->pdialcount = c - '0';
- /* a '0' is ten pulses */
- if (!chan->pdialcount) chan->pdialcount = 10;
- zt_rbs_sethook(chan, ZT_TXSIG_ONHOOK,
- ZT_TXSTATE_PULSEBREAK, chan->pulsebreaktime);
+ if ((c != 'W') && (chan->digitmode == DIGIT_MODE_PULSE)) {
+ if ((c >= '0') && (c <= '9') && (chan->txhooksig == ZT_TXSIG_OFFHOOK)) {
+ chan->pdialcount = (c == '0') ? 10 : c - '0';
+ zt_rbs_sethook(chan, ZT_TXSIG_ONHOOK, ZT_TXSTATE_PULSEBREAK,
+ chan->pulsebreaktime);
return;
}
} else {
- case 'w':
- case 'W':
- chan->curtone = zt_dtmf_tone(c, (chan->digitmode == DIGIT_MODE_MFV1));
+ chan->curtone = zt_dtmf_tone(chan, c);
chan->tonep = 0;
- /* All done */
if (chan->curtone) {
zt_init_tone_state(&chan->ts, chan->curtone);
return;
@@ -2770,6 +2817,7 @@ static void __do_dtmf(struct zt_chan *chan)
}
}
}
+
/* Notify userspace process if there is nothing left */
chan->dialing = 0;
__qevent(chan, ZT_EVENT_DIALCOMPLETE);
@@ -3567,43 +3615,70 @@ static int zt_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd
return res;
case ZT_DEFAULTZONE:
if (get_user(j,(int *)data))
- return -EFAULT; /* get conf # */
- if ((j < 0) || (j >= ZT_TONE_ZONE_MAX)) return (-EINVAL);
+ return -EFAULT;
+ if ((j < 0) || (j >= ZT_TONE_ZONE_MAX))
+ return -EINVAL;
write_lock(&zone_lock);
+ if (!tone_zones[j]) {
+ write_unlock(&zone_lock);
+ return -EINVAL;
+ }
default_zone = j;
write_unlock(&zone_lock);
- return 0;
+ break;
case ZT_LOADZONE:
return ioctl_load_zone(data);
case ZT_FREEZONE:
- get_user(j,(int *)data); /* get conf # */
- if ((j < 0) || (j >= ZT_TONE_ZONE_MAX)) return (-EINVAL);
+ get_user(j, (int *) data);
+ if ((j < 0) || (j >= ZT_TONE_ZONE_MAX))
+ return -EINVAL;
+ write_lock(&zone_lock);
+#if 0
+ if (j == default_zone) {
+ write_unlock(&zone_lock);
+ /* XXX: possibly a better return code here */
+ return -EINVAL;
+ }
+#endif
free_tone_zone(j);
- return 0;
+ write_unlock(&zone_lock);
+ break;
case ZT_SET_DIALPARAMS:
- if (copy_from_user(&tdp, (struct zt_dialparams *)data, sizeof(tdp)))
+ if (copy_from_user(&tdp, (struct zt_dialparams *) data, sizeof(tdp)))
return -EFAULT;
if ((tdp.dtmf_tonelen > 4000) || (tdp.dtmf_tonelen < 10))
return -EINVAL;
if ((tdp.mfv1_tonelen > 4000) || (tdp.mfv1_tonelen < 10))
return -EINVAL;
- for (i=0;i<16;i++)
- dtmf_tones[i].tonesamples = tdp.dtmf_tonelen * ZT_CHUNKSIZE;
+
+ global_dialparams = tdp;
+
+ /* update the lengths in all currently loaded zones */
+ write_lock(&zone_lock);
+ for (j = 0; j < sizeof(tone_zones) / sizeof(tone_zones[0]); j++) {
+ struct zt_zone *z = tone_zones[j];
+
+ if (!z)
+ continue;
+
+ for (i = 0; i < sizeof(z->dtmf) / sizeof(z->dtmf[0]); i++)
+ z->dtmf[i].tonesamples = tdp.dtmf_tonelen * ZT_CHUNKSIZE;
+
+ for (i = 0; i < sizeof(z->mf) / sizeof(z->mf[0]); i++)
+ z->mf[i].tonesamples = tdp.mfv1_tonelen * ZT_CHUNKSIZE;
+
+ /* Special case for K/P tone */
+ z->mf[10].tonesamples *= 5 / 3;
+ }
+ write_unlock(&zone_lock);
+
dtmf_silence.tonesamples = tdp.dtmf_tonelen * ZT_CHUNKSIZE;
- for (i=0;i<15;i++)
- mfv1_tones[i].tonesamples = tdp.mfv1_tonelen * ZT_CHUNKSIZE;
mfv1_silence.tonesamples = tdp.mfv1_tonelen * ZT_CHUNKSIZE;
- /* Special case for K/P tone */
- mfv1_tones[10].tonesamples = tdp.mfv1_tonelen * ZT_CHUNKSIZE * 5 / 3;
+
break;
case ZT_GET_DIALPARAMS:
- tdp.dtmf_tonelen = dtmf_tones[0].tonesamples / ZT_CHUNKSIZE;
- tdp.mfv1_tonelen = mfv1_tones[0].tonesamples / ZT_CHUNKSIZE;
- tdp.reserved[0] = 0;
- tdp.reserved[1] = 0;
- tdp.reserved[2] = 0;
- tdp.reserved[3] = 0;
- if (copy_to_user((struct zt_dialparams *)data, &tdp, sizeof(tdp)))
+ tdp = global_dialparams;
+ if (copy_to_user((struct zt_dialparams *) data, &tdp, sizeof(tdp)))
return -EFAULT;
break;
case ZT_GETVERSION:
@@ -3687,6 +3762,7 @@ static int zt_chanandpseudo_ioctl(struct inode *inode, struct file *file, unsign
unsigned long flags, flagso;
int i, j, k, rv;
int ret, c;
+ char *s;
if (!chan)
return -EINVAL;
@@ -3702,10 +3778,12 @@ static int zt_chanandpseudo_ioctl(struct inode *inode, struct file *file, unsign
if (copy_from_user(&stack.tdo, (struct zt_dialoperation *)data, sizeof(stack.tdo)))
return -EFAULT;
rv = 0;
- /* Force proper NULL termination */
+ /* Force proper NULL termination and uppercase entry */
stack.tdo.dialstr[ZT_MAX_DTMF_BUF - 1] = '\0';
+ for (s = stack.tdo.dialstr; *s; s++)
+ *s = toupper(*s);
spin_lock_irqsave(&chan->lock, flags);
- switch(stack.tdo.op) {
+ switch (stack.tdo.op) {
case ZT_DIAL_OP_CANCEL:
chan->curtone = NULL;
chan->dialing = 0;
@@ -3719,17 +3797,15 @@ static int zt_chanandpseudo_ioctl(struct inode *inode, struct file *file, unsign
__do_dtmf(chan);
break;
case ZT_DIAL_OP_APPEND:
- if (strlen(stack.tdo.dialstr) + strlen(chan->txdialbuf) >= ZT_MAX_DTMF_BUF)
- {
+ if (strlen(stack.tdo.dialstr) + strlen(chan->txdialbuf) >= (ZT_MAX_DTMF_BUF - 1)) {
rv = -EBUSY;
break;
- }
- strncpy(chan->txdialbuf + strlen(chan->txdialbuf), stack.tdo.dialstr, ZT_MAX_DTMF_BUF - strlen(chan->txdialbuf));
- if (!chan->dialing)
- {
+ }
+ strncpy(chan->txdialbuf + strlen(chan->txdialbuf), stack.tdo.dialstr, ZT_MAX_DTMF_BUF - strlen(chan->txdialbuf) - 1);
+ if (!chan->dialing) {
chan->dialing = 1;
__do_dtmf(chan);
- }
+ }
break;
default:
rv = -EINVAL;
@@ -7144,7 +7220,6 @@ static void __exit watchdog_cleanup(void)
static int __init zt_init(void) {
int res = 0;
- int i = 0;
#ifdef CONFIG_PROC_FS
proc_entries[0] = proc_mkdir("zaptel", NULL);
@@ -7178,26 +7253,6 @@ static int __init zt_init(void) {
}
#endif /* CONFIG_DEVFS_FS */
- if (!(dtmf_tones_continuous = kmalloc(sizeof(dtmf_tones), GFP_KERNEL))) {
- printk(KERN_ERR "Zaptel: THERE IS A CRISIS IN THE BATCAVE!"
- " Unable to allocate memory for continuous DTMF tones list!\n");
- return -ENOMEM;
- }
-
- if (!(mfv1_tones_continuous = kmalloc(sizeof(mfv1_tones), GFP_KERNEL))) {
- printk(KERN_ERR "Zaptel: THERE IS A CRISIS IN THE BATCAVE!"
- " Unable to allocate memory for continuous MFV1 tones list!\n");
- return -ENOMEM;
- }
-
- memcpy(dtmf_tones_continuous, dtmf_tones, sizeof(dtmf_tones));
- for (i = 0; i < (sizeof(dtmf_tones) / sizeof(dtmf_tones[0])); i++)
- dtmf_tones_continuous[i].next = dtmf_tones_continuous + i;
-
- memcpy(mfv1_tones_continuous, mfv1_tones, sizeof(mfv1_tones));
- for (i = 0; i < (sizeof(mfv1_tones) / sizeof(mfv1_tones[0])); i++)
- mfv1_tones_continuous[i].next = mfv1_tones_continuous + i;
-
printk(KERN_INFO "Zapata Telephony Interface Registered on major %d\n", ZT_MAJOR);
printk(KERN_INFO "Zaptel Version: %s\n", ZAPTEL_VERSION);
echo_can_init();
@@ -7224,16 +7279,6 @@ static void __exit zt_cleanup(void) {
kfree(tone_zones[x]);
}
- if (dtmf_tones_continuous) {
- kfree(dtmf_tones_continuous);
- dtmf_tones_continuous = NULL;
- }
-
- if (mfv1_tones_continuous) {
- kfree(mfv1_tones_continuous);
- mfv1_tones_continuous = NULL;
- }
-
#ifdef CONFIG_DEVFS_FS
devfs_unregister(timer);
devfs_unregister(transcode);