diff -Nru dahdi-linux-2.2.0.2/drivers/dahdi/dahdi-base.c dahdi-cnet-linux-2.2.0.2/drivers/dahdi/dahdi-base.c --- dahdi-linux-2.2.0.2/drivers/dahdi/dahdi-base.c 2009-07-21 13:11:53.000000000 -0500 +++ dahdi-cnet-linux-2.2.0.2/drivers/dahdi/dahdi-base.c 2009-07-24 11:35:11.000000000 -0500 @@ -3245,7 +3245,16 @@ default: if ((c != 'W') && (chan->digitmode == DIGIT_MODE_PULSE)) { if ((c >= '0') && (c <= '9') && (chan->txhooksig == DAHDI_TXSIG_OFFHOOK)) { - chan->pdialcount = (c == '0') ? 10 : c - '0'; + /* (CNET) Dial pulse mappings for FXO ports */ + switch(chan->map_pulse) + { + case MAP_PULSE_NZ_OSLO: /* 0=10 pulses, 1=9 pulses, ... 9=1 pulse */ + chan->pdialcount = 10 - (c - '0'); break; + case MAP_PULSE_SWEDEN: /* 0=1 pulse, 1=2 pulses, ... 9=10 pulses */ + chan->pdialcount = (c - '0') + 1; break; + default: /* standard dial pulse mapping */ + chan->pdialcount = (c == '0') ? 10 : c - '0'; break; + } dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_PULSEBREAK, chan->pulsebreaktime); return; @@ -4055,6 +4064,12 @@ !(chans[ch.chan]->flags & DAHDI_FLAG_NETDEV)) module_printk(KERN_NOTICE, "Unable to register HDLC device for channel %s\n", chans[ch.chan]->name); if (!res) { + /* (CNET) Ignore remote hookflash? */ + chans[ch.chan]->ignoreflash = ch.ignoreflash; + /* (CNET) Use Oslo/NZ or Swedish pulse mapping? */ + chans[ch.chan]->map_pulse = ch.map_pulse; + /* (CNET) Make outpulsing audible */ + chans[ch.chan]->hearpulsing = ch.hearpulsing; /* Setup default law */ chans[ch.chan]->deflaw = ch.deflaw; /* Copy back any modified settings */ @@ -5360,6 +5375,7 @@ if (rv) return rv; break; case DAHDI_FLASH: + if(chan->ignoreflash) break; /* (CNET) Ignore remote hookflash */ spin_lock_irqsave(&chan->lock, flags); if (chan->txstate != DAHDI_TXSTATE_OFFHOOK) { spin_unlock_irqrestore(&chan->lock, flags); @@ -6775,12 +6791,16 @@ short putlin[DAHDI_CHUNKSIZE],k[DAHDI_CHUNKSIZE]; int x,r; - if (ms->dialing) ms->afterdialingtimer = 50; - else if (ms->afterdialingtimer) ms->afterdialingtimer--; - if (ms->afterdialingtimer && (!(ms->flags & DAHDI_FLAG_PSEUDO))) { - /* Be careful since memset is likely a macro */ - rxb[0] = DAHDI_LIN2X(0, ms); - memset(&rxb[1], rxb[0], DAHDI_CHUNKSIZE - 1); /* receive as silence if dialing */ + /* (CNET) Allow caller to hear outpulsing if hearpulsing is true. + NOTE: This also requires a patched chan_zap with its own hearpulsing option! */ + if(!ms->hearpulsing) { + if (ms->dialing) ms->afterdialingtimer = 50; + else if (ms->afterdialingtimer) ms->afterdialingtimer--; + if (ms->afterdialingtimer && (!(ms->flags & DAHDI_FLAG_PSEUDO))) { + /* Be careful since memset is likely a macro */ + rxb[0] = DAHDI_LIN2X(0, ms); + memset(&rxb[1], rxb[0], DAHDI_CHUNKSIZE - 1); /* receive as silence if dialing */ + } } for (x=0;xrxgain[rxb[x]]; @@ -8031,6 +8051,14 @@ __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | '#'); } else if (span->chans[x]->pulsecount > 10) { __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | '*'); + } else if (span->chans[x]->map_pulse == MAP_PULSE_NZ_OSLO) { + /* (CNET) NZ/Oslo pluse mapping */ + __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | ('0' + + (10 - span->chans[x]->pulsecount))); + } else if (span->chans[x]->map_pulse == MAP_PULSE_SWEDEN) { + /* (CNET) Swedish pulse mapping */ + __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | ('0' + + (span->chans[x]->pulsecount - 1))); } else if (span->chans[x]->pulsecount > 9) { __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | '0'); } else { diff -Nru dahdi-linux-2.2.0.2/drivers/dahdi/dahdi-base.c.orig dahdi-cnet-linux-2.2.0.2/drivers/dahdi/dahdi-base.c.orig --- dahdi-linux-2.2.0.2/drivers/dahdi/dahdi-base.c.orig 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/dahdi/dahdi-base.c.orig 2009-07-21 13:11:53.000000000 -0500 @@ -0,0 +1,8219 @@ +/* + * DAHDI Telephony Interface Driver + * + * Written by Mark Spencer + * Based on previous works, designs, and architectures conceived and + * written by Jim Dixon . + * + * Special thanks to Steve Underwood + * for substantial contributions to signal processing functions + * in DAHDI and the Zapata library. + * + * Yury Bokhoncovich + * Adaptation for 2.4.20+ kernels (HDLC API was changed) + * The work has been performed as a part of our move + * from Cisco 3620 to IBM x305 here in F1 Group + * + * Copyright (C) 2001 Jim Dixon / Zapata Telephony. + * Copyright (C) 2001 - 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 + +#define module_printk(level, fmt, args...) printk(level "%s: " fmt, THIS_MODULE->name, ## args) + +/* #define BUF_MUNGE */ + +#include +/* Grab fasthdlc with tables */ +#define FAST_HDLC_NEED_TABLES +#include +#include "ecdis.h" + +#ifndef CONFIG_OLD_HDLC_API +#define NEW_HDLC_INTERFACE +#endif + +#ifdef CONFIG_DAHDI_PPP +#include +#include +#include +#endif + +#ifdef CONFIG_DAHDI_NET +#include +#endif + +#include "hpec/hpec_user.h" + +/* Get helper arithmetic */ +#include "arith.h" +#if defined(CONFIG_DAHDI_MMX) || defined(ECHO_CAN_FP) +#include +#endif + +#define hdlc_to_ztchan(h) (((struct dahdi_hdlc *)(h))->chan) +#define dev_to_ztchan(h) (((struct dahdi_hdlc *)(dev_to_hdlc(h)->priv))->chan) +#define ztchan_to_dev(h) ((h)->hdlcnetdev->netdev) + +/* macro-oni for determining a unit (channel) number */ +#define UNIT(file) MINOR(file->f_dentry->d_inode->i_rdev) + +/* names of tx level settings */ +static char *dahdi_txlevelnames[] = { +"0 db (CSU)/0-133 feet (DSX-1)", +"133-266 feet (DSX-1)", +"266-399 feet (DSX-1)", +"399-533 feet (DSX-1)", +"533-655 feet (DSX-1)", +"-7.5db (CSU)", +"-15db (CSU)", +"-22.5db (CSU)" +} ; + +EXPORT_SYMBOL(dahdi_transcode_fops); +EXPORT_SYMBOL(dahdi_init_tone_state); +EXPORT_SYMBOL(dahdi_mf_tone); +EXPORT_SYMBOL(dahdi_register); +EXPORT_SYMBOL(dahdi_unregister); +EXPORT_SYMBOL(__dahdi_mulaw); +EXPORT_SYMBOL(__dahdi_alaw); +#ifdef CONFIG_CALC_XLAW +EXPORT_SYMBOL(__dahdi_lineartoulaw); +EXPORT_SYMBOL(__dahdi_lineartoalaw); +#else +EXPORT_SYMBOL(__dahdi_lin2mu); +EXPORT_SYMBOL(__dahdi_lin2a); +#endif +EXPORT_SYMBOL(dahdi_lboname); +EXPORT_SYMBOL(dahdi_transmit); +EXPORT_SYMBOL(dahdi_receive); +EXPORT_SYMBOL(dahdi_rbsbits); +EXPORT_SYMBOL(dahdi_qevent_nolock); +EXPORT_SYMBOL(dahdi_qevent_lock); +EXPORT_SYMBOL(dahdi_hooksig); +EXPORT_SYMBOL(dahdi_alarm_notify); +EXPORT_SYMBOL(dahdi_set_dynamic_ioctl); +EXPORT_SYMBOL(dahdi_ec_chunk); +EXPORT_SYMBOL(dahdi_ec_span); +EXPORT_SYMBOL(dahdi_hdlc_abort); +EXPORT_SYMBOL(dahdi_hdlc_finish); +EXPORT_SYMBOL(dahdi_hdlc_getbuf); +EXPORT_SYMBOL(dahdi_hdlc_putbuf); +EXPORT_SYMBOL(dahdi_alarm_channel); +EXPORT_SYMBOL(dahdi_register_chardev); +EXPORT_SYMBOL(dahdi_unregister_chardev); + +EXPORT_SYMBOL(dahdi_register_echocan_factory); +EXPORT_SYMBOL(dahdi_unregister_echocan_factory); + +EXPORT_SYMBOL(dahdi_set_hpec_ioctl); + +#ifdef CONFIG_PROC_FS +static struct proc_dir_entry *proc_entries[DAHDI_MAX_SPANS]; +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,27) +#define CLASS_DEV_CREATE(class, devt, device, name) \ + device_create(class, device, devt, NULL, "%s", name) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) +#define CLASS_DEV_CREATE(class, devt, device, name) \ + device_create(class, device, devt, name) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,15) +#define CLASS_DEV_CREATE(class, devt, device, name) \ + class_device_create(class, NULL, devt, device, name) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +#define CLASS_DEV_CREATE(class, devt, device, name) \ + class_device_create(class, devt, device, name) +#else +#define CLASS_DEV_CREATE(class, devt, device, name) \ + class_simple_device_add(class, devt, device, name) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) +#define CLASS_DEV_DESTROY(class, devt) \ + device_destroy(class, devt) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +#define CLASS_DEV_DESTROY(class, devt) \ + class_device_destroy(class, devt) +#elif LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,9) +#define CLASS_DEV_DESTROY(class, devt) \ + class_simple_device_remove(devt) +#else +#define CLASS_DEV_DESTROY(class, devt) \ + class_simple_device_remove(class, devt) +#endif + +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,13) +static struct class *dahdi_class = NULL; +#else +static struct class_simple *dahdi_class = NULL; +#define class_create class_simple_create +#define class_destroy class_simple_destroy +#endif + +/* + * See issue http://bugs.digium.com/view.php?id=13504 for more information. + * on why reference counting on the echo canceller modules is disabled + * currently. + */ +#undef USE_ECHOCAN_REFCOUNT + +static int deftaps = 64; + +static int debug; + +/*! + * \brief states for transmit signalling + */ +enum dahdi_txstate { + DAHDI_TXSTATE_ONHOOK, + DAHDI_TXSTATE_OFFHOOK, + DAHDI_TXSTATE_START, + DAHDI_TXSTATE_PREWINK, + DAHDI_TXSTATE_WINK, + DAHDI_TXSTATE_PREFLASH, + DAHDI_TXSTATE_FLASH, + DAHDI_TXSTATE_DEBOUNCE, + DAHDI_TXSTATE_AFTERSTART, + DAHDI_TXSTATE_RINGON, + DAHDI_TXSTATE_RINGOFF, + DAHDI_TXSTATE_KEWL, + DAHDI_TXSTATE_AFTERKEWL, + DAHDI_TXSTATE_PULSEBREAK, + DAHDI_TXSTATE_PULSEMAKE, + DAHDI_TXSTATE_PULSEAFTER, +}; + +typedef short sumtype[DAHDI_MAX_CHUNKSIZE]; + +static sumtype sums[(DAHDI_MAX_CONF + 1) * 3]; + +/* Translate conference aliases into actual conferences + and vice-versa */ +static short confalias[DAHDI_MAX_CONF + 1]; +static short confrev[DAHDI_MAX_CONF + 1]; + +static sumtype *conf_sums_next; +static sumtype *conf_sums; +static sumtype *conf_sums_prev; + +static struct dahdi_span *master; +static struct file_operations dahdi_fops; +struct file_operations *dahdi_transcode_fops = NULL; + +static struct { + int src; /* source conf number */ + int dst; /* dst conf number */ +} conf_links[DAHDI_MAX_CONF + 1]; + +#ifdef CONFIG_DAHDI_CORE_TIMER + +static struct core_timer { + struct timer_list timer; + struct timespec start_interval; + atomic_t count; + atomic_t shutdown; + atomic_t last_count; +} core_timer; + +#endif /* CONFIG_DAHDI_CORE_TIMER */ + + + +/* There are three sets of conference sum accumulators. One for the current +sample chunk (conf_sums), one for the next sample chunk (conf_sums_next), and +one for the previous sample chunk (conf_sums_prev). The following routine +(rotate_sums) "rotates" the pointers to these accululator arrays as part +of the events of sample chink processing as follows: + +The following sequence is designed to be looked at from the reference point +of the receive routine of the master span. + +1. All (real span) receive chunks are processed (with putbuf). The last one +to be processed is the master span. The data received is loaded into the +accumulators for the next chunk (conf_sums_next), to be in alignment with +current data after rotate_sums() is called (which immediately follows). +Keep in mind that putbuf is *also* a transmit routine for the pseudo parts +of channels that are in the REALANDPSEUDO conference mode. These channels +are processed from data in the current sample chunk (conf_sums), being +that this is a "transmit" function (for the pseudo part). + +2. rotate_sums() is called. + +3. All pseudo channel receive chunks are processed. This data is loaded into +the current sample chunk accumulators (conf_sums). + +4. All conference links are processed (being that all receive data for this +chunk has already been processed by now). + +5. All pseudo channel transmit chunks are processed. This data is loaded from +the current sample chunk accumulators (conf_sums). + +6. All (real span) transmit chunks are processed (with getbuf). This data is +loaded from the current sample chunk accumulators (conf_sums). Keep in mind +that getbuf is *also* a receive routine for the pseudo part of channels that +are in the REALANDPSEUDO conference mode. These samples are loaded into +the next sample chunk accumulators (conf_sums_next) to be processed as part +of the next sample chunk's data (next time around the world). + +*/ + +enum dahdi_digit_mode { + DIGIT_MODE_DTMF, + DIGIT_MODE_MFR1, + DIGIT_MODE_PULSE, + DIGIT_MODE_MFR2_FWD, + DIGIT_MODE_MFR2_REV, +}; + +#include "digits.h" + +static struct dahdi_dialparams global_dialparams = { + .dtmf_tonelen = DEFAULT_DTMF_LENGTH, + .mfv1_tonelen = DEFAULT_MFR1_LENGTH, + .mfr2_tonelen = DEFAULT_MFR2_LENGTH, +}; + +static int dahdi_chan_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data, int unit); + +#if defined(CONFIG_DAHDI_MMX) || defined(ECHO_CAN_FP) +#define dahdi_kernel_fpu_begin kernel_fpu_begin +#endif + +struct dahdi_timer { + int ms; /* Countdown */ + int pos; /* Position */ + int ping; /* Whether we've been ping'd */ + int tripped; /* Whether we're tripped */ + struct list_head list; + wait_queue_head_t sel; +}; + +static LIST_HEAD(zaptimers); + +#ifdef DEFINE_SPINLOCK +static DEFINE_SPINLOCK(zaptimerlock); +static DEFINE_SPINLOCK(bigzaplock); +#else +static spinlock_t zaptimerlock = SPIN_LOCK_UNLOCKED; +static spinlock_t bigzaplock = SPIN_LOCK_UNLOCKED; +#endif + +struct dahdi_zone { + atomic_t refcount; + char name[40]; /* Informational, only */ + int ringcadence[DAHDI_MAX_CADENCE]; + struct dahdi_tone *tones[DAHDI_TONE_MAX]; + /* Each of these is a circular list + of dahdi_tones to generate what we + want. Use NULL if the tone is + unavailable */ + struct dahdi_tone dtmf[16]; /* DTMF tones for this zone, with desired length */ + struct dahdi_tone dtmf_continuous[16]; /* DTMF tones for this zone, continuous play */ + struct dahdi_tone mfr1[15]; /* MFR1 tones for this zone, with desired length */ + struct dahdi_tone mfr2_fwd[15]; /* MFR2 FWD tones for this zone, with desired length */ + struct dahdi_tone mfr2_rev[15]; /* MFR2 REV tones for this zone, with desired length */ + struct dahdi_tone mfr2_fwd_continuous[16]; /* MFR2 FWD tones for this zone, continuous play */ + struct dahdi_tone mfr2_rev_continuous[16]; /* MFR2 REV tones for this zone, continuous play */ +}; + +static struct dahdi_span *spans[DAHDI_MAX_SPANS]; +static struct dahdi_chan *chans[DAHDI_MAX_CHANNELS]; + +static int maxspans = 0; +static int maxchans = 0; +static int maxconfs = 0; +static int maxlinks = 0; + +static int default_zone = -1; + +short __dahdi_mulaw[256]; +short __dahdi_alaw[256]; + +#ifndef CONFIG_CALC_XLAW +u_char __dahdi_lin2mu[16384]; + +u_char __dahdi_lin2a[16384]; +#endif + +static u_char defgain[256]; + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 10) +#define __RW_LOCK_UNLOCKED() RW_LOCK_UNLOCKED +#endif + +#ifdef DEFINE_RWLOCK +static DEFINE_RWLOCK(zone_lock); +static DEFINE_RWLOCK(chan_lock); +#else +static rwlock_t zone_lock = RW_LOCK_UNLOCKED; +static rwlock_t chan_lock = RW_LOCK_UNLOCKED; +#endif + +static struct dahdi_zone *tone_zones[DAHDI_TONE_ZONE_MAX]; + +#define NUM_SIGS 10 + +#ifdef DEFINE_RWLOCK +static DEFINE_RWLOCK(ecfactory_list_lock); +#else +static rwlock_t ecfactory_list_lock = __RW_LOCK_UNLOCKED(); +#endif + +static LIST_HEAD(ecfactory_list); + +struct ecfactory { + const struct dahdi_echocan_factory *ec; + struct module *owner; + struct list_head list; +}; + +int dahdi_register_echocan_factory(const struct dahdi_echocan_factory *ec) +{ + struct ecfactory *cur; + + write_lock(&ecfactory_list_lock); + + /* make sure it isn't already registered */ + list_for_each_entry(cur, &ecfactory_list, list) { + if (cur->ec == ec) { + write_unlock(&ecfactory_list_lock); + return -EPERM; + } + } + + if (!(cur = kzalloc(sizeof(*cur), GFP_KERNEL))) { + write_unlock(&ecfactory_list_lock); + return -ENOMEM; + } + + cur->ec = ec; + INIT_LIST_HEAD(&cur->list); + + list_add_tail(&cur->list, &ecfactory_list); + + write_unlock(&ecfactory_list_lock); + + return 0; +} + +void dahdi_unregister_echocan_factory(const struct dahdi_echocan_factory *ec) +{ + struct ecfactory *cur, *next; + + write_lock(&ecfactory_list_lock); + + list_for_each_entry_safe(cur, next, &ecfactory_list, list) { + if (cur->ec == ec) { + list_del(&cur->list); + break; + } + } + + write_unlock(&ecfactory_list_lock); +} + +static inline void rotate_sums(void) +{ + /* Rotate where we sum and so forth */ + static int pos = 0; + conf_sums_prev = sums + (DAHDI_MAX_CONF + 1) * pos; + conf_sums = sums + (DAHDI_MAX_CONF + 1) * ((pos + 1) % 3); + conf_sums_next = sums + (DAHDI_MAX_CONF + 1) * ((pos + 2) % 3); + pos = (pos + 1) % 3; + memset(conf_sums_next, 0, maxconfs * sizeof(sumtype)); +} + +/*! + * \return quiescent (idle) signalling states, for the various signalling types + */ +static int dahdi_q_sig(struct dahdi_chan *chan) +{ + int x; + static const unsigned int in_sig[NUM_SIGS][2] = { + { DAHDI_SIG_NONE, 0 }, + { DAHDI_SIG_EM, (DAHDI_ABIT << 8) }, + { DAHDI_SIG_FXSLS, DAHDI_BBIT | (DAHDI_BBIT << 8) }, + { DAHDI_SIG_FXSGS, DAHDI_ABIT | DAHDI_BBIT | ((DAHDI_ABIT | DAHDI_BBIT) << 8) }, + { DAHDI_SIG_FXSKS, DAHDI_BBIT | DAHDI_BBIT | ((DAHDI_ABIT | DAHDI_BBIT) << 8) }, + { DAHDI_SIG_FXOLS, (DAHDI_ABIT << 8) }, + { DAHDI_SIG_FXOGS, DAHDI_BBIT | ((DAHDI_ABIT | DAHDI_BBIT) << 8) }, + { DAHDI_SIG_FXOKS, (DAHDI_ABIT << 8) }, + { DAHDI_SIG_SF, 0 }, + { DAHDI_SIG_EM_E1, DAHDI_DBIT | ((DAHDI_ABIT | DAHDI_DBIT) << 8) }, + }; + + /* must have span to begin with */ + if (!chan->span) + return -1; + + /* if RBS does not apply, return error */ + if (!(chan->span->flags & DAHDI_FLAG_RBS) || !chan->span->rbsbits) + return -1; + + if (chan->sig == DAHDI_SIG_CAS) + return chan->idlebits; + + for (x = 0; x < NUM_SIGS; x++) { + if (in_sig[x][0] == chan->sig) + return in_sig[x][1]; + } + + return -1; /* not found -- error */ +} + +#ifdef CONFIG_PROC_FS +static const char *sigstr(int sig) +{ + switch (sig) { + case DAHDI_SIG_FXSLS: + return "FXSLS"; + case DAHDI_SIG_FXSKS: + return "FXSKS"; + case DAHDI_SIG_FXSGS: + return "FXSGS"; + case DAHDI_SIG_FXOLS: + return "FXOLS"; + case DAHDI_SIG_FXOKS: + return "FXOKS"; + case DAHDI_SIG_FXOGS: + return "FXOGS"; + case DAHDI_SIG_EM: + return "E&M"; + case DAHDI_SIG_EM_E1: + return "E&M-E1"; + case DAHDI_SIG_CLEAR: + return "Clear"; + case DAHDI_SIG_HDLCRAW: + return "HDLCRAW"; + case DAHDI_SIG_HDLCFCS: + return "HDLCFCS"; + case DAHDI_SIG_HDLCNET: + return "HDLCNET"; + case DAHDI_SIG_HARDHDLC: + return "Hardware-assisted HDLC"; + case DAHDI_SIG_MTP2: + return "MTP2"; + case DAHDI_SIG_SLAVE: + return "Slave"; + case DAHDI_SIG_CAS: + return "CAS"; + case DAHDI_SIG_DACS: + return "DACS"; + case DAHDI_SIG_DACS_RBS: + return "DACS+RBS"; + case DAHDI_SIG_SF: + return "SF (ToneOnly)"; + case DAHDI_SIG_NONE: + default: + return "Unconfigured"; + } +} + +static int fill_alarm_string(char *buf, int count, int alarms) +{ + int len; + + if (alarms <= 0) + return 0; + + len = snprintf(buf, count, "%s%s%s%s%s%s", + (alarms & DAHDI_ALARM_BLUE) ? "BLUE " : "", + (alarms & DAHDI_ALARM_YELLOW) ? "YELLOW " : "", + (alarms & DAHDI_ALARM_RED) ? "RED " : "", + (alarms & DAHDI_ALARM_LOOPBACK) ? "LOOP " : "", + (alarms & DAHDI_ALARM_RECOVER) ? "RECOVERING " : "", + (alarms & DAHDI_ALARM_NOTOPEN) ? "NOTOPEN " : ""); + + if (len > 0) + buf[--len] = '\0'; /* strip last space */ + + return len; +} + +static int dahdi_proc_read(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int x, len = 0, real_count; + long span; + + /* In Linux 2.6, page is always PROC_BLOCK_SIZE=(PAGE_SIZE-1024) bytes. + * 0name) + len += snprintf(page + len, count - len, "Span %ld: %s ", + span, spans[span]->name); + if (spans[span]->desc) + len += snprintf(page + len, count - len, "\"%s\"", + spans[span]->desc); + else + len += snprintf(page + len, count - len, "\"\""); + + if (spans[span] == master) + len += snprintf(page + len, count - len, " (MASTER)"); + + if (spans[span]->lineconfig) { + /* framing first */ + if (spans[span]->lineconfig & DAHDI_CONFIG_B8ZS) + len += snprintf(page + len, count - len, " B8ZS/"); + else if (spans[span]->lineconfig & DAHDI_CONFIG_AMI) + len += snprintf(page + len, count - len, " AMI/"); + else if (spans[span]->lineconfig & DAHDI_CONFIG_HDB3) + len += snprintf(page + len, count - len, " HDB3/"); + /* then coding */ + if (spans[span]->lineconfig & DAHDI_CONFIG_ESF) + len += snprintf(page + len, count - len, "ESF"); + else if (spans[span]->lineconfig & DAHDI_CONFIG_D4) + len += snprintf(page + len, count - len, "D4"); + else if (spans[span]->lineconfig & DAHDI_CONFIG_CCS) + len += snprintf(page + len, count - len, "CCS"); + /* E1's can enable CRC checking */ + if (spans[span]->lineconfig & DAHDI_CONFIG_CRC4) + len += snprintf(page + len, count - len, "/CRC4"); + } + + len += snprintf(page + len, count - len, " "); + + /* list alarms */ + len += fill_alarm_string(page + len, count - len, spans[span]->alarms); + if (spans[span]->syncsrc && + (spans[span]->syncsrc == spans[span]->spanno)) + len += snprintf(page + len, count - len, "ClockSource "); + len += snprintf(page + len, count - len, "\n"); + if (spans[span]->bpvcount) + len += snprintf(page + len, count - len, "\tBPV count: %d\n", + spans[span]->bpvcount); + if (spans[span]->crc4count) + len += snprintf(page + len, count - len, + "\tCRC4 error count: %d\n", + spans[span]->crc4count); + if (spans[span]->ebitcount) + len += snprintf(page + len, count - len, + "\tE-bit error count: %d\n", + spans[span]->ebitcount); + if (spans[span]->fascount) + len += snprintf(page + len, count - len, + "\tFAS error count: %d\n", + spans[span]->fascount); + if (spans[span]->irqmisses) + len += snprintf(page + len, count - len, + "\tIRQ misses: %d\n", + spans[span]->irqmisses); + if (spans[span]->timingslips) + len += snprintf(page + len, count - len, + "\tTiming slips: %d\n", + spans[span]->timingslips); + len += snprintf(page + len, count - len, "\n"); + + for (x = 0; x < spans[span]->channels; x++) { + struct dahdi_chan *chan = spans[span]->chans[x]; + + if (chan->name) + len += snprintf(page + len, count - len, + "\t%4d %s ", chan->channo, chan->name); + + if (chan->sig) { + if (chan->sig == DAHDI_SIG_SLAVE) + len += snprintf(page+len, count-len, "%s ", + sigstr(chan->master->sig)); + else { + len += snprintf(page+len, count-len, "%s ", + sigstr(chan->sig)); + if (chan->nextslave && + (chan->master->channo == chan->channo)) + len += snprintf(page+len, count-len, + "Master "); + } + } + + if (test_bit(DAHDI_FLAGBIT_OPEN, &chan->flags)) + len += snprintf(page + len, count - len, "(In use) "); + +#ifdef OPTIMIZE_CHANMUTE + if (chan->chanmute) + len += snprintf(page+len, count-len, "(no pcm) "); +#endif + + len += fill_alarm_string(page+len, count-len, + chan->chan_alarms); + + if (chan->ec_factory) + len += snprintf(page+len, count-len, "(SWEC: %s) ", + chan->ec_factory->name); + + if (chan->ec_state) + len += snprintf(page+len, count-len, "(EC: %s) ", + chan->ec_state->ops->name); + + len += snprintf(page+len, count-len, "\n"); + + /* If everything printed so far is before beginning + * of request */ + if (len <= off) { + off -= len; + len = 0; + } + + /* stop if we've already generated enough */ + if (len > off + count) + break; + /* stop if we're NEAR danger limit. let it be -128 bytes. */ + if (len > count-128) + break; + } + count = real_count; + /* If everything printed so far is before beginning of request */ + if (len <= off) { + off = 0; + len = 0; + } + *start = page + off; + len -= off; /* un-count any remaining offset */ + *eof = 1; + if (len > count) + len = count; /* don't return bytes not asked for */ + return len; +} +#endif + +static int dahdi_first_empty_alias(void) +{ + /* Find the first conference which has no alias pointing to it */ + int x; + for (x=1;x 0; x--) { + if (confrev[x]) { + maxconfs = x + 1; + return; + } + } + + maxconfs = 0; +} + +static void recalc_maxlinks(void) +{ + int x; + + for (x = DAHDI_MAX_CONF - 1; x > 0; x--) { + if (conf_links[x].src || conf_links[x].dst) { + maxlinks = x + 1; + return; + } + } + + maxlinks = 0; +} + +static int dahdi_first_empty_conference(void) +{ + /* Find the first conference which has no alias */ + int x; + + for (x = DAHDI_MAX_CONF - 1; x > 0; x--) { + if (!confalias[x]) + return x; + } + + return -1; +} + +static int dahdi_get_conf_alias(int x) +{ + int a; + + if (confalias[x]) + return confalias[x]; + + /* Allocate an alias */ + a = dahdi_first_empty_alias(); + confalias[x] = a; + confrev[a] = x; + + /* Highest conference may have changed */ + recalc_maxconfs(); + + return a; +} + +static void dahdi_check_conf(int x) +{ + int y; + + /* return if no valid conf number */ + if (x <= 0) + return; + + /* Return if there is no alias */ + if (!confalias[x]) + return; + + for (y = 0; y < maxchans; y++) { + if (chans[y] && (chans[y]->confna == x) && + ((chans[y]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONF || + (chans[y]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONFANN || + (chans[y]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONFMON || + (chans[y]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONFANNMON || + (chans[y]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_REALANDPSEUDO)) { + return; + } + } + + /* If we get here, nobody is in the conference anymore. Clear it out + both forward and reverse */ + confrev[confalias[x]] = 0; + confalias[x] = 0; + + /* Highest conference may have changed */ + recalc_maxconfs(); +} + +/* enqueue an event on a channel */ +static void __qevent(struct dahdi_chan *chan, int event) +{ + /* if full, ignore */ + if ((chan->eventoutidx == 0) && (chan->eventinidx == (DAHDI_MAX_EVENTSIZE - 1))) + return; + + /* if full, ignore */ + if (chan->eventinidx == (chan->eventoutidx - 1)) + return; + + /* save the event */ + chan->eventbuf[chan->eventinidx++] = event; + + /* wrap the index, if necessary */ + if (chan->eventinidx >= DAHDI_MAX_EVENTSIZE) + chan->eventinidx = 0; + + /* wake em all up */ + if (chan->iomask & DAHDI_IOMUX_SIGEVENT) + wake_up_interruptible(&chan->eventbufq); + + wake_up_interruptible(&chan->readbufq); + wake_up_interruptible(&chan->writebufq); + wake_up_interruptible(&chan->sel); + + return; +} + +void dahdi_qevent_nolock(struct dahdi_chan *chan, int event) +{ + __qevent(chan, event); +} + +void dahdi_qevent_lock(struct dahdi_chan *chan, int event) +{ + unsigned long flags; + spin_lock_irqsave(&chan->lock, flags); + __qevent(chan, event); + spin_unlock_irqrestore(&chan->lock, flags); +} + +/* sleep in user space until woken up. Equivilant of tsleep() in BSD */ +static int schluffen(wait_queue_head_t *q) +{ + DECLARE_WAITQUEUE(wait, current); + + add_wait_queue(q, &wait); + current->state = TASK_INTERRUPTIBLE; + + if (!signal_pending(current)) + schedule(); + + current->state = TASK_RUNNING; + remove_wait_queue(q, &wait); + + if (signal_pending(current)) + return -ERESTARTSYS; + + return 0; +} + +static inline void calc_fcs(struct dahdi_chan *ss, int inwritebuf) +{ + int x; + unsigned int fcs = PPP_INITFCS; + unsigned char *data = ss->writebuf[inwritebuf]; + int len = ss->writen[inwritebuf]; + + /* Not enough space to do FCS calculation */ + if (len < 2) + return; + + for (x = 0; x < len - 2; x++) + fcs = PPP_FCS(fcs, data[x]); + + fcs ^= 0xffff; + /* Send out the FCS */ + data[len - 2] = (fcs & 0xff); + data[len - 1] = (fcs >> 8) & 0xff; +} + +static int dahdi_reallocbufs(struct dahdi_chan *ss, int j, int numbufs) +{ + unsigned char *newbuf, *oldbuf; + unsigned long flags; + int x; + + /* Check numbufs */ + if (numbufs < 2) + numbufs = 2; + + if (numbufs > DAHDI_MAX_NUM_BUFS) + numbufs = DAHDI_MAX_NUM_BUFS; + + /* We need to allocate our buffers now */ + if (j) { + if (!(newbuf = kcalloc(j * 2, numbufs, GFP_KERNEL))) + return -ENOMEM; + } else + newbuf = NULL; + + /* Now that we've allocated our new buffer, we can safely + move things around... */ + + spin_lock_irqsave(&ss->lock, flags); + + ss->blocksize = j; /* set the blocksize */ + oldbuf = ss->readbuf[0]; /* Keep track of the old buffer */ + ss->readbuf[0] = NULL; + + if (newbuf) { + for (x = 0; x < numbufs; x++) { + ss->readbuf[x] = newbuf + x * j; + ss->writebuf[x] = newbuf + (numbufs + x) * j; + } + } else { + for (x = 0; x < numbufs; x++) { + ss->readbuf[x] = NULL; + ss->writebuf[x] = NULL; + } + } + + /* Mark all buffers as empty */ + for (x = 0; x < numbufs; x++) { + ss->writen[x] = + ss->writeidx[x]= + ss->readn[x]= + ss->readidx[x] = 0; + } + + /* Keep track of where our data goes (if it goes + anywhere at all) */ + if (newbuf) { + ss->inreadbuf = 0; + ss->inwritebuf = 0; + } else { + ss->inreadbuf = -1; + ss->inwritebuf = -1; + } + + ss->outreadbuf = -1; + ss->outwritebuf = -1; + ss->numbufs = numbufs; + + if ((ss->txbufpolicy == DAHDI_POLICY_WHEN_FULL) || (ss->txbufpolicy == DAHDI_POLICY_HALF_FULL)) + ss->txdisable = 1; + else + ss->txdisable = 0; + + if (ss->rxbufpolicy == DAHDI_POLICY_WHEN_FULL) + ss->rxdisable = 1; + else + ss->rxdisable = 0; + + spin_unlock_irqrestore(&ss->lock, flags); + + if (oldbuf) + kfree(oldbuf); + + return 0; +} + +static int dahdi_hangup(struct dahdi_chan *chan); +static void dahdi_set_law(struct dahdi_chan *chan, int law); + +/* Pull a DAHDI_CHUNKSIZE piece off the queue. Returns + 0 on success or -1 on failure. If failed, provides + silence */ +static int __buf_pull(struct confq *q, u_char *data, struct dahdi_chan *c, char *label) +{ + int oldoutbuf = q->outbuf; + /* Ain't nuffin to read */ + if (q->outbuf < 0) { + if (data) + memset(data, DAHDI_LIN2X(0,c), DAHDI_CHUNKSIZE); + return -1; + } + if (data) + memcpy(data, q->buf[q->outbuf], DAHDI_CHUNKSIZE); + q->outbuf = (q->outbuf + 1) % DAHDI_CB_SIZE; + + /* Won't be nuffin next time */ + if (q->outbuf == q->inbuf) { + q->outbuf = -1; + } + + /* If they thought there was no space then + there is now where we just read */ + if (q->inbuf < 0) + q->inbuf = oldoutbuf; + return 0; +} + +/* Returns a place to put stuff, or NULL if there is + no room */ + +static u_char *__buf_pushpeek(struct confq *q) +{ + if (q->inbuf < 0) + return NULL; + return q->buf[q->inbuf]; +} + +static u_char *__buf_peek(struct confq *q) +{ + if (q->outbuf < 0) + return NULL; + return q->buf[q->outbuf]; +} + +#ifdef BUF_MUNGE +static u_char *__buf_cpush(struct confq *q) +{ + int pos; + /* If we have no space, return where the + last space that we *did* have was */ + if (q->inbuf > -1) + return NULL; + pos = q->outbuf - 1; + if (pos < 0) + pos += DAHDI_CB_SIZE; + return q->buf[pos]; +} + +static void __buf_munge(struct dahdi_chan *chan, u_char *old, u_char *new) +{ + /* Run a weighted average of the old and new, in order to + mask a missing sample */ + int x; + int val; + for (x=0;xinbuf; + if (q->inbuf < 0) { + return -1; + } + if (data) + /* Copy in the data */ + memcpy(q->buf[q->inbuf], data, DAHDI_CHUNKSIZE); + + /* Advance the inbuf pointer */ + q->inbuf = (q->inbuf + 1) % DAHDI_CB_SIZE; + + if (q->inbuf == q->outbuf) { + /* No space anymore... */ + q->inbuf = -1; + } + /* If they don't think data is ready, let + them know it is now */ + if (q->outbuf < 0) { + q->outbuf = oldinbuf; + } + return 0; +} + +static void reset_conf(struct dahdi_chan *chan) +{ + int x; + + /* Empty out buffers and reset to initialization */ + + for (x = 0; x < DAHDI_CB_SIZE; x++) + chan->confin.buf[x] = chan->confin.buffer + DAHDI_CHUNKSIZE * x; + + chan->confin.inbuf = 0; + chan->confin.outbuf = -1; + + for (x = 0; x < DAHDI_CB_SIZE; x++) + chan->confout.buf[x] = chan->confout.buffer + DAHDI_CHUNKSIZE * x; + + chan->confout.inbuf = 0; + chan->confout.outbuf = -1; +} + + +static const struct dahdi_echocan_factory *find_echocan(const char *name) +{ + struct ecfactory *cur; + char name_upper[strlen(name) + 1]; + char *c; + const char *d; + char modname_buf[128] = "dahdi_echocan_"; + unsigned int tried_once = 0; + + for (c = name_upper, d = name; *d; c++, d++) { + *c = toupper(*d); + } + + *c = '\0'; + +retry: + read_lock(&ecfactory_list_lock); + + list_for_each_entry(cur, &ecfactory_list, list) { + if (!strcmp(name_upper, cur->ec->name)) { +#ifdef USE_ECHOCAN_REFCOUNT + if (try_module_get(cur->owner)) { + read_unlock(&ecfactory_list_lock); + return cur->ec; + } else { + read_unlock(&ecfactory_list_lock); + return NULL; + } +#else + read_unlock(&ecfactory_list_lock); + return cur->ec; +#endif + } + } + + read_unlock(&ecfactory_list_lock); + + if (tried_once) { + return NULL; + } + + /* couldn't find it, let's try to load it */ + + for (c = &modname_buf[strlen(modname_buf)], d = name; *d; c++, d++) { + *c = tolower(*d); + } + + request_module("%s", modname_buf); + + tried_once = 1; + + /* and try one more time */ + goto retry; +} + +static void release_echocan(const struct dahdi_echocan_factory *ec) +{ +#ifdef USE_ECHOCAN_REFCOUNT + if (ec) + module_put(ec->owner); +#endif +} + +/** + * close_channel - close the channel, resetting any channel variables + * @chan: the dahdi_chan to close + * + * This function might be called before the channel is placed on the global + * array of channels, (chans), and therefore, neither this function nor it's + * children should depend on the dahdi_chan.channo member which is not set yet. + */ +static void close_channel(struct dahdi_chan *chan) +{ + unsigned long flags; + void *rxgain = NULL; + struct dahdi_echocan_state *ec_state; + const struct dahdi_echocan_factory *ec_current; + int oldconf; + short *readchunkpreec; +#ifdef CONFIG_DAHDI_PPP + struct ppp_channel *ppp; +#endif + + might_sleep(); + + /* XXX Buffers should be send out before reallocation!!! XXX */ + if (!(chan->flags & DAHDI_FLAG_NOSTDTXRX)) + dahdi_reallocbufs(chan, 0, 0); + spin_lock_irqsave(&chan->lock, flags); +#ifdef CONFIG_DAHDI_PPP + ppp = chan->ppp; + chan->ppp = NULL; +#endif + ec_state = chan->ec_state; + chan->ec_state = NULL; + ec_current = chan->ec_current; + chan->ec_current = NULL; + readchunkpreec = chan->readchunkpreec; + chan->readchunkpreec = NULL; + chan->curtone = NULL; + if (chan->curzone) + atomic_dec(&chan->curzone->refcount); + chan->curzone = NULL; + chan->cadencepos = 0; + chan->pdialcount = 0; + dahdi_hangup(chan); + chan->itimerset = chan->itimer = 0; + chan->pulsecount = 0; + chan->pulsetimer = 0; + chan->ringdebtimer = 0; + init_waitqueue_head(&chan->sel); + init_waitqueue_head(&chan->readbufq); + init_waitqueue_head(&chan->writebufq); + init_waitqueue_head(&chan->eventbufq); + init_waitqueue_head(&chan->txstateq); + chan->txdialbuf[0] = '\0'; + chan->digitmode = DIGIT_MODE_DTMF; + chan->dialing = 0; + chan->afterdialingtimer = 0; + /* initialize IO MUX mask */ + chan->iomask = 0; + /* save old conf number, if any */ + oldconf = chan->confna; + /* initialize conference variables */ + chan->_confn = 0; + if ((chan->sig & __DAHDI_SIG_DACS) != __DAHDI_SIG_DACS) { + chan->confna = 0; + chan->confmode = 0; + } + chan->confmute = 0; + /* release conference resource, if any to release */ + if (oldconf) dahdi_check_conf(oldconf); + chan->gotgs = 0; + reset_conf(chan); + + if (chan->gainalloc && chan->rxgain) + rxgain = chan->rxgain; + + chan->rxgain = defgain; + chan->txgain = defgain; + chan->gainalloc = 0; + chan->eventinidx = chan->eventoutidx = 0; + chan->flags &= ~(DAHDI_FLAG_LOOPED | DAHDI_FLAG_LINEAR | DAHDI_FLAG_PPP | DAHDI_FLAG_SIGFREEZE); + + dahdi_set_law(chan,0); + + memset(chan->conflast, 0, sizeof(chan->conflast)); + memset(chan->conflast1, 0, sizeof(chan->conflast1)); + memset(chan->conflast2, 0, sizeof(chan->conflast2)); + + if (chan->span && chan->span->dacs && oldconf) + chan->span->dacs(chan, NULL); + + if (ec_state) { + ec_state->ops->echocan_free(chan, ec_state); + release_echocan(ec_current); + } + + spin_unlock_irqrestore(&chan->lock, flags); + + if (rxgain) + kfree(rxgain); + if (readchunkpreec) + kfree(readchunkpreec); + +#ifdef CONFIG_DAHDI_PPP + if (ppp) { + tasklet_kill(&chan->ppp_calls); + skb_queue_purge(&chan->ppp_rq); + ppp_unregister_channel(ppp); + kfree(ppp); + } +#endif + +} + +static int free_tone_zone(int num) +{ + struct dahdi_zone *z = NULL; + int res = 0; + + if ((num >= DAHDI_TONE_ZONE_MAX) || (num < 0)) + return -EINVAL; + + write_lock(&zone_lock); + if (tone_zones[num]) { + if (!atomic_read(&tone_zones[num]->refcount)) { + z = tone_zones[num]; + tone_zones[num] = NULL; + } else { + res = -EBUSY; + } + } + write_unlock(&zone_lock); + + if (z) + kfree(z); + + return res; +} + +static int dahdi_register_tone_zone(int num, struct dahdi_zone *zone) +{ + int res = 0; + + if ((num >= DAHDI_TONE_ZONE_MAX) || (num < 0)) + return -EINVAL; + + write_lock(&zone_lock); + if (tone_zones[num]) { + res = -EINVAL; + } else { + res = 0; + tone_zones[num] = zone; + } + write_unlock(&zone_lock); + + if (!res) + module_printk(KERN_INFO, "Registered tone zone %d (%s)\n", num, zone->name); + + return res; +} + +static int start_tone_digit(struct dahdi_chan *chan, int tone) +{ + struct dahdi_tone *playtone = NULL; + int base, max; + + if (!chan->curzone) + return -ENODATA; + + switch (chan->digitmode) { + case DIGIT_MODE_DTMF: + /* Set dialing so that a dial operation doesn't interrupt this tone */ + chan->dialing = 1; + base = DAHDI_TONE_DTMF_BASE; + max = DAHDI_TONE_DTMF_MAX; + break; + case DIGIT_MODE_MFR2_FWD: + base = DAHDI_TONE_MFR2_FWD_BASE; + max = DAHDI_TONE_MFR2_FWD_MAX; + break; + case DIGIT_MODE_MFR2_REV: + base = DAHDI_TONE_MFR2_REV_BASE; + max = DAHDI_TONE_MFR2_REV_MAX; + break; + default: + return -EINVAL; + } + + if ((tone < base) || (tone > max)) + return -EINVAL; + + switch (chan->digitmode) { + case DIGIT_MODE_DTMF: + playtone = &chan->curzone->dtmf_continuous[tone - base]; + break; + case DIGIT_MODE_MFR2_FWD: + playtone = &chan->curzone->mfr2_fwd_continuous[tone - base]; + break; + case DIGIT_MODE_MFR2_REV: + playtone = &chan->curzone->mfr2_rev_continuous[tone - base]; + break; + } + + if (!playtone || !playtone->tonesamples) + return -ENOSYS; + + chan->curtone = playtone; + + return 0; +} + +static int start_tone(struct dahdi_chan *chan, int tone) +{ + int res = -EINVAL; + + /* Stop the current tone, no matter what */ + chan->tonep = 0; + chan->curtone = NULL; + chan->pdialcount = 0; + chan->txdialbuf[0] = '\0'; + chan->dialing = 0; + + if (tone == -1) { + /* Just stop the current tone */ + res = 0; + } else if (!chan->curzone) { + static int __warnonce = 1; + if (__warnonce) { + __warnonce = 0; + /* The tonezones are loaded by dahdi_cfg based on /etc/dahdi/system.conf. */ + module_printk(KERN_WARNING, "DAHDI: Cannot start tones until tone zone is loaded.\n"); + } + /* Note that no tone zone exists at the moment */ + res = -ENODATA; + } else if ((tone >= 0 && tone <= DAHDI_TONE_MAX)) { + /* Have a tone zone */ + if (chan->curzone->tones[tone]) { + chan->curtone = chan->curzone->tones[tone]; + res = 0; + } else { /* Indicate that zone is loaded but no such tone exists */ + res = -ENOSYS; + } + } else if (chan->digitmode == DIGIT_MODE_DTMF || + chan->digitmode == DIGIT_MODE_MFR2_FWD || + chan->digitmode == DIGIT_MODE_MFR2_REV) { + res = start_tone_digit(chan, tone); + } else { + chan->dialing = 0; + res = -EINVAL; + } + + if (chan->curtone) + dahdi_init_tone_state(&chan->ts, chan->curtone); + + return res; +} + +static int set_tone_zone(struct dahdi_chan *chan, int zone) +{ + int res = 0; + struct dahdi_zone *z; + + /* Do not call with the channel locked. */ + + if (zone == -1) + zone = default_zone; + + if ((zone >= DAHDI_TONE_ZONE_MAX) || (zone < 0)) + return -EINVAL; + + read_lock(&zone_lock); + + if ((z = tone_zones[zone])) { + unsigned long flags; + + spin_lock_irqsave(&chan->lock, flags); + + if (chan->curzone) + atomic_dec(&chan->curzone->refcount); + + atomic_inc(&z->refcount); + chan->curzone = z; + chan->tonezone = zone; + memcpy(chan->ringcadence, z->ringcadence, sizeof(chan->ringcadence)); + + spin_unlock_irqrestore(&chan->lock, flags); + } else { + res = -ENODATA; + } + + read_unlock(&zone_lock); + + return res; +} + +static void dahdi_set_law(struct dahdi_chan *chan, int law) +{ + if (!law) { + if (chan->deflaw) + law = chan->deflaw; + else + if (chan->span) law = chan->span->deflaw; + else law = DAHDI_LAW_MULAW; + } + if (law == DAHDI_LAW_ALAW) { + chan->xlaw = __dahdi_alaw; +#ifdef CONFIG_CALC_XLAW + chan->lineartoxlaw = __dahdi_lineartoalaw; +#else + chan->lin2x = __dahdi_lin2a; +#endif + } else { + chan->xlaw = __dahdi_mulaw; +#ifdef CONFIG_CALC_XLAW + chan->lineartoxlaw = __dahdi_lineartoulaw; +#else + chan->lin2x = __dahdi_lin2mu; +#endif + } +} + +static int dahdi_chan_reg(struct dahdi_chan *chan) +{ + int x; + unsigned long flags; + + might_sleep(); + + spin_lock_init(&chan->lock); + if (!chan->master) + chan->master = chan; + if (!chan->readchunk) + chan->readchunk = chan->sreadchunk; + if (!chan->writechunk) + chan->writechunk = chan->swritechunk; + dahdi_set_law(chan, 0); + close_channel(chan); + + write_lock_irqsave(&chan_lock, flags); + for (x = 1; x < DAHDI_MAX_CHANNELS; x++) { + if (chans[x]) + continue; + + chans[x] = chan; + if (maxchans < x + 1) + maxchans = x + 1; + chan->channo = x; + write_unlock_irqrestore(&chan_lock, flags); + /* set this AFTER running close_channel() so that + HDLC channels wont cause hangage */ + set_bit(DAHDI_FLAGBIT_REGISTERED, &chan->flags); + break; + } + + if (DAHDI_MAX_CHANNELS == x) { + write_unlock_irqrestore(&chan_lock, flags); + module_printk(KERN_ERR, "No more channels available\n"); + return -ENOMEM; + } + + return 0; +} + +char *dahdi_lboname(int x) +{ + if ((x < 0) || (x > 7)) + return "Unknown"; + return dahdi_txlevelnames[x]; +} + +#if defined(CONFIG_DAHDI_NET) || defined(CONFIG_DAHDI_PPP) +static inline void print_debug_writebuf(struct dahdi_chan* ss, struct sk_buff *skb, int oldbuf) +{ +#ifdef CONFIG_DAHDI_DEBUG + int x; + + module_printk(KERN_NOTICE, "Buffered %d bytes to go out in buffer %d\n", ss->writen[oldbuf], oldbuf); + module_printk(KERN_DEBUG ""); + for (x=0;xwriten[oldbuf];x++) + printk("%02x ", ss->writebuf[oldbuf][x]); + printk("\n"); +#endif +} +#endif + +#ifdef CONFIG_DAHDI_NET +#ifdef NEW_HDLC_INTERFACE +static int dahdi_net_open(struct net_device *dev) +{ + int res = hdlc_open(dev); + struct dahdi_chan *ms = dev_to_ztchan(dev); + +/* if (!dev->hard_start_xmit) return res; is this really necessary? --byg */ + if (res) /* this is necessary to avoid kernel panic when UNSPEC link encap, proven --byg */ + return res; +#else +static int dahdi_net_open(hdlc_device *hdlc) +{ + struct dahdi_chan *ms = hdlc_to_ztchan(hdlc); + int res; +#endif + if (!ms) { + module_printk(KERN_NOTICE, "dahdi_net_open: nothing??\n"); + return -EINVAL; + } + if (test_bit(DAHDI_FLAGBIT_OPEN, &ms->flags)) { + module_printk(KERN_NOTICE, "%s is already open!\n", ms->name); + return -EBUSY; + } + if (!(ms->flags & DAHDI_FLAG_NETDEV)) { + module_printk(KERN_NOTICE, "%s is not a net device!\n", ms->name); + return -EINVAL; + } + ms->txbufpolicy = DAHDI_POLICY_IMMEDIATE; + ms->rxbufpolicy = DAHDI_POLICY_IMMEDIATE; + + res = dahdi_reallocbufs(ms, DAHDI_DEFAULT_MTU_MRU, DAHDI_DEFAULT_NUM_BUFS); + if (res) + return res; + + fasthdlc_init(&ms->rxhdlc, (ms->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + fasthdlc_init(&ms->txhdlc, (ms->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + ms->infcs = PPP_INITFCS; + + netif_start_queue(ztchan_to_dev(ms)); + +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "DAHDINET: Opened channel %d name %s\n", ms->channo, ms->name); +#endif + return 0; +} + +static int dahdi_register_hdlc_device(struct net_device *dev, const char *dev_name) +{ + int result; + + if (dev_name && *dev_name) { + if ((result = dev_alloc_name(dev, dev_name)) < 0) + return result; + } + result = register_netdev(dev); + if (result != 0) + return -EIO; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,14) + if (netif_carrier_ok(dev)) + netif_carrier_off(dev); /* no carrier until DCD goes up */ +#endif + return 0; +} + +#ifdef NEW_HDLC_INTERFACE +static int dahdi_net_stop(struct net_device *dev) +{ + hdlc_device *h = dev_to_hdlc(dev); + struct dahdi_hdlc *hdlc = h->priv; + +#else +static void dahdi_net_close(hdlc_device *hdlc) +{ +#endif + struct dahdi_chan *ms = hdlc_to_ztchan(hdlc); + if (!ms) { +#ifdef NEW_HDLC_INTERFACE + module_printk(KERN_NOTICE, "dahdi_net_stop: nothing??\n"); + return 0; +#else + module_printk(KERN_NOTICE, "dahdi_net_close: nothing??\n"); + return; +#endif + } + if (!(ms->flags & DAHDI_FLAG_NETDEV)) { +#ifdef NEW_HDLC_INTERFACE + module_printk(KERN_NOTICE, "dahdi_net_stop: %s is not a net device!\n", ms->name); + return 0; +#else + module_printk(KERN_NOTICE, "dahdi_net_close: %s is not a net device!\n", ms->name); + return; +#endif + } + /* Not much to do here. Just deallocate the buffers */ + netif_stop_queue(ztchan_to_dev(ms)); + dahdi_reallocbufs(ms, 0, 0); + hdlc_close(dev); +#ifdef NEW_HDLC_INTERFACE + return 0; +#else + return; +#endif +} + +#ifdef NEW_HDLC_INTERFACE +/* kernel 2.4.20+ has introduced attach function, dunno what to do, + just copy sources from dscc4 to be sure and ready for further mastering, + NOOP right now (i.e. really a stub) --byg */ +static int dahdi_net_attach(struct net_device *dev, unsigned short encoding, + unsigned short parity) +{ +/* struct net_device *dev = hdlc_to_dev(hdlc); + struct dscc4_dev_priv *dpriv = dscc4_priv(dev); + + if (encoding != ENCODING_NRZ && + encoding != ENCODING_NRZI && + encoding != ENCODING_FM_MARK && + encoding != ENCODING_FM_SPACE && + encoding != ENCODING_MANCHESTER) + return -EINVAL; + + if (parity != PARITY_NONE && + parity != PARITY_CRC16_PR0_CCITT && + parity != PARITY_CRC16_PR1_CCITT && + parity != PARITY_CRC32_PR0_CCITT && + parity != PARITY_CRC32_PR1_CCITT) + return -EINVAL; + + dpriv->encoding = encoding; + dpriv->parity = parity;*/ + return 0; +} +#endif + +static struct dahdi_hdlc *dahdi_hdlc_alloc(void) +{ + return kzalloc(sizeof(struct dahdi_hdlc), GFP_KERNEL); +} + +#ifdef NEW_HDLC_INTERFACE +static int dahdi_xmit(struct sk_buff *skb, struct net_device *dev) +{ + /* FIXME: this construction seems to be not very optimal for me but I could find nothing better at the moment (Friday, 10PM :( ) --byg */ +/* struct dahdi_chan *ss = hdlc_to_ztchan(list_entry(dev, struct dahdi_hdlc, netdev.netdev));*/ + struct dahdi_chan *ss = dev_to_ztchan(dev); + struct net_device_stats *stats = hdlc_stats(dev); + +#else +static int dahdi_xmit(hdlc_device *hdlc, struct sk_buff *skb) +{ + struct dahdi_chan *ss = hdlc_to_ztchan(hdlc); + struct net_device *dev = &ss->hdlcnetdev->netdev.netdev; + struct net_device_stats *stats = &ss->hdlcnetdev->netdev.stats; +#endif + int retval = 1; + int x,oldbuf; + unsigned int fcs; + unsigned char *data; + unsigned long flags; + /* See if we have any buffers */ + spin_lock_irqsave(&ss->lock, flags); + if (skb->len > ss->blocksize - 2) { + module_printk(KERN_ERR, "dahdi_xmit(%s): skb is too large (%d > %d)\n", dev->name, skb->len, ss->blocksize -2); + stats->tx_dropped++; + retval = 0; + } else if (ss->inwritebuf >= 0) { + /* We have a place to put this packet */ + /* XXX We should keep the SKB and avoid the memcpy XXX */ + data = ss->writebuf[ss->inwritebuf]; + memcpy(data, skb->data, skb->len); + ss->writen[ss->inwritebuf] = skb->len; + ss->writeidx[ss->inwritebuf] = 0; + /* Calculate the FCS */ + fcs = PPP_INITFCS; + for (x=0;xlen;x++) + fcs = PPP_FCS(fcs, data[x]); + /* Invert it */ + fcs ^= 0xffff; + /* Send it out LSB first */ + data[ss->writen[ss->inwritebuf]++] = (fcs & 0xff); + data[ss->writen[ss->inwritebuf]++] = (fcs >> 8) & 0xff; + /* Advance to next window */ + oldbuf = ss->inwritebuf; + ss->inwritebuf = (ss->inwritebuf + 1) % ss->numbufs; + + if (ss->inwritebuf == ss->outwritebuf) { + /* Whoops, no more space. */ + ss->inwritebuf = -1; + + netif_stop_queue(ztchan_to_dev(ss)); + } + if (ss->outwritebuf < 0) { + /* Let the interrupt handler know there's + some space for us */ + ss->outwritebuf = oldbuf; + } + dev->trans_start = jiffies; + stats->tx_packets++; + stats->tx_bytes += ss->writen[oldbuf]; + print_debug_writebuf(ss, skb, oldbuf); + retval = 0; + /* Free the SKB */ + dev_kfree_skb_any(skb); + } + spin_unlock_irqrestore(&ss->lock, flags); + return retval; +} + +#ifdef NEW_HDLC_INTERFACE +static int dahdi_net_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + return hdlc_ioctl(dev, ifr, cmd); +} +#else +static int dahdi_net_ioctl(hdlc_device *hdlc, struct ifreq *ifr, int cmd) +{ + return -EIO; +} +#endif + +#endif + +#ifdef CONFIG_DAHDI_PPP + +static int dahdi_ppp_xmit(struct ppp_channel *ppp, struct sk_buff *skb) +{ + + /* + * If we can't handle the packet right now, return 0. If we + * we handle or drop it, return 1. Always free if we return + * 1 and never if we return 0 + */ + struct dahdi_chan *ss = ppp->private; + int x,oldbuf; + unsigned int fcs; + unsigned char *data; + unsigned long flags; + int retval = 0; + + /* See if we have any buffers */ + spin_lock_irqsave(&ss->lock, flags); + if (!(test_bit(DAHDI_FLAGBIT_OPEN, &ss->flags))) { + module_printk(KERN_ERR, "Can't transmit on closed channel\n"); + retval = 1; + } else if (skb->len > ss->blocksize - 4) { + module_printk(KERN_ERR, "dahdi_ppp_xmit(%s): skb is too large (%d > %d)\n", ss->name, skb->len, ss->blocksize -2); + retval = 1; + } else if (ss->inwritebuf >= 0) { + /* We have a place to put this packet */ + /* XXX We should keep the SKB and avoid the memcpy XXX */ + data = ss->writebuf[ss->inwritebuf]; + /* Start with header of two bytes */ + /* Add "ALL STATIONS" and "UNNUMBERED" */ + data[0] = 0xff; + data[1] = 0x03; + ss->writen[ss->inwritebuf] = 2; + + /* Copy real data and increment amount written */ + memcpy(data + 2, skb->data, skb->len); + + ss->writen[ss->inwritebuf] += skb->len; + + /* Re-set index back to zero */ + ss->writeidx[ss->inwritebuf] = 0; + + /* Calculate the FCS */ + fcs = PPP_INITFCS; + for (x=0;xlen + 2;x++) + fcs = PPP_FCS(fcs, data[x]); + /* Invert it */ + fcs ^= 0xffff; + + /* Point past the real data now */ + data += (skb->len + 2); + + /* Send FCS out LSB first */ + data[0] = (fcs & 0xff); + data[1] = (fcs >> 8) & 0xff; + + /* Account for FCS length */ + ss->writen[ss->inwritebuf]+=2; + + /* Advance to next window */ + oldbuf = ss->inwritebuf; + ss->inwritebuf = (ss->inwritebuf + 1) % ss->numbufs; + + if (ss->inwritebuf == ss->outwritebuf) { + /* Whoops, no more space. */ + ss->inwritebuf = -1; + } + if (ss->outwritebuf < 0) { + /* Let the interrupt handler know there's + some space for us */ + ss->outwritebuf = oldbuf; + } + print_debug_writebuf(ss, skb, oldbuf); + retval = 1; + } + spin_unlock_irqrestore(&ss->lock, flags); + if (retval) { + /* Get rid of the SKB if we're returning non-zero */ + /* N.B. this is called in process or BH context so + dev_kfree_skb is OK. */ + dev_kfree_skb(skb); + } + return retval; +} + +static int dahdi_ppp_ioctl(struct ppp_channel *ppp, unsigned int cmd, unsigned long flags) +{ + return -EIO; +} + +static struct ppp_channel_ops ztppp_ops = +{ + .start_xmit = dahdi_ppp_xmit, + .ioctl = dahdi_ppp_ioctl, +}; + +#endif + +static void dahdi_chan_unreg(struct dahdi_chan *chan) +{ + int x; + unsigned long flags; + + might_sleep(); + +#ifdef CONFIG_DAHDI_NET + if (chan->flags & DAHDI_FLAG_NETDEV) { + unregister_hdlc_device(chan->hdlcnetdev->netdev); + free_netdev(chan->hdlcnetdev->netdev); + kfree(chan->hdlcnetdev); + chan->hdlcnetdev = NULL; + } +#endif + write_lock_irqsave(&chan_lock, flags); + if (test_bit(DAHDI_FLAGBIT_REGISTERED, &chan->flags)) { + chans[chan->channo] = NULL; + clear_bit(DAHDI_FLAGBIT_REGISTERED, &chan->flags); + } +#ifdef CONFIG_DAHDI_PPP + if (chan->ppp) { + module_printk(KERN_NOTICE, "HUH??? PPP still attached??\n"); + } +#endif + maxchans = 0; + for (x=1;xmaster == chan) { + chans[x]->master = chans[x]; + } + if ((chans[x]->confna == chan->channo) && + ((chans[x]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR || + (chans[x]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITORTX || + (chans[x]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITORBOTH || + (chans[x]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR_RX_PREECHO || + (chans[x]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR_TX_PREECHO || + (chans[x]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITORBOTH_PREECHO || + (chans[x]->confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_DIGITALMON)) { + /* Take them out of conference with us */ + /* release conference resource if any */ + if (chans[x]->confna) { + dahdi_check_conf(chans[x]->confna); + if (chans[x]->span && chans[x]->span->dacs) + chans[x]->span->dacs(chans[x], NULL); + } + chans[x]->confna = 0; + chans[x]->_confn = 0; + chans[x]->confmode = 0; + } + } + chan->channo = -1; + write_unlock_irqrestore(&chan_lock, flags); +} + +static ssize_t dahdi_chan_read(struct file *file, char *usrbuf, size_t count, int unit) +{ + struct dahdi_chan *chan = chans[unit]; + int amnt; + int res, rv; + int oldbuf,x; + unsigned long flags; + + /* Make sure count never exceeds 65k, and make sure it's unsigned */ + count &= 0xffff; + + if (!chan) + return -EINVAL; + + if (count < 1) + return -EINVAL; + + for (;;) { + spin_lock_irqsave(&chan->lock, flags); + if (chan->eventinidx != chan->eventoutidx) { + spin_unlock_irqrestore(&chan->lock, flags); + return -ELAST /* - chan->eventbuf[chan->eventoutidx]*/; + } + res = chan->outreadbuf; + if (chan->rxdisable) + res = -1; + spin_unlock_irqrestore(&chan->lock, flags); + if (res >= 0) + break; + if (file->f_flags & O_NONBLOCK) + return -EAGAIN; + rv = schluffen(&chan->readbufq); + if (rv) + return rv; + } + amnt = count; +/* added */ +#if 0 + if ((unit == 24) || (unit == 48) || (unit == 16) || (unit == 47)) { + int myamnt = amnt; + int x; + if (amnt > chan->readn[res]) + myamnt = chan->readn[res]; + module_printk(KERN_NOTICE, "dahdi_chan_read(unit: %d, inwritebuf: %d, outwritebuf: %d amnt: %d\n", + unit, chan->inwritebuf, chan->outwritebuf, myamnt); + + module_printk(KERN_DEBUG, "\t("); + for (x = 0; x < myamnt; x++) + printk((x ? " %02x" : "%02x"), (unsigned char)usrbuf[x]); + printk(")\n"); + } +#endif +/* end addition */ + if (chan->flags & DAHDI_FLAG_LINEAR) { + if (amnt > (chan->readn[res] << 1)) + amnt = chan->readn[res] << 1; + if (amnt) { + /* There seems to be a max stack size, so we have + to do this in smaller pieces */ + short lindata[128]; + int left = amnt >> 1; /* amnt is in bytes */ + int pos = 0; + int pass; + while (left) { + pass = left; + if (pass > 128) + pass = 128; + for (x = 0; x < pass; x++) + lindata[x] = DAHDI_XLAW(chan->readbuf[res][x + pos], chan); + if (copy_to_user(usrbuf + (pos << 1), lindata, pass << 1)) + return -EFAULT; + left -= pass; + pos += pass; + } + } + } else { + if (amnt > chan->readn[res]) + amnt = chan->readn[res]; + if (amnt) { + if (copy_to_user(usrbuf, chan->readbuf[res], amnt)) + return -EFAULT; + } + } + spin_lock_irqsave(&chan->lock, flags); + chan->readidx[res] = 0; + chan->readn[res] = 0; + oldbuf = res; + chan->outreadbuf = (res + 1) % chan->numbufs; + if (chan->outreadbuf == chan->inreadbuf) { + /* Out of stuff */ + chan->outreadbuf = -1; + if (chan->rxbufpolicy == DAHDI_POLICY_WHEN_FULL) + chan->rxdisable = 1; + } + if (chan->inreadbuf < 0) { + /* Notify interrupt handler that we have some space now */ + chan->inreadbuf = oldbuf; + } + spin_unlock_irqrestore(&chan->lock, flags); + + return amnt; +} + +static int num_filled_bufs(struct dahdi_chan *chan) +{ + int range1, range2; + + if (chan->inwritebuf < 0) { + return chan->numbufs; + } + + if (chan->outwritebuf < 0) { + return 0; + } + + if (chan->outwritebuf <= chan->inwritebuf) { + return chan->inwritebuf - chan->outwritebuf; + } + + /* This means (in > out) and we have wrap around */ + range1 = chan->numbufs - chan->outwritebuf; + range2 = chan->inwritebuf; + + return range1 + range2; +} + +static ssize_t dahdi_chan_write(struct file *file, const char *usrbuf, size_t count, int unit) +{ + unsigned long flags; + struct dahdi_chan *chan = chans[unit]; + int res, amnt, oldbuf, rv, x; + + /* Make sure count never exceeds 65k, and make sure it's unsigned */ + count &= 0xffff; + + if (!chan) + return -EINVAL; + + if (count < 1) { + return -EINVAL; + } + + for (;;) { + spin_lock_irqsave(&chan->lock, flags); + if ((chan->curtone || chan->pdialcount) && !(chan->flags & DAHDI_FLAG_PSEUDO)) { + chan->curtone = NULL; + chan->tonep = 0; + chan->dialing = 0; + chan->txdialbuf[0] = '\0'; + chan->pdialcount = 0; + } + if (chan->eventinidx != chan->eventoutidx) { + spin_unlock_irqrestore(&chan->lock, flags); + return -ELAST; + } + res = chan->inwritebuf; + spin_unlock_irqrestore(&chan->lock, flags); + if (res >= 0) + break; + if (file->f_flags & O_NONBLOCK) { +#ifdef BUFFER_DEBUG + printk("Error: Nonblock\n"); +#endif + return -EAGAIN; + } + /* Wait for something to be available */ + rv = schluffen(&chan->writebufq); + if (rv) { + return rv; + } + } + + amnt = count; + if (chan->flags & DAHDI_FLAG_LINEAR) { + if (amnt > (chan->blocksize << 1)) + amnt = chan->blocksize << 1; + } else { + if (amnt > chan->blocksize) + amnt = chan->blocksize; + } + +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "dahdi_chan_write(unit: %d, res: %d, outwritebuf: %d amnt: %d\n", + unit, res, chan->outwritebuf, amnt); +#endif +#if 0 + if ((unit == 24) || (unit == 48) || (unit == 16) || (unit == 47)) { + int x; + module_printk(KERN_NOTICE, "dahdi_chan_write/in(unit: %d, res: %d, outwritebuf: %d amnt: %d, txdisable: %d)\n", + unit, res, chan->outwritebuf, amnt, chan->txdisable); + module_printk(KERN_DEBUG, "\t("); for (x = 0; x < amnt; x++) module_printk(KERN_DEBUG, (x ? " %02x" : "%02x"), (unsigned char)usrbuf[x]); + module_printk(KERN_DEBUG, ")\n"); + } +#endif + + if (amnt) { + if (chan->flags & DAHDI_FLAG_LINEAR) { + /* There seems to be a max stack size, so we have + to do this in smaller pieces */ + short lindata[128]; + int left = amnt >> 1; /* amnt is in bytes */ + int pos = 0; + int pass; + while (left) { + pass = left; + if (pass > 128) + pass = 128; + if (copy_from_user(lindata, usrbuf + (pos << 1), pass << 1)) { + return -EFAULT; + } + left -= pass; + for (x = 0; x < pass; x++) + chan->writebuf[res][x + pos] = DAHDI_LIN2X(lindata[x], chan); + pos += pass; + } + chan->writen[res] = amnt >> 1; + } else { + if (copy_from_user(chan->writebuf[res], usrbuf, amnt)) { + return -EFAULT; + } + chan->writen[res] = amnt; + } + chan->writeidx[res] = 0; + if (chan->flags & DAHDI_FLAG_FCS) + calc_fcs(chan, res); + oldbuf = res; + spin_lock_irqsave(&chan->lock, flags); + chan->inwritebuf = (res + 1) % chan->numbufs; + + if (chan->inwritebuf == chan->outwritebuf) { + /* Don't stomp on the transmitter, just wait for them to + wake us up */ + chan->inwritebuf = -1; + /* Make sure the transmitter is transmitting in case of POLICY_WHEN_FULL */ + chan->txdisable = 0; + } + + if (chan->outwritebuf < 0) { + /* Okay, the interrupt handler has been waiting for us. Give them a buffer */ + chan->outwritebuf = oldbuf; + } + + if ((chan->txbufpolicy == DAHDI_POLICY_HALF_FULL) && (chan->txdisable)) { + if (num_filled_bufs(chan) >= (chan->numbufs >> 1)) { +#ifdef BUFFER_DEBUG + printk("Reached buffer fill mark of %d\n", num_filled_bufs(chan)); +#endif + chan->txdisable = 0; + } + } + +#ifdef BUFFER_DEBUG + if ((chan->statcount <= 0) || (amnt != 128) || (num_filled_bufs(chan) != chan->lastnumbufs)) { + printk("amnt: %d Number of filled buffers: %d\n", amnt, num_filled_bufs(chan)); + chan->statcount = 32000; + chan->lastnumbufs = num_filled_bufs(chan); + } +#endif + + spin_unlock_irqrestore(&chan->lock, flags); + + if (chan->flags & DAHDI_FLAG_NOSTDTXRX && chan->span->hdlc_hard_xmit) + chan->span->hdlc_hard_xmit(chan); + } + return amnt; +} + +static int dahdi_ctl_open(struct inode *inode, struct file *file) +{ + /* Nothing to do, really */ + return 0; +} + +static int dahdi_chan_open(struct inode *inode, struct file *file) +{ + /* Nothing to do here for now either */ + return 0; +} + +static int dahdi_ctl_release(struct inode *inode, struct file *file) +{ + /* Nothing to do */ + return 0; +} + +static int dahdi_chan_release(struct inode *inode, struct file *file) +{ + /* Nothing to do for now */ + return 0; +} + +static void set_txtone(struct dahdi_chan *ss, int fac, int init_v2, int init_v3) +{ + if (fac == 0) { + ss->v2_1 = 0; + ss->v3_1 = 0; + return; + } + ss->txtone = fac; + ss->v1_1 = 0; + ss->v2_1 = init_v2; + ss->v3_1 = init_v3; + return; +} + +static void dahdi_rbs_sethook(struct dahdi_chan *chan, int txsig, int txstate, + int timeout) +{ + static const struct { + unsigned int sig_type; + /* Index is dahdi_txsig enum */ + unsigned int bits[DAHDI_TXSIG_TOTAL]; + } outs[NUM_SIGS] = { + { + /* + * We set the idle case of the DAHDI_SIG_NONE to this pattern to make idle E1 CAS + * channels happy. Should not matter with T1, since on an un-configured channel, + * who cares what the sig bits are as long as they are stable + */ + .sig_type = DAHDI_SIG_NONE, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_ACD, + }, { + .sig_type = DAHDI_SIG_EM, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_ABCD, + .bits[DAHDI_TXSIG_START] = DAHDI_BITS_ABCD, + }, { + .sig_type = DAHDI_SIG_FXSLS, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_BD, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_ABCD, + .bits[DAHDI_TXSIG_START] = DAHDI_BITS_ABCD, + }, { + .sig_type = DAHDI_SIG_FXSGS, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_BD, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_ABCD, +#ifndef CONFIG_CAC_GROUNDSTART + .bits[DAHDI_TXSIG_START] = DAHDI_BITS_AC, +#endif + }, { + .sig_type = DAHDI_SIG_FXSKS, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_BD, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_ABCD, + .bits[DAHDI_TXSIG_START] = DAHDI_BITS_ABCD, + }, { + .sig_type = DAHDI_SIG_FXOLS, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_BD, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_BD, + }, { + .sig_type = DAHDI_SIG_FXOGS, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_ABCD, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_BD, + }, { + .sig_type = DAHDI_SIG_FXOKS, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_BD, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_BD, + .bits[DAHDI_TXSIG_KEWL] = DAHDI_BITS_ABCD, + }, { + .sig_type = DAHDI_SIG_SF, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_BITS_BCD, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_ABCD, + .bits[DAHDI_TXSIG_START] = DAHDI_BITS_ABCD, + .bits[DAHDI_TXSIG_KEWL] = DAHDI_BITS_BCD, + }, { + .sig_type = DAHDI_SIG_EM_E1, + .bits[DAHDI_TXSIG_ONHOOK] = DAHDI_DBIT, + .bits[DAHDI_TXSIG_OFFHOOK] = DAHDI_BITS_ABD, + .bits[DAHDI_TXSIG_START] = DAHDI_BITS_ABD, + .bits[DAHDI_TXSIG_KEWL] = DAHDI_DBIT, + } + }; + int x; + + /* if no span, return doing nothing */ + if (!chan->span) + return; + + if (!chan->span->flags & DAHDI_FLAG_RBS) { + module_printk(KERN_NOTICE, "dahdi_rbs: Tried to set RBS hook state on non-RBS channel %s\n", chan->name); + return; + } + if ((txsig > 3) || (txsig < 0)) { + module_printk(KERN_NOTICE, "dahdi_rbs: Tried to set RBS hook state %d (> 3) on channel %s\n", txsig, chan->name); + return; + } + if (!chan->span->rbsbits && !chan->span->hooksig) { + module_printk(KERN_NOTICE, "dahdi_rbs: Tried to set RBS hook state %d on channel %s while span %s lacks rbsbits or hooksig function\n", + txsig, chan->name, chan->span->name); + return; + } + /* Don't do anything for RBS */ + if (chan->sig == DAHDI_SIG_DACS_RBS) + return; + chan->txstate = txstate; + + /* if tone signalling */ + if (chan->sig == DAHDI_SIG_SF) { + chan->txhooksig = txsig; + if (chan->txtone) { /* if set to make tone for tx */ + if ((txsig && !(chan->toneflags & DAHDI_REVERSE_TXTONE)) || + ((!txsig) && (chan->toneflags & DAHDI_REVERSE_TXTONE))) { + set_txtone(chan,chan->txtone,chan->tx_v2,chan->tx_v3); + } else { + set_txtone(chan,0,0,0); + } + } + chan->otimer = timeout * DAHDI_CHUNKSIZE; /* Otimer is timer in samples */ + return; + } + if (chan->span->hooksig) { + if (chan->txhooksig != txsig) { + chan->txhooksig = txsig; + chan->span->hooksig(chan, txsig); + } + chan->otimer = timeout * DAHDI_CHUNKSIZE; /* Otimer is timer in samples */ + return; + } else { + for (x = 0; x < NUM_SIGS; x++) { + if (outs[x].sig_type == chan->sig) { +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Setting bits to %d for channel %s state %d in %d signalling\n", outs[x].bits[txsig], chan->name, txsig, chan->sig); +#endif + chan->txhooksig = txsig; + chan->txsig = outs[x].bits[txsig]; + chan->span->rbsbits(chan, chan->txsig); + chan->otimer = timeout * DAHDI_CHUNKSIZE; /* Otimer is timer in samples */ + return; + } + } + } + module_printk(KERN_NOTICE, "dahdi_rbs: Don't know RBS signalling type %d on channel %s\n", chan->sig, chan->name); +} + +static int dahdi_cas_setbits(struct dahdi_chan *chan, int bits) +{ + /* if no span, return as error */ + if (!chan->span) + return -1; + if (chan->span->rbsbits) { + chan->txsig = bits; + chan->span->rbsbits(chan, bits); + } else { + module_printk(KERN_NOTICE, "Huh? CAS setbits, but no RBS bits function\n"); + } + + return 0; +} + +static int dahdi_hangup(struct dahdi_chan *chan) +{ + int x, res = 0; + + /* Can't hangup pseudo channels */ + if (!chan->span) + return 0; + + /* Can't hang up a clear channel */ + if (chan->flags & (DAHDI_FLAG_CLEAR | DAHDI_FLAG_NOSTDTXRX)) + return -EINVAL; + + chan->kewlonhook = 0; + + if ((chan->sig == DAHDI_SIG_FXSLS) || (chan->sig == DAHDI_SIG_FXSKS) || + (chan->sig == DAHDI_SIG_FXSGS)) { + chan->ringdebtimer = RING_DEBOUNCE_TIME; + } + + if (chan->span->flags & DAHDI_FLAG_RBS) { + if (chan->sig == DAHDI_SIG_CAS) { + dahdi_cas_setbits(chan, chan->idlebits); + } else if ((chan->sig == DAHDI_SIG_FXOKS) && (chan->txstate != DAHDI_TXSTATE_ONHOOK) + /* if other party is already on-hook we shouldn't do any battery drop */ + && !((chan->rxhooksig == DAHDI_RXSIG_ONHOOK) && (chan->itimer <= 0))) { + /* Do RBS signalling on the channel's behalf */ + dahdi_rbs_sethook(chan, DAHDI_TXSIG_KEWL, DAHDI_TXSTATE_KEWL, DAHDI_KEWLTIME); + } else + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_ONHOOK, 0); + } else { + /* Let the driver hang up the line if it wants to */ + if (chan->span->sethook) { + if (chan->txhooksig != DAHDI_ONHOOK) { + chan->txhooksig = DAHDI_ONHOOK; + res = chan->span->sethook(chan, DAHDI_ONHOOK); + } else + res = 0; + } + } + + /* if not registered yet, just return here */ + if (!test_bit(DAHDI_FLAGBIT_REGISTERED, &chan->flags)) + return res; + + /* Mark all buffers as empty */ + for (x = 0; x < chan->numbufs; x++) { + chan->writen[x] = + chan->writeidx[x]= + chan->readn[x]= + chan->readidx[x] = 0; + } + + if (chan->readbuf[0]) { + chan->inreadbuf = 0; + chan->inwritebuf = 0; + } else { + chan->inreadbuf = -1; + chan->inwritebuf = -1; + } + chan->outreadbuf = -1; + chan->outwritebuf = -1; + chan->dialing = 0; + chan->afterdialingtimer = 0; + chan->curtone = NULL; + chan->pdialcount = 0; + chan->cadencepos = 0; + chan->txdialbuf[0] = 0; + + return res; +} + +static int initialize_channel(struct dahdi_chan *chan) +{ + int res; + unsigned long flags; + void *rxgain=NULL; + struct dahdi_echocan_state *ec_state; + const struct dahdi_echocan_factory *ec_current; + + if ((res = dahdi_reallocbufs(chan, DAHDI_DEFAULT_BLOCKSIZE, DAHDI_DEFAULT_NUM_BUFS))) + return res; + + spin_lock_irqsave(&chan->lock, flags); + + chan->rxbufpolicy = DAHDI_POLICY_IMMEDIATE; + chan->txbufpolicy = DAHDI_POLICY_IMMEDIATE; + + ec_state = chan->ec_state; + chan->ec_state = NULL; + ec_current = chan->ec_current; + chan->ec_current = NULL; + + chan->txdisable = 0; + chan->rxdisable = 0; + + chan->digitmode = DIGIT_MODE_DTMF; + chan->dialing = 0; + chan->afterdialingtimer = 0; + + chan->cadencepos = 0; + chan->firstcadencepos = 0; /* By default loop back to first cadence position */ + + /* HDLC & FCS stuff */ + fasthdlc_init(&chan->rxhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + fasthdlc_init(&chan->txhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + chan->infcs = PPP_INITFCS; + + /* Timings for RBS */ + chan->prewinktime = DAHDI_DEFAULT_PREWINKTIME; + chan->preflashtime = DAHDI_DEFAULT_PREFLASHTIME; + chan->winktime = DAHDI_DEFAULT_WINKTIME; + chan->flashtime = DAHDI_DEFAULT_FLASHTIME; + + if (chan->sig & __DAHDI_SIG_FXO) + chan->starttime = DAHDI_DEFAULT_RINGTIME; + else + chan->starttime = DAHDI_DEFAULT_STARTTIME; + chan->rxwinktime = DAHDI_DEFAULT_RXWINKTIME; + chan->rxflashtime = DAHDI_DEFAULT_RXFLASHTIME; + chan->debouncetime = DAHDI_DEFAULT_DEBOUNCETIME; + chan->pulsemaketime = DAHDI_DEFAULT_PULSEMAKETIME; + chan->pulsebreaktime = DAHDI_DEFAULT_PULSEBREAKTIME; + chan->pulseaftertime = DAHDI_DEFAULT_PULSEAFTERTIME; + + /* Initialize RBS timers */ + chan->itimerset = chan->itimer = chan->otimer = 0; + chan->ringdebtimer = 0; + + init_waitqueue_head(&chan->sel); + init_waitqueue_head(&chan->readbufq); + init_waitqueue_head(&chan->writebufq); + init_waitqueue_head(&chan->eventbufq); + init_waitqueue_head(&chan->txstateq); + + /* Reset conferences */ + reset_conf(chan); + + /* I/O Mask, etc */ + chan->iomask = 0; + /* release conference resource if any */ + if (chan->confna) + dahdi_check_conf(chan->confna); + if ((chan->sig & __DAHDI_SIG_DACS) != __DAHDI_SIG_DACS) { + chan->confna = 0; + chan->confmode = 0; + if (chan->span && chan->span->dacs) + chan->span->dacs(chan, NULL); + } + chan->_confn = 0; + memset(chan->conflast, 0, sizeof(chan->conflast)); + memset(chan->conflast1, 0, sizeof(chan->conflast1)); + memset(chan->conflast2, 0, sizeof(chan->conflast2)); + chan->confmute = 0; + chan->gotgs = 0; + chan->curtone = NULL; + chan->tonep = 0; + chan->pdialcount = 0; + if (chan->gainalloc && chan->rxgain) + rxgain = chan->rxgain; + chan->rxgain = defgain; + chan->txgain = defgain; + chan->gainalloc = 0; + chan->eventinidx = chan->eventoutidx = 0; + dahdi_set_law(chan,0); + dahdi_hangup(chan); + + /* Make sure that the audio flag is cleared on a clear channel */ + if ((chan->sig & DAHDI_SIG_CLEAR) || (chan->sig & DAHDI_SIG_HARDHDLC)) + chan->flags &= ~DAHDI_FLAG_AUDIO; + + if ((chan->sig == DAHDI_SIG_CLEAR) || (chan->sig == DAHDI_SIG_HARDHDLC)) + chan->flags &= ~(DAHDI_FLAG_PPP | DAHDI_FLAG_FCS | DAHDI_FLAG_HDLC); + + chan->flags &= ~DAHDI_FLAG_LINEAR; + if (chan->curzone) { + /* Take cadence from tone zone */ + memcpy(chan->ringcadence, chan->curzone->ringcadence, sizeof(chan->ringcadence)); + } else { + /* Do a default */ + memset(chan->ringcadence, 0, sizeof(chan->ringcadence)); + chan->ringcadence[0] = chan->starttime; + chan->ringcadence[1] = DAHDI_RINGOFFTIME; + } + + if (ec_state) { + ec_state->ops->echocan_free(chan, ec_state); + release_echocan(ec_current); + } + + spin_unlock_irqrestore(&chan->lock, flags); + + set_tone_zone(chan, -1); + + if (rxgain) + kfree(rxgain); + + return 0; +} + +static int dahdi_timing_open(struct inode *inode, struct file *file) +{ + struct dahdi_timer *t; + unsigned long flags; + + if (!(t = kzalloc(sizeof(*t), GFP_KERNEL))) + return -ENOMEM; + + init_waitqueue_head(&t->sel); + INIT_LIST_HEAD(&t->list); + file->private_data = t; + + spin_lock_irqsave(&zaptimerlock, flags); + list_add(&t->list, &zaptimers); + spin_unlock_irqrestore(&zaptimerlock, flags); + + return 0; +} + +static int dahdi_timer_release(struct inode *inode, struct file *file) +{ + struct dahdi_timer *t, *cur, *next; + unsigned long flags; + + if (!(t = file->private_data)) + return 0; + + spin_lock_irqsave(&zaptimerlock, flags); + + list_for_each_entry_safe(cur, next, &zaptimers, list) { + if (t == cur) { + list_del(&cur->list); + break; + } + } + + spin_unlock_irqrestore(&zaptimerlock, flags); + + if (!cur) { + module_printk(KERN_NOTICE, "Timer: Not on list??\n"); + return 0; + } + + kfree(cur); + + return 0; +} + +static int dahdi_specchan_open(struct inode *inode, struct file *file, int unit) +{ + int res = 0; + + if (chans[unit] && chans[unit]->sig) { + /* Make sure we're not already open, a net device, or a slave device */ + if (chans[unit]->flags & DAHDI_FLAG_NETDEV) + res = -EBUSY; + else if (chans[unit]->master != chans[unit]) + res = -EBUSY; + else if ((chans[unit]->sig & __DAHDI_SIG_DACS) == __DAHDI_SIG_DACS) + res = -EBUSY; + else if (!test_and_set_bit(DAHDI_FLAGBIT_OPEN, &chans[unit]->flags)) { + unsigned long flags; + res = initialize_channel(chans[unit]); + if (res) { + /* Reallocbufs must have failed */ + clear_bit(DAHDI_FLAGBIT_OPEN, &chans[unit]->flags); + return res; + } + spin_lock_irqsave(&chans[unit]->lock, flags); + if (chans[unit]->flags & DAHDI_FLAG_PSEUDO) + chans[unit]->flags |= DAHDI_FLAG_AUDIO; + if (chans[unit]->span && chans[unit]->span->open) { + res = chans[unit]->span->open(chans[unit]); + } + if (!res) { + chans[unit]->file = file; + spin_unlock_irqrestore(&chans[unit]->lock, flags); + } else { + spin_unlock_irqrestore(&chans[unit]->lock, flags); + close_channel(chans[unit]); + clear_bit(DAHDI_FLAGBIT_OPEN, &chans[unit]->flags); + } + } else + res = -EBUSY; + } else + res = -ENXIO; + return res; +} + +static int dahdi_specchan_release(struct inode *node, struct file *file, int unit) +{ + int res=0; + unsigned long flags; + + if (chans[unit]) { + /* Chan lock protects contents against potentially non atomic accesses. + * So if the pointer setting is not atomic, we should protect */ + spin_lock_irqsave(&chans[unit]->lock, flags); + chans[unit]->file = NULL; + spin_unlock_irqrestore(&chans[unit]->lock, flags); + close_channel(chans[unit]); + if (chans[unit]->span && chans[unit]->span->close) + res = chans[unit]->span->close(chans[unit]); + /* The channel might be destroyed by low-level driver span->close() */ + if(chans[unit]) + clear_bit(DAHDI_FLAGBIT_OPEN, &chans[unit]->flags); + } else + res = -ENXIO; + return res; +} + +static int can_open_timer(void) +{ +#ifdef CONFIG_DAHDI_CORE_TIMER + return 1; +#else + return maxspans > 0; +#endif +} + +static struct dahdi_chan *dahdi_alloc_pseudo(void) +{ + struct dahdi_chan *pseudo; + + /* Don't allow /dev/dahdi/pseudo to open if there is not a timing + * source. */ + if (!can_open_timer()) + return NULL; + + if (!(pseudo = kzalloc(sizeof(*pseudo), GFP_KERNEL))) + return NULL; + + pseudo->sig = DAHDI_SIG_CLEAR; + pseudo->sigcap = DAHDI_SIG_CLEAR; + pseudo->flags = DAHDI_FLAG_PSEUDO | DAHDI_FLAG_AUDIO; + + if (dahdi_chan_reg(pseudo)) { + kfree(pseudo); + pseudo = NULL; + } else { + snprintf(pseudo->name, sizeof(pseudo->name)-1,"Pseudo/%d", pseudo->channo); + } + + return pseudo; +} + +static void dahdi_free_pseudo(struct dahdi_chan *pseudo) +{ + if (pseudo) { + dahdi_chan_unreg(pseudo); + kfree(pseudo); + } +} + +static int dahdi_open(struct inode *inode, struct file *file) +{ + int unit = UNIT(file); + struct dahdi_chan *chan; + /* Minor 0: Special "control" descriptor */ + if (!unit) + return dahdi_ctl_open(inode, file); + if (unit == 250) { + if (!dahdi_transcode_fops) { + if (request_module("dahdi_transcode")) { + return -ENXIO; + } + } +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) + __MOD_INC_USE_COUNT (dahdi_transcode_fops->owner); +#else + if (!try_module_get(dahdi_transcode_fops->owner)) { + return -ENXIO; + } +#endif + if (dahdi_transcode_fops && dahdi_transcode_fops->open) { + return dahdi_transcode_fops->open(inode, file); + } else { + /* dahdi_transcode module should have exported a + * file_operations table. */ + WARN_ON(1); + } + return -ENXIO; + } + if (unit == 253) { + if (can_open_timer()) { + return dahdi_timing_open(inode, file); + } else { + return -ENXIO; + } + } + if (unit == 254) + return dahdi_chan_open(inode, file); + if (unit == 255) { + chan = dahdi_alloc_pseudo(); + if (chan) { + file->private_data = chan; + return dahdi_specchan_open(inode, file, chan->channo); + } else { + return -ENXIO; + } + } + return dahdi_specchan_open(inode, file, unit); +} + +#if 0 +static int dahdi_open(struct inode *inode, struct file *file) +{ + int res; + unsigned long flags; + spin_lock_irqsave(&bigzaplock, flags); + res = __dahdi_open(inode, file); + spin_unlock_irqrestore(&bigzaplock, flags); + return res; +} +#endif + +static ssize_t dahdi_read(struct file *file, char *usrbuf, size_t count, loff_t *ppos) +{ + int unit = UNIT(file); + struct dahdi_chan *chan; + + /* Can't read from control */ + if (!unit) { + return -EINVAL; + } + + if (unit == 253) + return -EINVAL; + + if (unit == 254) { + chan = file->private_data; + if (!chan) + return -EINVAL; + return dahdi_chan_read(file, usrbuf, count, chan->channo); + } + + if (unit == 255) { + chan = file->private_data; + if (!chan) { + module_printk(KERN_NOTICE, "No pseudo channel structure to read?\n"); + return -EINVAL; + } + return dahdi_chan_read(file, usrbuf, count, chan->channo); + } + if (count < 0) + return -EINVAL; + + return dahdi_chan_read(file, usrbuf, count, unit); +} + +static ssize_t dahdi_write(struct file *file, const char *usrbuf, size_t count, loff_t *ppos) +{ + int unit = UNIT(file); + struct dahdi_chan *chan; + /* Can't read from control */ + if (!unit) + return -EINVAL; + if (count < 0) + return -EINVAL; + if (unit == 253) + return -EINVAL; + if (unit == 254) { + chan = file->private_data; + if (!chan) + return -EINVAL; + return dahdi_chan_write(file, usrbuf, count, chan->channo); + } + if (unit == 255) { + chan = file->private_data; + if (!chan) { + module_printk(KERN_NOTICE, "No pseudo channel structure to read?\n"); + return -EINVAL; + } + return dahdi_chan_write(file, usrbuf, count, chan->channo); + } + return dahdi_chan_write(file, usrbuf, count, unit); + +} + +static int dahdi_set_default_zone(int defzone) +{ + if ((defzone < 0) || (defzone >= DAHDI_TONE_ZONE_MAX)) + return -EINVAL; + write_lock(&zone_lock); + if (!tone_zones[defzone]) { + write_unlock(&zone_lock); + return -EINVAL; + } + if ((default_zone != -1) && tone_zones[default_zone]) + atomic_dec(&tone_zones[default_zone]->refcount); + atomic_inc(&tone_zones[defzone]->refcount); + default_zone = defzone; + write_unlock(&zone_lock); + return 0; +} + +/* No bigger than 32k for everything per tone zone */ +#define MAX_SIZE 32768 +/* 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 dahdi_tone *samples[MAX_TONES] = { NULL, }; + short next[MAX_TONES] = { 0, }; + struct dahdi_tone_def_header th; + struct dahdi_tone_def td; + struct dahdi_zone *z; + struct dahdi_tone *t; + void *slab, *ptr; + int x; + size_t space; + size_t size; + int res; + + if (copy_from_user(&th, (struct dahdi_tone_def_header *) data, sizeof(th))) + return -EFAULT; + + data += sizeof(th); + + if ((th.count < 0) || (th.count > MAX_TONES)) { + module_printk(KERN_NOTICE, "Too many tones included\n"); + return -EINVAL; + } + + space = size = sizeof(*z) + th.count * sizeof(*t); + + if (size > MAX_SIZE) + return -E2BIG; + + if (!(z = ptr = slab = kzalloc(size, GFP_KERNEL))) + return -ENOMEM; + + ptr += sizeof(*z); + space -= sizeof(*z); + + dahdi_copy_string(z->name, th.name, sizeof(z->name)); + + for (x = 0; x < DAHDI_MAX_CADENCE; x++) + z->ringcadence[x] = th.ringcadence[x]; + + atomic_set(&z->refcount, 0); + + for (x = 0; x < th.count; x++) { + enum { + REGULAR_TONE, + DTMF_TONE, + MFR1_TONE, + MFR2_FWD_TONE, + MFR2_REV_TONE, + } tone_type; + + if (space < sizeof(*t)) { + kfree(slab); + module_printk(KERN_NOTICE, "Insufficient tone zone space\n"); + return -EINVAL; + } + + if (copy_from_user(&td, (struct dahdi_tone_def *) data, sizeof(td))) { + kfree(slab); + return -EFAULT; + } + + data += sizeof(td); + + if ((td.tone >= 0) && (td.tone < DAHDI_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)) { + module_printk(KERN_NOTICE, "Invalid 'next' pointer: %d\n", next[x]); + kfree(slab); + return -EINVAL; + } + } else if ((td.tone >= DAHDI_TONE_DTMF_BASE) && + (td.tone <= DAHDI_TONE_DTMF_MAX)) { + tone_type = DTMF_TONE; + td.tone -= DAHDI_TONE_DTMF_BASE; + t = &z->dtmf[td.tone]; + } else if ((td.tone >= DAHDI_TONE_MFR1_BASE) && + (td.tone <= DAHDI_TONE_MFR1_MAX)) { + tone_type = MFR1_TONE; + td.tone -= DAHDI_TONE_MFR1_BASE; + t = &z->mfr1[td.tone]; + } else if ((td.tone >= DAHDI_TONE_MFR2_FWD_BASE) && + (td.tone <= DAHDI_TONE_MFR2_FWD_MAX)) { + tone_type = MFR2_FWD_TONE; + td.tone -= DAHDI_TONE_MFR2_FWD_BASE; + t = &z->mfr2_fwd[td.tone]; + } else if ((td.tone >= DAHDI_TONE_MFR2_REV_BASE) && + (td.tone <= DAHDI_TONE_MFR2_REV_MAX)) { + tone_type = MFR2_REV_TONE; + td.tone -= DAHDI_TONE_MFR2_REV_BASE; + t = &z->mfr2_rev[td.tone]; + } else { + module_printk(KERN_NOTICE, "Invalid tone (%d) defined\n", td.tone); + kfree(slab); + return -EINVAL; + } + + t->fac1 = td.fac1; + t->init_v2_1 = td.init_v2_1; + t->init_v3_1 = td.init_v3_1; + t->fac2 = td.fac2; + t->init_v2_2 = td.init_v2_2; + t->init_v3_2 = td.init_v3_2; + t->modulate = td.modulate; + + 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 MFR1_TONE: + switch (td.tone + DAHDI_TONE_MFR1_BASE) { + case DAHDI_TONE_MFR1_KP: + case DAHDI_TONE_MFR1_ST: + case DAHDI_TONE_MFR1_STP: + case DAHDI_TONE_MFR1_ST2P: + case DAHDI_TONE_MFR1_ST3P: + /* signaling control tones are always 100ms */ + t->tonesamples = 100 * DAHDI_CHUNKSIZE; + break; + default: + t->tonesamples = global_dialparams.mfv1_tonelen; + break; + } + t->next = &mfr1_silence; + break; + case MFR2_FWD_TONE: + t->tonesamples = global_dialparams.mfr2_tonelen; + t->next = &dtmf_silence; + z->mfr2_fwd_continuous[td.tone] = *t; + z->mfr2_fwd_continuous[td.tone].next = &z->mfr2_fwd_continuous[td.tone]; + break; + case MFR2_REV_TONE: + t->tonesamples = global_dialparams.mfr2_tonelen; + t->next = &dtmf_silence; + z->mfr2_rev_continuous[td.tone] = *t; + z->mfr2_rev_continuous[td.tone].next = &z->mfr2_rev_continuous[td.tone]; + break; + } + } + + for (x = 0; x < th.count; x++) { + if (samples[x]) + samples[x]->next = samples[next[x]]; + } + + if ((res = dahdi_register_tone_zone(th.zone, z))) { + kfree(slab); + } else { + if ( -1 == default_zone ) { + dahdi_set_default_zone(th.zone); + } + } + + return res; +} + +void dahdi_init_tone_state(struct dahdi_tone_state *ts, struct dahdi_tone *zt) +{ + ts->v1_1 = 0; + ts->v2_1 = zt->init_v2_1; + ts->v3_1 = zt->init_v3_1; + ts->v1_2 = 0; + ts->v2_2 = zt->init_v2_2; + ts->v3_2 = zt->init_v3_2; + ts->modulate = zt->modulate; +} + +struct dahdi_tone *dahdi_mf_tone(const struct dahdi_chan *chan, char digit, int digitmode) +{ + unsigned int tone_index; + + if (!chan->curzone) { + static int __warnonce = 1; + if (__warnonce) { + __warnonce = 0; + /* The tonezones are loaded by dahdi_cfg based on /etc/dahdi/system.conf. */ + module_printk(KERN_WARNING, "Cannot get dtmf tone until tone zone is loaded.\n"); + } + return NULL; + } + + switch (digitmode) { + case DIGIT_MODE_PULSE: + /* We should only get here with a pulse digit if we need + * to "dial" 'W' (wait 0.5 second) + */ + if (digit == 'W') + return &tone_pause; + + return NULL; + /* You should not get here */ + case DIGIT_MODE_DTMF: + switch (digit) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + tone_index = DAHDI_TONE_DTMF_0 + (digit - '0'); + break; + case '*': + tone_index = DAHDI_TONE_DTMF_s; + break; + case '#': + tone_index = DAHDI_TONE_DTMF_p; + break; + case 'A': + case 'B': + case 'C': + case 'D': + tone_index = DAHDI_TONE_DTMF_A + (digit - 'A'); + case 'W': + return &tone_pause; + default: + return NULL; + } + return &chan->curzone->dtmf[tone_index - DAHDI_TONE_DTMF_BASE]; + case DIGIT_MODE_MFR1: + switch (digit) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + tone_index = DAHDI_TONE_MFR1_0 + (digit - '0'); + break; + case '*': + tone_index = DAHDI_TONE_MFR1_KP; + break; + case '#': + tone_index = DAHDI_TONE_MFR1_ST; + break; + case 'A': + tone_index = DAHDI_TONE_MFR1_STP; + break; + case 'B': + tone_index = DAHDI_TONE_MFR1_ST2P; + break; + case 'C': + tone_index = DAHDI_TONE_MFR1_ST3P; + break; + case 'W': + return &tone_pause; + default: + return NULL; + } + return &chan->curzone->mfr1[tone_index - DAHDI_TONE_MFR1_BASE]; + case DIGIT_MODE_MFR2_FWD: + switch (digit) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + tone_index = DAHDI_TONE_MFR2_FWD_1 + (digit - '1'); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + tone_index = DAHDI_TONE_MFR2_FWD_10 + (digit - 'A'); + break; + case 'W': + return &tone_pause; + default: + return NULL; + } + return &chan->curzone->mfr2_fwd[tone_index - DAHDI_TONE_MFR2_FWD_BASE]; + case DIGIT_MODE_MFR2_REV: + switch (digit) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + tone_index = DAHDI_TONE_MFR2_REV_1 + (digit - '1'); + break; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + tone_index = DAHDI_TONE_MFR2_REV_10 + (digit - 'A'); + break; + case 'W': + return &tone_pause; + default: + return NULL; + } + return &chan->curzone->mfr2_rev[tone_index - DAHDI_TONE_MFR2_REV_BASE]; + default: + return NULL; + } +} + +static void __do_dtmf(struct dahdi_chan *chan) +{ + char c; + + /* Called with chan->lock held */ + while ((c = chan->txdialbuf[0])) { + memmove(chan->txdialbuf, chan->txdialbuf + 1, sizeof(chan->txdialbuf) - 1); + switch (c) { + case 'T': + chan->digitmode = DIGIT_MODE_DTMF; + chan->tonep = 0; + break; + case 'M': + chan->digitmode = DIGIT_MODE_MFR1; + chan->tonep = 0; + break; + case 'O': + chan->digitmode = DIGIT_MODE_MFR2_FWD; + chan->tonep = 0; + break; + case 'R': + chan->digitmode = DIGIT_MODE_MFR2_REV; + chan->tonep = 0; + break; + case 'P': + chan->digitmode = DIGIT_MODE_PULSE; + chan->tonep = 0; + break; + default: + if ((c != 'W') && (chan->digitmode == DIGIT_MODE_PULSE)) { + if ((c >= '0') && (c <= '9') && (chan->txhooksig == DAHDI_TXSIG_OFFHOOK)) { + chan->pdialcount = (c == '0') ? 10 : c - '0'; + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_PULSEBREAK, + chan->pulsebreaktime); + return; + } + } else { + chan->curtone = dahdi_mf_tone(chan, c, chan->digitmode); + chan->tonep = 0; + if (chan->curtone) { + dahdi_init_tone_state(&chan->ts, chan->curtone); + return; + } + } + } + } + + /* Notify userspace process if there is nothing left */ + chan->dialing = 0; + __qevent(chan, DAHDI_EVENT_DIALCOMPLETE); +} + +static int dahdi_release(struct inode *inode, struct file *file) +{ + int unit = UNIT(file); + int res; + struct dahdi_chan *chan; + + if (!unit) + return dahdi_ctl_release(inode, file); + if (unit == 253) { + return dahdi_timer_release(inode, file); + } + if (unit == 250) { + /* We should not be here because the dahdi_transcode.ko module + * should have updated the file_operations for this file + * handle when the file was opened. */ + WARN_ON(1); + return -EFAULT; + } + if (unit == 254) { + chan = file->private_data; + if (!chan) + return dahdi_chan_release(inode, file); + else + return dahdi_specchan_release(inode, file, chan->channo); + } + if (unit == 255) { + chan = file->private_data; + if (chan) { + res = dahdi_specchan_release(inode, file, chan->channo); + dahdi_free_pseudo(chan); + } else { + module_printk(KERN_NOTICE, "Pseudo release and no private data??\n"); + res = 0; + } + return res; + } + return dahdi_specchan_release(inode, file, unit); +} + +#if 0 +static int dahdi_release(struct inode *inode, struct file *file) +{ + /* Lock the big zap lock when handling a release */ + unsigned long flags; + int res; + spin_lock_irqsave(&bigzaplock, flags); + res = __dahdi_release(inode, file); + spin_unlock_irqrestore(&bigzaplock, flags); + return res; +} +#endif + + +void dahdi_alarm_channel(struct dahdi_chan *chan, int alarms) +{ + unsigned long flags; + + spin_lock_irqsave(&chan->lock, flags); + if (chan->chan_alarms != alarms) { + chan->chan_alarms = alarms; + dahdi_qevent_nolock(chan, alarms ? DAHDI_EVENT_ALARM : DAHDI_EVENT_NOALARM); + } + spin_unlock_irqrestore(&chan->lock, flags); +} + +void dahdi_alarm_notify(struct dahdi_span *span) +{ + int x; + + span->alarms &= ~DAHDI_ALARM_LOOPBACK; + /* Determine maint status */ + if (span->maintstat || span->mainttimer) + span->alarms |= DAHDI_ALARM_LOOPBACK; + /* DON'T CHANGE THIS AGAIN. THIS WAS DONE FOR A REASON. + The expression (a != b) does *NOT* do the same thing + as ((!a) != (!b)) */ + /* if change in general state */ + if ((!span->alarms) != (!span->lastalarms)) { + span->lastalarms = span->alarms; + for (x = 0; x < span->channels; x++) + dahdi_alarm_channel(span->chans[x], span->alarms); + /* Switch to other master if current master in alarm */ + for (x=1; xalarms && (spans[x]->flags & DAHDI_FLAG_RUNNING)) { + if(master != spans[x]) + module_printk(KERN_NOTICE, "Master changed to %s\n", spans[x]->name); + master = spans[x]; + break; + } + } + } +} + +#define VALID_SPAN(j) do { \ + if ((j >= DAHDI_MAX_SPANS) || (j < 1)) \ + return -EINVAL; \ + if (!spans[j]) \ + return -ENXIO; \ +} while(0) + +#define CHECK_VALID_SPAN(j) do { \ + /* Start a given span */ \ + if (get_user(j, (int *)data)) \ + return -EFAULT; \ + VALID_SPAN(j); \ +} while(0) + +#define VALID_CHANNEL(j) do { \ + if ((j >= DAHDI_MAX_CHANNELS) || (j < 1)) \ + return -EINVAL; \ + if (!chans[j]) \ + return -ENXIO; \ +} while(0) + +static int dahdi_timer_ioctl(struct inode *node, struct file *file, unsigned int cmd, unsigned long data, struct dahdi_timer *timer) +{ + int j; + unsigned long flags; + switch(cmd) { + case DAHDI_TIMERCONFIG: + get_user(j, (int *)data); + if (j < 0) + j = 0; + spin_lock_irqsave(&zaptimerlock, flags); + timer->ms = timer->pos = j; + spin_unlock_irqrestore(&zaptimerlock, flags); + break; + case DAHDI_TIMERACK: + get_user(j, (int *)data); + spin_lock_irqsave(&zaptimerlock, flags); + if ((j < 1) || (j > timer->tripped)) + j = timer->tripped; + timer->tripped -= j; + spin_unlock_irqrestore(&zaptimerlock, flags); + break; + case DAHDI_GETEVENT: /* Get event on queue */ + j = DAHDI_EVENT_NONE; + spin_lock_irqsave(&zaptimerlock, flags); + /* set up for no event */ + if (timer->tripped) + j = DAHDI_EVENT_TIMER_EXPIRED; + if (timer->ping) + j = DAHDI_EVENT_TIMER_PING; + spin_unlock_irqrestore(&zaptimerlock, flags); + put_user(j,(int *)data); + break; + case DAHDI_TIMERPING: + spin_lock_irqsave(&zaptimerlock, flags); + timer->ping = 1; + wake_up_interruptible(&timer->sel); + spin_unlock_irqrestore(&zaptimerlock, flags); + break; + case DAHDI_TIMERPONG: + spin_lock_irqsave(&zaptimerlock, flags); + timer->ping = 0; + spin_unlock_irqrestore(&zaptimerlock, flags); + break; + default: + return -ENOTTY; + } + return 0; +} + +static int dahdi_common_ioctl(struct inode *node, struct file *file, unsigned int cmd, unsigned long data, int unit) +{ + union { + struct dahdi_gains gain; + struct dahdi_spaninfo spaninfo; + struct dahdi_params param; + } stack; + struct dahdi_chan *chan; + unsigned long flags; + unsigned char *txgain, *rxgain; + int i,j; + int return_master = 0; + size_t size_to_copy; + + switch(cmd) { + /* get channel parameters */ + case DAHDI_GET_PARAMS_V1: /* Intentional drop through. */ + case DAHDI_GET_PARAMS: + size_to_copy = sizeof(struct dahdi_params); + if (copy_from_user(&stack.param, (struct dahdi_params *) data, size_to_copy)) + return -EFAULT; + + /* check to see if the caller wants to receive our master channel number */ + if (stack.param.channo & DAHDI_GET_PARAMS_RETURN_MASTER) { + return_master = 1; + stack.param.channo &= ~DAHDI_GET_PARAMS_RETURN_MASTER; + } + + /* Pick the right channo's */ + if (!stack.param.channo || unit) { + stack.param.channo = unit; + } + /* Check validity of channel */ + VALID_CHANNEL(stack.param.channo); + chan = chans[stack.param.channo]; + + /* point to relevant structure */ + stack.param.sigtype = chan->sig; /* get signalling type */ + /* return non-zero if rx not in idle state */ + if (chan->span) { + j = dahdi_q_sig(chan); + if (j >= 0) { /* if returned with success */ + stack.param.rxisoffhook = ((chan->rxsig & (j >> 8)) != (j & 0xff)); + } else { + stack.param.rxisoffhook = ((chan->rxhooksig != DAHDI_RXSIG_ONHOOK) && + (chan->rxhooksig != DAHDI_RXSIG_INITIAL)); + } + } else if ((chan->txstate == DAHDI_TXSTATE_KEWL) || (chan->txstate == DAHDI_TXSTATE_AFTERKEWL)) + stack.param.rxisoffhook = 1; + else + stack.param.rxisoffhook = 0; + if (chan->span && chan->span->rbsbits && !(chan->sig & DAHDI_SIG_CLEAR)) { + stack.param.rxbits = chan->rxsig; + stack.param.txbits = chan->txsig; + stack.param.idlebits = chan->idlebits; + } else { + stack.param.rxbits = -1; + stack.param.txbits = -1; + stack.param.idlebits = 0; + } + if (chan->span && (chan->span->rbsbits || chan->span->hooksig) && + !(chan->sig & DAHDI_SIG_CLEAR)) { + stack.param.rxhooksig = chan->rxhooksig; + stack.param.txhooksig = chan->txhooksig; + } else { + stack.param.rxhooksig = -1; + stack.param.txhooksig = -1; + } + stack.param.prewinktime = chan->prewinktime; + stack.param.preflashtime = chan->preflashtime; + stack.param.winktime = chan->winktime; + stack.param.flashtime = chan->flashtime; + stack.param.starttime = chan->starttime; + stack.param.rxwinktime = chan->rxwinktime; + stack.param.rxflashtime = chan->rxflashtime; + stack.param.debouncetime = chan->debouncetime; + stack.param.channo = chan->channo; + stack.param.chan_alarms = chan->chan_alarms; + + /* if requested, put the master channel number in the top 16 bits of the result */ + if (return_master) + stack.param.channo |= chan->master->channo << 16; + + stack.param.pulsemaketime = chan->pulsemaketime; + stack.param.pulsebreaktime = chan->pulsebreaktime; + stack.param.pulseaftertime = chan->pulseaftertime; + if (chan->span) stack.param.spanno = chan->span->spanno; + else stack.param.spanno = 0; + dahdi_copy_string(stack.param.name, chan->name, sizeof(stack.param.name)); + stack.param.chanpos = chan->chanpos; + stack.param.sigcap = chan->sigcap; + /* Return current law */ + if (chan->xlaw == __dahdi_alaw) + stack.param.curlaw = DAHDI_LAW_ALAW; + else + stack.param.curlaw = DAHDI_LAW_MULAW; + + if (copy_to_user((struct dahdi_params *) data, &stack.param, size_to_copy)) + return -EFAULT; + + break; + /* set channel parameters */ + case DAHDI_SET_PARAMS: + if (copy_from_user(&stack.param, (struct dahdi_params *) data, sizeof(struct dahdi_params))) + return -EFAULT; + + stack.param.chan_alarms = 0; /* be explicit about the above */ + + /* Pick the right channo's */ + if (!stack.param.channo || unit) { + stack.param.channo = unit; + } + /* Check validity of channel */ + VALID_CHANNEL(stack.param.channo); + chan = chans[stack.param.channo]; + /* point to relevant structure */ + /* NOTE: sigtype is *not* included in this */ + /* get timing stack.paramters */ + chan->prewinktime = stack.param.prewinktime; + chan->preflashtime = stack.param.preflashtime; + chan->winktime = stack.param.winktime; + chan->flashtime = stack.param.flashtime; + chan->starttime = stack.param.starttime; + /* Update ringtime if not using a tone zone */ + if (!chan->curzone) + chan->ringcadence[0] = chan->starttime; + chan->rxwinktime = stack.param.rxwinktime; + chan->rxflashtime = stack.param.rxflashtime; + chan->debouncetime = stack.param.debouncetime; + chan->pulsemaketime = stack.param.pulsemaketime; + chan->pulsebreaktime = stack.param.pulsebreaktime; + chan->pulseaftertime = stack.param.pulseaftertime; + break; + case DAHDI_GETGAINS_V1: /* Intentional drop through. */ + case DAHDI_GETGAINS: /* get gain stuff */ + if (copy_from_user(&stack.gain,(struct dahdi_gains *) data,sizeof(stack.gain))) + return -EFAULT; + i = stack.gain.chan; /* get channel no */ + /* if zero, use current channel no */ + if (!i) i = unit; + /* make sure channel number makes sense */ + if ((i < 0) || (i > DAHDI_MAX_CHANNELS) || !chans[i]) return(-EINVAL); + + if (!(chans[i]->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + stack.gain.chan = i; /* put the span # in here */ + for (j=0;j<256;j++) { + stack.gain.txgain[j] = chans[i]->txgain[j]; + stack.gain.rxgain[j] = chans[i]->rxgain[j]; + } + if (copy_to_user((struct dahdi_gains *) data,&stack.gain,sizeof(stack.gain))) + return -EFAULT; + break; + case DAHDI_SETGAINS: /* set gain stuff */ + if (copy_from_user(&stack.gain,(struct dahdi_gains *) data,sizeof(stack.gain))) + return -EFAULT; + i = stack.gain.chan; /* get channel no */ + /* if zero, use current channel no */ + if (!i) i = unit; + /* make sure channel number makes sense */ + if ((i < 0) || (i > DAHDI_MAX_CHANNELS) || !chans[i]) return(-EINVAL); + if (!(chans[i]->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + + if (!(rxgain = kmalloc(512, GFP_KERNEL))) + return -ENOMEM; + + stack.gain.chan = i; /* put the span # in here */ + txgain = rxgain + 256; + + for (j=0;j<256;j++) { + rxgain[j] = stack.gain.rxgain[j]; + txgain[j] = stack.gain.txgain[j]; + } + + if (!memcmp(rxgain, defgain, 256) && + !memcmp(txgain, defgain, 256)) { + if (rxgain) + kfree(rxgain); + spin_lock_irqsave(&chans[i]->lock, flags); + if (chans[i]->gainalloc) + kfree(chans[i]->rxgain); + chans[i]->gainalloc = 0; + chans[i]->rxgain = defgain; + chans[i]->txgain = defgain; + spin_unlock_irqrestore(&chans[i]->lock, flags); + } else { + /* This is a custom gain setting */ + spin_lock_irqsave(&chans[i]->lock, flags); + if (chans[i]->gainalloc) + kfree(chans[i]->rxgain); + chans[i]->gainalloc = 1; + chans[i]->rxgain = rxgain; + chans[i]->txgain = txgain; + spin_unlock_irqrestore(&chans[i]->lock, flags); + } + if (copy_to_user((struct dahdi_gains *) data,&stack.gain,sizeof(stack.gain))) + return -EFAULT; + break; + case DAHDI_SPANSTAT: + size_to_copy = sizeof(struct dahdi_spaninfo); + if (copy_from_user(&stack.spaninfo, (struct dahdi_spaninfo *) data, size_to_copy)) + return -EFAULT; + i = stack.spaninfo.spanno; /* get specified span number */ + if ((i < 0) || (i >= maxspans)) return(-EINVAL); /* if bad span no */ + if (i == 0) { + /* if to figure it out for this chan */ + if (!chans[unit]) + return -EINVAL; + i = chans[unit]->span->spanno; + } + if (!spans[i]) + return -EINVAL; + stack.spaninfo.spanno = i; /* put the span # in here */ + stack.spaninfo.totalspans = 0; + if (maxspans) stack.spaninfo.totalspans = maxspans - 1; /* put total number of spans here */ + dahdi_copy_string(stack.spaninfo.desc, spans[i]->desc, sizeof(stack.spaninfo.desc)); + dahdi_copy_string(stack.spaninfo.name, spans[i]->name, sizeof(stack.spaninfo.name)); + stack.spaninfo.alarms = spans[i]->alarms; /* get alarm status */ + stack.spaninfo.bpvcount = spans[i]->bpvcount; /* get BPV count */ + stack.spaninfo.rxlevel = spans[i]->rxlevel; /* get rx level */ + stack.spaninfo.txlevel = spans[i]->txlevel; /* get tx level */ + stack.spaninfo.crc4count = spans[i]->crc4count; /* get CRC4 error count */ + stack.spaninfo.ebitcount = spans[i]->ebitcount; /* get E-bit error count */ + stack.spaninfo.fascount = spans[i]->fascount; /* get FAS error count */ + stack.spaninfo.irqmisses = spans[i]->irqmisses; /* get IRQ miss count */ + stack.spaninfo.syncsrc = spans[i]->syncsrc; /* get active sync source */ + stack.spaninfo.totalchans = spans[i]->channels; + stack.spaninfo.numchans = 0; + for (j = 0; j < spans[i]->channels; j++) { + if (spans[i]->chans[j]->sig) + stack.spaninfo.numchans++; + } + stack.spaninfo.lbo = spans[i]->lbo; + stack.spaninfo.lineconfig = spans[i]->lineconfig; + stack.spaninfo.irq = spans[i]->irq; + stack.spaninfo.linecompat = spans[i]->linecompat; + dahdi_copy_string(stack.spaninfo.lboname, dahdi_lboname(spans[i]->lbo), sizeof(stack.spaninfo.lboname)); + if (spans[i]->manufacturer) + dahdi_copy_string(stack.spaninfo.manufacturer, spans[i]->manufacturer, + sizeof(stack.spaninfo.manufacturer)); + if (spans[i]->devicetype) + dahdi_copy_string(stack.spaninfo.devicetype, spans[i]->devicetype, sizeof(stack.spaninfo.devicetype)); + dahdi_copy_string(stack.spaninfo.location, spans[i]->location, sizeof(stack.spaninfo.location)); + if (spans[i]->spantype) + dahdi_copy_string(stack.spaninfo.spantype, spans[i]->spantype, sizeof(stack.spaninfo.spantype)); + + if (copy_to_user((struct dahdi_spaninfo *) data, &stack.spaninfo, size_to_copy)) + return -EFAULT; + break; + case DAHDI_CHANDIAG_V1: /* Intentional drop through. */ + case DAHDI_CHANDIAG: + { + /* there really is no need to initialize this structure because when it is used it has + * already been completely overwritten, but apparently the compiler cannot figure that + * out and warns about uninitialized usage... so initialize it. + */ + struct dahdi_echocan_state ec_state = { .ops = NULL, }; + + get_user(j, (int *) data); /* get channel number from user */ + /* make sure its a valid channel number */ + if ((j < 1) || (j >= maxchans)) + return -EINVAL; + /* if channel not mapped, not there */ + if (!chans[j]) + return -EINVAL; + + chan = kmalloc(sizeof(*chan), GFP_KERNEL); + if (!chan) + return -ENOMEM; + + /* lock channel */ + spin_lock_irqsave(&chans[j]->lock, flags); + /* make static copy of channel */ + *chan = *chans[j]; + if (chan->ec_state) { + ec_state = *chan->ec_state; + } + /* release it. */ + spin_unlock_irqrestore(&chans[j]->lock, flags); + + module_printk(KERN_INFO, "Dump of DAHDI Channel %d (%s,%d,%d):\n\n",j, + chan->name, chan->channo, chan->chanpos); + module_printk(KERN_INFO, "flags: %x hex, writechunk: %p, readchunk: %p\n", + (unsigned int) chan->flags, chan->writechunk, chan->readchunk); + module_printk(KERN_INFO, "rxgain: %p, txgain: %p, gainalloc: %d\n", + chan->rxgain, chan->txgain, chan->gainalloc); + module_printk(KERN_INFO, "span: %p, sig: %x hex, sigcap: %x hex\n", + chan->span, chan->sig, chan->sigcap); + module_printk(KERN_INFO, "inreadbuf: %d, outreadbuf: %d, inwritebuf: %d, outwritebuf: %d\n", + chan->inreadbuf, chan->outreadbuf, chan->inwritebuf, chan->outwritebuf); + module_printk(KERN_INFO, "blocksize: %d, numbufs: %d, txbufpolicy: %d, txbufpolicy: %d\n", + chan->blocksize, chan->numbufs, chan->txbufpolicy, chan->rxbufpolicy); + module_printk(KERN_INFO, "txdisable: %d, rxdisable: %d, iomask: %d\n", + chan->txdisable, chan->rxdisable, chan->iomask); + module_printk(KERN_INFO, "curzone: %p, tonezone: %d, curtone: %p, tonep: %d\n", + chan->curzone, chan->tonezone, chan->curtone, chan->tonep); + module_printk(KERN_INFO, "digitmode: %d, txdialbuf: %s, dialing: %d, aftdialtimer: %d, cadpos. %d\n", + chan->digitmode, chan->txdialbuf, chan->dialing, + chan->afterdialingtimer, chan->cadencepos); + module_printk(KERN_INFO, "confna: %d, confn: %d, confmode: %d, confmute: %d\n", + chan->confna, chan->_confn, chan->confmode, chan->confmute); + module_printk(KERN_INFO, "ec: %p, deflaw: %d, xlaw: %p\n", + chan->ec_state, chan->deflaw, chan->xlaw); + if (chan->ec_state) { + module_printk(KERN_INFO, "echostate: %02x, echotimer: %d, echolastupdate: %d\n", + ec_state.status.mode, ec_state.status.pretrain_timer, ec_state.status.last_train_tap); + } + module_printk(KERN_INFO, "itimer: %d, otimer: %d, ringdebtimer: %d\n\n", + chan->itimer, chan->otimer, chan->ringdebtimer); + kfree(chan); + break; + } + default: + return -ENOTTY; + } + return 0; +} + +static int (*dahdi_dynamic_ioctl)(unsigned int cmd, unsigned long data); + +void dahdi_set_dynamic_ioctl(int (*func)(unsigned int cmd, unsigned long data)) +{ + dahdi_dynamic_ioctl = func; +} + +static int (*dahdi_hpec_ioctl)(unsigned int cmd, unsigned long data); + +void dahdi_set_hpec_ioctl(int (*func)(unsigned int cmd, unsigned long data)) +{ + dahdi_hpec_ioctl = func; +} + +static void recalc_slaves(struct dahdi_chan *chan) +{ + int x; + struct dahdi_chan *last = chan; + + /* Makes no sense if you don't have a span */ + if (!chan->span) + return; + +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Recalculating slaves on %s\n", chan->name); +#endif + + /* Link all slaves appropriately */ + for (x=chan->chanpos;xspan->channels;x++) + if (chan->span->chans[x]->master == chan) { +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Channel %s, slave to %s, last is %s, its next will be %d\n", + chan->span->chans[x]->name, chan->name, last->name, x); +#endif + last->nextslave = x; + last = chan->span->chans[x]; + } + /* Terminate list */ + last->nextslave = 0; +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Done Recalculating slaves on %s (last is %s)\n", chan->name, last->name); +#endif +} + +static int dahdi_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data) +{ + /* I/O CTL's for control interface */ + int i,j; + int sigcap; + int res = 0; + int x,y; + struct dahdi_chan *newmaster; + unsigned long flags; + int rv; + switch(cmd) { + case DAHDI_INDIRECT: + { + struct dahdi_indirect_data ind; + + if (copy_from_user(&ind, (struct dahdi_indirect_data *)data, sizeof(ind))) + return -EFAULT; + VALID_CHANNEL(ind.chan); + return dahdi_chan_ioctl(inode, file, ind.op, (unsigned long) ind.data, ind.chan); + } + case DAHDI_SPANCONFIG: + { + struct dahdi_lineconfig lc; + + if (copy_from_user(&lc, (struct dahdi_lineconfig *)data, sizeof(lc))) + return -EFAULT; + VALID_SPAN(lc.span); + if ((lc.lineconfig & 0x07f0 & spans[lc.span]->linecompat) != (lc.lineconfig & 0x07f0)) + return -EINVAL; + if (spans[lc.span]->spanconfig) { + spans[lc.span]->lineconfig = lc.lineconfig; + spans[lc.span]->lbo = lc.lbo; + spans[lc.span]->txlevel = lc.lbo; + spans[lc.span]->rxlevel = 0; + + return spans[lc.span]->spanconfig(spans[lc.span], &lc); + } + return 0; + } + case DAHDI_STARTUP: + CHECK_VALID_SPAN(j); + if (spans[j]->flags & DAHDI_FLAG_RUNNING) + return 0; + if (spans[j]->startup) + res = spans[j]->startup(spans[j]); + if (!res) { + /* Mark as running and hangup any channels */ + spans[j]->flags |= DAHDI_FLAG_RUNNING; + for (x=0;xchannels;x++) { + y = dahdi_q_sig(spans[j]->chans[x]) & 0xff; + if (y >= 0) spans[j]->chans[x]->rxsig = (unsigned char)y; + spin_lock_irqsave(&spans[j]->chans[x]->lock, flags); + dahdi_hangup(spans[j]->chans[x]); + spin_unlock_irqrestore(&spans[j]->chans[x]->lock, flags); + spans[j]->chans[x]->rxhooksig = DAHDI_RXSIG_INITIAL; + } + } + return 0; + case DAHDI_SHUTDOWN: + CHECK_VALID_SPAN(j); + if (spans[j]->shutdown) + res = spans[j]->shutdown(spans[j]); + spans[j]->flags &= ~DAHDI_FLAG_RUNNING; + return 0; + case DAHDI_ATTACH_ECHOCAN: + { + struct dahdi_attach_echocan ae; + const struct dahdi_echocan_factory *new = NULL, *old; + + if (copy_from_user(&ae, (struct dahdi_attach_echocan *) data, sizeof(ae))) { + return -EFAULT; + } + + VALID_CHANNEL(ae.chan); + + ae.echocan[sizeof(ae.echocan) - 1] = 0; + if (ae.echocan[0]) { + if (!(new = find_echocan(ae.echocan))) { + return -EINVAL; + } + } + + spin_lock_irqsave(&chans[ae.chan]->lock, flags); + old = chans[ae.chan]->ec_factory; + chans[ae.chan]->ec_factory = new; + spin_unlock_irqrestore(&chans[ae.chan]->lock, flags); + + if (old) { + release_echocan(old); + } + + break; + } + case DAHDI_CHANCONFIG: + { + struct dahdi_chanconfig ch; + + if (copy_from_user(&ch, (struct dahdi_chanconfig *)data, sizeof(ch))) + return -EFAULT; + VALID_CHANNEL(ch.chan); + if (ch.sigtype == DAHDI_SIG_SLAVE) { + /* We have to use the master's sigtype */ + if ((ch.master < 1) || (ch.master >= DAHDI_MAX_CHANNELS)) + return -EINVAL; + if (!chans[ch.master]) + return -EINVAL; + ch.sigtype = chans[ch.master]->sig; + newmaster = chans[ch.master]; + } else if ((ch.sigtype & __DAHDI_SIG_DACS) == __DAHDI_SIG_DACS) { + newmaster = chans[ch.chan]; + if ((ch.idlebits < 1) || (ch.idlebits >= DAHDI_MAX_CHANNELS)) + return -EINVAL; + if (!chans[ch.idlebits]) + return -EINVAL; + } else { + newmaster = chans[ch.chan]; + } + spin_lock_irqsave(&chans[ch.chan]->lock, flags); +#ifdef CONFIG_DAHDI_NET + if (chans[ch.chan]->flags & DAHDI_FLAG_NETDEV) { + if (ztchan_to_dev(chans[ch.chan])->flags & IFF_UP) { + spin_unlock_irqrestore(&chans[ch.chan]->lock, flags); + module_printk(KERN_WARNING, "Can't switch HDLC net mode on channel %s, since current interface is up\n", chans[ch.chan]->name); + return -EBUSY; + } + spin_unlock_irqrestore(&chans[ch.chan]->lock, flags); + unregister_hdlc_device(chans[ch.chan]->hdlcnetdev->netdev); + spin_lock_irqsave(&chans[ch.chan]->lock, flags); + free_netdev(chans[ch.chan]->hdlcnetdev->netdev); + kfree(chans[ch.chan]->hdlcnetdev); + chans[ch.chan]->hdlcnetdev = NULL; + chans[ch.chan]->flags &= ~DAHDI_FLAG_NETDEV; + } +#else + if (ch.sigtype == DAHDI_SIG_HDLCNET) { + spin_unlock_irqrestore(&chans[ch.chan]->lock, flags); + module_printk(KERN_WARNING, "DAHDI networking not supported by this build.\n"); + return -ENOSYS; + } +#endif + sigcap = chans[ch.chan]->sigcap; + /* If they support clear channel, then they support the HDLC and such through + us. */ + if (sigcap & DAHDI_SIG_CLEAR) + sigcap |= (DAHDI_SIG_HDLCRAW | DAHDI_SIG_HDLCFCS | DAHDI_SIG_HDLCNET | DAHDI_SIG_DACS); + + if ((sigcap & ch.sigtype) != ch.sigtype) + res = -EINVAL; + + if (!res && chans[ch.chan]->span->chanconfig) + res = chans[ch.chan]->span->chanconfig(chans[ch.chan], ch.sigtype); + + if (chans[ch.chan]->master != chans[ch.chan]) { + struct dahdi_chan *oldmaster = chans[ch.chan]->master; + + /* Clear the master channel */ + chans[ch.chan]->master = chans[ch.chan]; + chans[ch.chan]->nextslave = 0; + /* Unlink this channel from the master's channel list */ + recalc_slaves(oldmaster); + } + + if (!res) { + chans[ch.chan]->sig = ch.sigtype; + if (chans[ch.chan]->sig == DAHDI_SIG_CAS) + chans[ch.chan]->idlebits = ch.idlebits; + else + chans[ch.chan]->idlebits = 0; + if ((ch.sigtype & DAHDI_SIG_CLEAR) == DAHDI_SIG_CLEAR) { + /* Set clear channel flag if appropriate */ + chans[ch.chan]->flags &= ~DAHDI_FLAG_AUDIO; + chans[ch.chan]->flags |= DAHDI_FLAG_CLEAR; + } else { + /* Set audio flag and not clear channel otherwise */ + chans[ch.chan]->flags |= DAHDI_FLAG_AUDIO; + chans[ch.chan]->flags &= ~DAHDI_FLAG_CLEAR; + } + if ((ch.sigtype & DAHDI_SIG_HDLCRAW) == DAHDI_SIG_HDLCRAW) { + /* Set the HDLC flag */ + chans[ch.chan]->flags |= DAHDI_FLAG_HDLC; + } else { + /* Clear the HDLC flag */ + chans[ch.chan]->flags &= ~DAHDI_FLAG_HDLC; + } + if ((ch.sigtype & DAHDI_SIG_HDLCFCS) == DAHDI_SIG_HDLCFCS) { + /* Set FCS to be calculated if appropriate */ + chans[ch.chan]->flags |= DAHDI_FLAG_FCS; + } else { + /* Clear FCS flag */ + chans[ch.chan]->flags &= ~DAHDI_FLAG_FCS; + } + if ((ch.sigtype & __DAHDI_SIG_DACS) == __DAHDI_SIG_DACS) { + /* Setup conference properly */ + chans[ch.chan]->confmode = DAHDI_CONF_DIGITALMON; + chans[ch.chan]->confna = ch.idlebits; + if (chans[ch.chan]->span && + chans[ch.chan]->span->dacs && + chans[ch.idlebits] && + chans[ch.chan]->span && + (chans[ch.chan]->span->dacs == chans[ch.idlebits]->span->dacs)) + chans[ch.chan]->span->dacs(chans[ch.chan], chans[ch.idlebits]); + } else if (chans[ch.chan]->span && chans[ch.chan]->span->dacs) { + chans[ch.chan]->span->dacs(chans[ch.chan], NULL); + } + chans[ch.chan]->master = newmaster; + /* Note new slave if we are not our own master */ + if (newmaster != chans[ch.chan]) { + recalc_slaves(chans[ch.chan]->master); + } + if ((ch.sigtype & DAHDI_SIG_HARDHDLC) == DAHDI_SIG_HARDHDLC) { + chans[ch.chan]->flags &= ~DAHDI_FLAG_FCS; + chans[ch.chan]->flags &= ~DAHDI_FLAG_HDLC; + chans[ch.chan]->flags |= DAHDI_FLAG_NOSTDTXRX; + } else { + chans[ch.chan]->flags &= ~DAHDI_FLAG_NOSTDTXRX; + } + + if ((ch.sigtype & DAHDI_SIG_MTP2) == DAHDI_SIG_MTP2) + chans[ch.chan]->flags |= DAHDI_FLAG_MTP2; + else + chans[ch.chan]->flags &= ~DAHDI_FLAG_MTP2; + } +#ifdef CONFIG_DAHDI_NET + if (!res && + (newmaster == chans[ch.chan]) && + (chans[ch.chan]->sig == DAHDI_SIG_HDLCNET)) { + chans[ch.chan]->hdlcnetdev = dahdi_hdlc_alloc(); + if (chans[ch.chan]->hdlcnetdev) { +/* struct hdlc_device *hdlc = chans[ch.chan]->hdlcnetdev; + struct net_device *d = hdlc_to_dev(hdlc); mmm...get it right later --byg */ + + chans[ch.chan]->hdlcnetdev->netdev = alloc_hdlcdev(chans[ch.chan]->hdlcnetdev); + if (chans[ch.chan]->hdlcnetdev->netdev) { + chans[ch.chan]->hdlcnetdev->chan = chans[ch.chan]; +#if LINUX_VERSION_CODE <= KERNEL_VERSION(2,6,23) + SET_MODULE_OWNER(chans[ch.chan]->hdlcnetdev->netdev); +#endif + chans[ch.chan]->hdlcnetdev->netdev->irq = chans[ch.chan]->span->irq; + chans[ch.chan]->hdlcnetdev->netdev->tx_queue_len = 50; + chans[ch.chan]->hdlcnetdev->netdev->do_ioctl = dahdi_net_ioctl; + chans[ch.chan]->hdlcnetdev->netdev->open = dahdi_net_open; + chans[ch.chan]->hdlcnetdev->netdev->stop = dahdi_net_stop; + dev_to_hdlc(chans[ch.chan]->hdlcnetdev->netdev)->attach = dahdi_net_attach; + dev_to_hdlc(chans[ch.chan]->hdlcnetdev->netdev)->xmit = dahdi_xmit; + spin_unlock_irqrestore(&chans[ch.chan]->lock, flags); + /* Briefly restore interrupts while we register the device */ + res = dahdi_register_hdlc_device(chans[ch.chan]->hdlcnetdev->netdev, ch.netdev_name); + spin_lock_irqsave(&chans[ch.chan]->lock, flags); + } else { + module_printk(KERN_NOTICE, "Unable to allocate hdlc: *shrug*\n"); + res = -1; + } + if (!res) + chans[ch.chan]->flags |= DAHDI_FLAG_NETDEV; + } else { + module_printk(KERN_NOTICE, "Unable to allocate netdev: out of memory\n"); + res = -1; + } + } +#endif + if ((chans[ch.chan]->sig == DAHDI_SIG_HDLCNET) && + (chans[ch.chan] == newmaster) && + !(chans[ch.chan]->flags & DAHDI_FLAG_NETDEV)) + module_printk(KERN_NOTICE, "Unable to register HDLC device for channel %s\n", chans[ch.chan]->name); + if (!res) { + /* Setup default law */ + chans[ch.chan]->deflaw = ch.deflaw; + /* Copy back any modified settings */ + spin_unlock_irqrestore(&chans[ch.chan]->lock, flags); + if (copy_to_user((struct dahdi_chanconfig *)data, &ch, sizeof(ch))) + return -EFAULT; + spin_lock_irqsave(&chans[ch.chan]->lock, flags); + /* And hangup */ + dahdi_hangup(chans[ch.chan]); + y = dahdi_q_sig(chans[ch.chan]) & 0xff; + if (y >= 0) + chans[ch.chan]->rxsig = (unsigned char) y; + chans[ch.chan]->rxhooksig = DAHDI_RXSIG_INITIAL; + } +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Configured channel %s, flags %04lx, sig %04x\n", chans[ch.chan]->name, chans[ch.chan]->flags, chans[ch.chan]->sig); +#endif + spin_unlock_irqrestore(&chans[ch.chan]->lock, flags); + + return res; + } + case DAHDI_SFCONFIG: + { + struct dahdi_sfconfig sf; + + if (copy_from_user(&sf, (struct dahdi_chanconfig *)data, sizeof(sf))) + return -EFAULT; + VALID_CHANNEL(sf.chan); + if (chans[sf.chan]->sig != DAHDI_SIG_SF) return -EINVAL; + spin_lock_irqsave(&chans[sf.chan]->lock, flags); + chans[sf.chan]->rxp1 = sf.rxp1; + chans[sf.chan]->rxp2 = sf.rxp2; + chans[sf.chan]->rxp3 = sf.rxp3; + chans[sf.chan]->txtone = sf.txtone; + chans[sf.chan]->tx_v2 = sf.tx_v2; + chans[sf.chan]->tx_v3 = sf.tx_v3; + chans[sf.chan]->toneflags = sf.toneflag; + if (sf.txtone) /* if set to make tone for tx */ + { + if ((chans[sf.chan]->txhooksig && !(sf.toneflag & DAHDI_REVERSE_TXTONE)) || + ((!chans[sf.chan]->txhooksig) && (sf.toneflag & DAHDI_REVERSE_TXTONE))) + { + set_txtone(chans[sf.chan],sf.txtone,sf.tx_v2,sf.tx_v3); + } + else + { + set_txtone(chans[sf.chan],0,0,0); + } + } + spin_unlock_irqrestore(&chans[sf.chan]->lock, flags); + return res; + } + case DAHDI_DEFAULTZONE: + if (get_user(j,(int *)data)) + return -EFAULT; + return dahdi_set_default_zone(j); + case DAHDI_LOADZONE: + return ioctl_load_zone(data); + case DAHDI_FREEZONE: + get_user(j, (int *) data); + return free_tone_zone(j); + case DAHDI_SET_DIALPARAMS: + { + struct dahdi_dialparams tdp; + + if (copy_from_user(&tdp, (struct dahdi_dialparams *) data, sizeof(tdp))) + return -EFAULT; + + if ((tdp.dtmf_tonelen >= 10) && (tdp.dtmf_tonelen <= 4000)) { + global_dialparams.dtmf_tonelen = tdp.dtmf_tonelen; + } + if ((tdp.mfv1_tonelen >= 10) && (tdp.mfv1_tonelen <= 4000)) { + global_dialparams.mfv1_tonelen = tdp.mfv1_tonelen; + } + if ((tdp.mfr2_tonelen >= 10) && (tdp.mfr2_tonelen <= 4000)) { + global_dialparams.mfr2_tonelen = tdp.mfr2_tonelen; + } + + /* update the lengths in all currently loaded zones */ + write_lock(&zone_lock); + for (j = 0; j < ARRAY_SIZE(tone_zones); j++) { + struct dahdi_zone *z = tone_zones[j]; + + if (!z) + continue; + + for (i = 0; i < ARRAY_SIZE(z->dtmf); i++) { + z->dtmf[i].tonesamples = global_dialparams.dtmf_tonelen * DAHDI_CHUNKSIZE; + } + + /* for MFR1, we only adjust the length of the digits */ + for (i = DAHDI_TONE_MFR1_0; i <= DAHDI_TONE_MFR1_9; i++) { + z->mfr1[i - DAHDI_TONE_MFR1_BASE].tonesamples = global_dialparams.mfv1_tonelen * DAHDI_CHUNKSIZE; + } + + for (i = 0; i < ARRAY_SIZE(z->mfr2_fwd); i++) { + z->mfr2_fwd[i].tonesamples = global_dialparams.mfr2_tonelen * DAHDI_CHUNKSIZE; + } + + for (i = 0; i < ARRAY_SIZE(z->mfr2_rev); i++) { + z->mfr2_rev[i].tonesamples = global_dialparams.mfr2_tonelen * DAHDI_CHUNKSIZE; + } + } + write_unlock(&zone_lock); + + dtmf_silence.tonesamples = global_dialparams.dtmf_tonelen * DAHDI_CHUNKSIZE; + mfr1_silence.tonesamples = global_dialparams.mfv1_tonelen * DAHDI_CHUNKSIZE; + mfr2_silence.tonesamples = global_dialparams.mfr2_tonelen * DAHDI_CHUNKSIZE; + + break; + } + case DAHDI_GET_DIALPARAMS: + { + struct dahdi_dialparams tdp; + + tdp = global_dialparams; + if (copy_to_user((struct dahdi_dialparams *) data, &tdp, sizeof(tdp))) + return -EFAULT; + break; + } + case DAHDI_GETVERSION: + { + struct dahdi_versioninfo vi; + struct ecfactory *cur; + size_t space = sizeof(vi.echo_canceller) - 1; + + memset(&vi, 0, sizeof(vi)); + dahdi_copy_string(vi.version, DAHDI_VERSION, sizeof(vi.version)); + read_lock(&ecfactory_list_lock); + list_for_each_entry(cur, &ecfactory_list, list) { + strncat(vi.echo_canceller + strlen(vi.echo_canceller), cur->ec->name, space); + space -= strlen(cur->ec->name); + if (space < 1) { + break; + } + if (cur->list.next && (cur->list.next != &ecfactory_list)) { + strncat(vi.echo_canceller + strlen(vi.echo_canceller), ", ", space); + space -= 2; + if (space < 1) { + break; + } + } + } + read_unlock(&ecfactory_list_lock); + if (copy_to_user((struct dahdi_versioninfo *) data, &vi, sizeof(vi))) + return -EFAULT; + break; + } + case DAHDI_MAINT: /* do maintenance stuff */ + { + struct dahdi_maintinfo maint; + /* get struct from user */ + if (copy_from_user(&maint,(struct dahdi_maintinfo *) data, sizeof(maint))) + return -EFAULT; + /* must be valid span number */ + if ((maint.spanno < 1) || (maint.spanno > DAHDI_MAX_SPANS) || (!spans[maint.spanno])) + return -EINVAL; + if (!spans[maint.spanno]->maint) + return -ENOSYS; + spin_lock_irqsave(&spans[maint.spanno]->lock, flags); + /* save current maint state */ + i = spans[maint.spanno]->maintstat; + /* set maint mode */ + spans[maint.spanno]->maintstat = maint.command; + switch(maint.command) { + case DAHDI_MAINT_NONE: + case DAHDI_MAINT_LOCALLOOP: + case DAHDI_MAINT_REMOTELOOP: + /* if same, ignore it */ + if (i == maint.command) + break; + rv = spans[maint.spanno]->maint(spans[maint.spanno], maint.command); + spin_unlock_irqrestore(&spans[maint.spanno]->lock, flags); + if (rv) + return rv; + spin_lock_irqsave(&spans[maint.spanno]->lock, flags); + break; + case DAHDI_MAINT_LOOPUP: + case DAHDI_MAINT_LOOPDOWN: + spans[maint.spanno]->mainttimer = DAHDI_LOOPCODE_TIME * DAHDI_CHUNKSIZE; + rv = spans[maint.spanno]->maint(spans[maint.spanno], maint.command); + spin_unlock_irqrestore(&spans[maint.spanno]->lock, flags); + if (rv) + return rv; + rv = schluffen(&spans[maint.spanno]->maintq); + if (rv) + return rv; + spin_lock_irqsave(&spans[maint.spanno]->lock, flags); + break; + default: + module_printk(KERN_NOTICE, "Unknown maintenance event: %d\n", maint.command); + } + dahdi_alarm_notify(spans[maint.spanno]); /* process alarm-related events */ + spin_unlock_irqrestore(&spans[maint.spanno]->lock, flags); + break; + } + case DAHDI_DYNAMIC_CREATE: + case DAHDI_DYNAMIC_DESTROY: + if (dahdi_dynamic_ioctl) { + return dahdi_dynamic_ioctl(cmd, data); + } else { + request_module("dahdi_dynamic"); + if (dahdi_dynamic_ioctl) + return dahdi_dynamic_ioctl(cmd, data); + } + return -ENOSYS; + case DAHDI_EC_LICENSE_CHALLENGE: + case DAHDI_EC_LICENSE_RESPONSE: + if (dahdi_hpec_ioctl) { + return dahdi_hpec_ioctl(cmd, data); + } else { + request_module("dahdi_echocan_hpec"); + if (dahdi_hpec_ioctl) + return dahdi_hpec_ioctl(cmd, data); + } + return -ENOSYS; + default: + return dahdi_common_ioctl(inode, file, cmd, data, 0); + } + return 0; +} + +static int ioctl_dahdi_dial(struct dahdi_chan *chan, unsigned long data) +{ + struct dahdi_dialoperation *tdo; + unsigned long flags; + char *s; + int rv; + + tdo = kmalloc(sizeof(*tdo), GFP_KERNEL); + + if (!tdo) + return -ENOMEM; + + if (copy_from_user(tdo, (struct dahdi_dialoperation *)data, sizeof(*tdo))) + return -EFAULT; + rv = 0; + /* Force proper NULL termination and uppercase entry */ + tdo->dialstr[DAHDI_MAX_DTMF_BUF - 1] = '\0'; + for (s = tdo->dialstr; *s; s++) + *s = toupper(*s); + spin_lock_irqsave(&chan->lock, flags); + if (!chan->curzone) { + spin_unlock_irqrestore(&chan->lock, flags); + /* The tone zones are loaded by dahdi_cfg from /etc/dahdi/system.conf */ + module_printk(KERN_WARNING, "Cannot dial until a tone zone is loaded.\n"); + return -ENODATA; + } + switch (tdo->op) { + case DAHDI_DIAL_OP_CANCEL: + chan->curtone = NULL; + chan->dialing = 0; + chan->txdialbuf[0] = '\0'; + chan->tonep = 0; + chan->pdialcount = 0; + break; + case DAHDI_DIAL_OP_REPLACE: + strcpy(chan->txdialbuf, tdo->dialstr); + chan->dialing = 1; + __do_dtmf(chan); + break; + case DAHDI_DIAL_OP_APPEND: + if (strlen(tdo->dialstr) + strlen(chan->txdialbuf) >= (DAHDI_MAX_DTMF_BUF - 1)) { + rv = -EBUSY; + break; + } + dahdi_copy_string(chan->txdialbuf + strlen(chan->txdialbuf), tdo->dialstr, DAHDI_MAX_DTMF_BUF - strlen(chan->txdialbuf)); + if (!chan->dialing) { + chan->dialing = 1; + __do_dtmf(chan); + } + break; + default: + rv = -EINVAL; + } + spin_unlock_irqrestore(&chan->lock, flags); + return rv; +} + +static int dahdi_chanandpseudo_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data, int unit) +{ + struct dahdi_chan *chan = chans[unit]; + union { + struct dahdi_bufferinfo bi; + struct dahdi_confinfo conf; + struct dahdi_ring_cadence cad; + } stack; + unsigned long flags; + int i, j, k, rv; + int ret, c; + + if (!chan) + return -EINVAL; + switch(cmd) { + case DAHDI_DIALING: + spin_lock_irqsave(&chan->lock, flags); + j = chan->dialing; + spin_unlock_irqrestore(&chan->lock, flags); + if (copy_to_user((int *)data,&j,sizeof(int))) + return -EFAULT; + return 0; + case DAHDI_DIAL: + return ioctl_dahdi_dial(chan, data); + case DAHDI_GET_BUFINFO: + memset(&stack.bi, 0, sizeof(stack.bi)); + stack.bi.rxbufpolicy = chan->rxbufpolicy; + stack.bi.txbufpolicy = chan->txbufpolicy; + stack.bi.numbufs = chan->numbufs; + stack.bi.bufsize = chan->blocksize; + /* XXX FIXME! XXX */ + stack.bi.readbufs = -1; + stack.bi.writebufs = -1; + if (copy_to_user((struct dahdi_bufferinfo *)data, &stack.bi, sizeof(stack.bi))) + return -EFAULT; + break; + case DAHDI_SET_BUFINFO: + if (copy_from_user(&stack.bi, (struct dahdi_bufferinfo *)data, sizeof(stack.bi))) + return -EFAULT; + if (stack.bi.bufsize > DAHDI_MAX_BLOCKSIZE) + return -EINVAL; + if (stack.bi.bufsize < 16) + return -EINVAL; + if (stack.bi.bufsize * stack.bi.numbufs > DAHDI_MAX_BUF_SPACE) + return -EINVAL; + /* It does not make sense to allow user mode to change the + * receive buffering policy. DAHDI always provides received + * buffers to upper layers immediately. Transmission is + * different since we might want to allow the kernel to build + * up a buffer in order to prevent underruns from the + * interrupt context. */ + chan->txbufpolicy = stack.bi.txbufpolicy & 0x3; + if ((rv = dahdi_reallocbufs(chan, stack.bi.bufsize, stack.bi.numbufs))) + return (rv); + break; + case DAHDI_GET_BLOCKSIZE: /* get blocksize */ + put_user(chan->blocksize,(int *)data); /* return block size */ + break; + case DAHDI_SET_BLOCKSIZE: /* set blocksize */ + get_user(j,(int *)data); + /* cannot be larger than max amount */ + if (j > DAHDI_MAX_BLOCKSIZE) return(-EINVAL); + /* cannot be less then 16 */ + if (j < 16) return(-EINVAL); + /* allocate a single kernel buffer which we then + sub divide into four pieces */ + if ((rv = dahdi_reallocbufs(chan, j, chan->numbufs))) + return (rv); + break; + case DAHDI_FLUSH: /* flush input buffer, output buffer, and/or event queue */ + get_user(i,(int *)data); /* get param */ + spin_lock_irqsave(&chan->lock, flags); + if (i & DAHDI_FLUSH_READ) /* if for read (input) */ + { + /* initialize read buffers and pointers */ + chan->inreadbuf = 0; + chan->outreadbuf = -1; + for (j=0;jnumbufs;j++) { + /* Do we need this? */ + chan->readn[j] = 0; + chan->readidx[j] = 0; + } + wake_up_interruptible(&chan->readbufq); /* wake_up_interruptible waiting on read */ + wake_up_interruptible(&chan->sel); /* wake_up_interruptible waiting on select */ + } + if (i & DAHDI_FLUSH_WRITE) /* if for write (output) */ + { + /* initialize write buffers and pointers */ + chan->outwritebuf = -1; + chan->inwritebuf = 0; + for (j=0;jnumbufs;j++) { + /* Do we need this? */ + chan->writen[j] = 0; + chan->writeidx[j] = 0; + } + wake_up_interruptible(&chan->writebufq); /* wake_up_interruptible waiting on write */ + wake_up_interruptible(&chan->sel); /* wake_up_interruptible waiting on select */ + /* if IO MUX wait on write empty, well, this + certainly *did* empty the write */ + if (chan->iomask & DAHDI_IOMUX_WRITEEMPTY) + wake_up_interruptible(&chan->eventbufq); /* wake_up_interruptible waiting on IOMUX */ + } + if (i & DAHDI_FLUSH_EVENT) /* if for events */ + { + /* initialize the event pointers */ + chan->eventinidx = chan->eventoutidx = 0; + } + spin_unlock_irqrestore(&chan->lock, flags); + break; + case DAHDI_SYNC: /* wait for no tx */ + for(;;) /* loop forever */ + { + spin_lock_irqsave(&chan->lock, flags); + /* Know if there is a write pending */ + i = (chan->outwritebuf > -1); + spin_unlock_irqrestore(&chan->lock, flags); + if (!i) break; /* skip if none */ + rv = schluffen(&chan->writebufq); + if (rv) return(rv); + } + break; + case DAHDI_IOMUX: /* wait for something to happen */ + get_user(chan->iomask,(int*)data); /* save mask */ + if (!chan->iomask) return(-EINVAL); /* cant wait for nothing */ + for(;;) /* loop forever */ + { + /* has to have SOME mask */ + ret = 0; /* start with empty return value */ + spin_lock_irqsave(&chan->lock, flags); + /* if looking for read */ + if (chan->iomask & DAHDI_IOMUX_READ) + { + /* if read available */ + if ((chan->outreadbuf > -1) && !chan->rxdisable) + ret |= DAHDI_IOMUX_READ; + } + /* if looking for write avail */ + if (chan->iomask & DAHDI_IOMUX_WRITE) + { + if (chan->inwritebuf > -1) + ret |= DAHDI_IOMUX_WRITE; + } + /* if looking for write empty */ + if (chan->iomask & DAHDI_IOMUX_WRITEEMPTY) + { + /* if everything empty -- be sure the transmitter is enabled */ + chan->txdisable = 0; + if (chan->outwritebuf < 0) + ret |= DAHDI_IOMUX_WRITEEMPTY; + } + /* if looking for signalling event */ + if (chan->iomask & DAHDI_IOMUX_SIGEVENT) + { + /* if event */ + if (chan->eventinidx != chan->eventoutidx) + ret |= DAHDI_IOMUX_SIGEVENT; + } + spin_unlock_irqrestore(&chan->lock, flags); + /* if something to return, or not to wait */ + if (ret || (chan->iomask & DAHDI_IOMUX_NOWAIT)) + { + /* set return value */ + put_user(ret,(int *)data); + break; /* get out of loop */ + } + rv = schluffen(&chan->eventbufq); + if (rv) return(rv); + } + /* clear IO MUX mask */ + chan->iomask = 0; + break; + case DAHDI_GETEVENT: /* Get event on queue */ + /* set up for no event */ + j = DAHDI_EVENT_NONE; + spin_lock_irqsave(&chan->lock, flags); + /* if some event in queue */ + if (chan->eventinidx != chan->eventoutidx) + { + j = chan->eventbuf[chan->eventoutidx++]; + /* get the data, bump index */ + /* if index overflow, set to beginning */ + if (chan->eventoutidx >= DAHDI_MAX_EVENTSIZE) + chan->eventoutidx = 0; + } + spin_unlock_irqrestore(&chan->lock, flags); + put_user(j,(int *)data); + break; + case DAHDI_CONFMUTE: /* set confmute flag */ + get_user(j,(int *)data); /* get conf # */ + if (!(chan->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + spin_lock_irqsave(&bigzaplock, flags); + chan->confmute = j; + spin_unlock_irqrestore(&bigzaplock, flags); + break; + case DAHDI_GETCONFMUTE: /* get confmute flag */ + if (!(chan->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + j = chan->confmute; + put_user(j,(int *)data); /* get conf # */ + rv = 0; + break; + case DAHDI_SETTONEZONE: + get_user(j, (int *) data); + rv = set_tone_zone(chan, j); + return rv; + case DAHDI_GETTONEZONE: + spin_lock_irqsave(&chan->lock, flags); + if (chan->curzone) + j = chan->tonezone; + spin_unlock_irqrestore(&chan->lock, flags); + put_user(j, (int *) data); + break; + case DAHDI_SENDTONE: + get_user(j,(int *)data); + spin_lock_irqsave(&chan->lock, flags); + rv = start_tone(chan, j); + spin_unlock_irqrestore(&chan->lock, flags); + return rv; + case DAHDI_GETCONF_V1: /* intentional drop through */ + case DAHDI_GETCONF: /* get conf stuff */ + if (copy_from_user(&stack.conf,(struct dahdi_confinfo *) data,sizeof(stack.conf))) + return -EFAULT; + i = stack.conf.chan; /* get channel no */ + /* if zero, use current channel no */ + if (!i) i = chan->channo; + /* make sure channel number makes sense */ + if ((i < 0) || (i > DAHDI_MAX_CONF) || (!chans[i])) return(-EINVAL); + if (!(chans[i]->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + stack.conf.chan = i; /* get channel number */ + stack.conf.confno = chans[i]->confna; /* get conference number */ + stack.conf.confmode = chans[i]->confmode; /* get conference mode */ + if (copy_to_user((struct dahdi_confinfo *) data,&stack.conf,sizeof(stack.conf))) + return -EFAULT; + break; + case DAHDI_SETCONF_V1: /* Intentional fall through. */ + case DAHDI_SETCONF: /* set conf stuff */ + if (copy_from_user(&stack.conf,(struct dahdi_confinfo *) data,sizeof(stack.conf))) + return -EFAULT; + i = stack.conf.chan; /* get channel no */ + /* if zero, use current channel no */ + if (!i) i = chan->channo; + /* make sure channel number makes sense */ + if ((i < 1) || (i > DAHDI_MAX_CHANNELS) || (!chans[i])) return(-EINVAL); + if (!(chans[i]->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + if ((stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITORTX || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITORBOTH || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR_RX_PREECHO || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR_TX_PREECHO || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITORBOTH_PREECHO) { + /* Monitor mode -- it's a channel */ + if ((stack.conf.confno < 0) || (stack.conf.confno >= DAHDI_MAX_CHANNELS) || !chans[stack.conf.confno]) return(-EINVAL); + } else { + /* make sure conf number makes sense, too */ + if ((stack.conf.confno < -1) || (stack.conf.confno > DAHDI_MAX_CONF)) return(-EINVAL); + } + + /* if taking off of any conf, must have 0 mode */ + if ((!stack.conf.confno) && stack.conf.confmode) return(-EINVAL); + /* likewise if 0 mode must have no conf */ + if ((!stack.conf.confmode) && stack.conf.confno) return (-EINVAL); + stack.conf.chan = i; /* return with real channel # */ + spin_lock_irqsave(&bigzaplock, flags); + spin_lock(&chan->lock); + if (stack.conf.confno == -1) + stack.conf.confno = dahdi_first_empty_conference(); + if ((stack.conf.confno < 1) && (stack.conf.confmode)) { + /* No more empty conferences */ + spin_unlock(&chan->lock); + spin_unlock_irqrestore(&bigzaplock, flags); + return -EBUSY; + } + /* if changing confs, clear last added info */ + if (stack.conf.confno != chans[i]->confna) { + memset(chans[i]->conflast, 0, DAHDI_MAX_CHUNKSIZE); + memset(chans[i]->conflast1, 0, DAHDI_MAX_CHUNKSIZE); + memset(chans[i]->conflast2, 0, DAHDI_MAX_CHUNKSIZE); + } + j = chans[i]->confna; /* save old conference number */ + chans[i]->confna = stack.conf.confno; /* set conference number */ + chans[i]->confmode = stack.conf.confmode; /* set conference mode */ + chans[i]->_confn = 0; /* Clear confn */ + dahdi_check_conf(j); + dahdi_check_conf(stack.conf.confno); + if (chans[i]->span && chans[i]->span->dacs) { + if (((stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_DIGITALMON) && + chans[stack.conf.confno]->span && + chans[stack.conf.confno]->span->dacs == chans[i]->span->dacs && + chans[i]->txgain == defgain && + chans[i]->rxgain == defgain && + chans[stack.conf.confno]->txgain == defgain && + chans[stack.conf.confno]->rxgain == defgain) { + chans[i]->span->dacs(chans[i], chans[stack.conf.confno]); + } else { + chans[i]->span->dacs(chans[i], NULL); + } + } + /* if we are going onto a conf */ + if (stack.conf.confno && + ((stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONF || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONFANN || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONFMON || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_CONFANNMON || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_REALANDPSEUDO)) { + /* Get alias */ + chans[i]->_confn = dahdi_get_conf_alias(stack.conf.confno); + } + + if (chans[stack.conf.confno]) { + if ((stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR_RX_PREECHO || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITOR_TX_PREECHO || + (stack.conf.confmode & DAHDI_CONF_MODE_MASK) == DAHDI_CONF_MONITORBOTH_PREECHO) + chans[stack.conf.confno]->readchunkpreec = kmalloc(sizeof(*chans[stack.conf.confno]->readchunkpreec) * DAHDI_CHUNKSIZE, GFP_ATOMIC); + else { + if (chans[stack.conf.confno]->readchunkpreec) { + kfree(chans[stack.conf.confno]->readchunkpreec); + chans[stack.conf.confno]->readchunkpreec = NULL; + } + } + } + + spin_unlock(&chan->lock); + spin_unlock_irqrestore(&bigzaplock, flags); + if (copy_to_user((struct dahdi_confinfo *) data,&stack.conf,sizeof(stack.conf))) + return -EFAULT; + break; + case DAHDI_CONFLINK: /* do conf link stuff */ + if (!(chan->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + if (copy_from_user(&stack.conf,(struct dahdi_confinfo *) data,sizeof(stack.conf))) + return -EFAULT; + /* check sanity of arguments */ + if ((stack.conf.chan < 0) || (stack.conf.chan > DAHDI_MAX_CONF)) return(-EINVAL); + if ((stack.conf.confno < 0) || (stack.conf.confno > DAHDI_MAX_CONF)) return(-EINVAL); + /* cant listen to self!! */ + if (stack.conf.chan && (stack.conf.chan == stack.conf.confno)) return(-EINVAL); + spin_lock_irqsave(&bigzaplock, flags); + spin_lock(&chan->lock); + /* if to clear all links */ + if ((!stack.conf.chan) && (!stack.conf.confno)) + { + /* clear all the links */ + memset(conf_links,0,sizeof(conf_links)); + recalc_maxlinks(); + spin_unlock(&chan->lock); + spin_unlock_irqrestore(&bigzaplock, flags); + break; + } + rv = 0; /* clear return value */ + /* look for already existant specified combination */ + for(i = 1; i <= DAHDI_MAX_CONF; i++) + { + /* if found, exit */ + if ((conf_links[i].src == stack.conf.chan) && + (conf_links[i].dst == stack.conf.confno)) break; + } + if (i <= DAHDI_MAX_CONF) /* if found */ + { + if (!stack.conf.confmode) /* if to remove link */ + { + conf_links[i].src = conf_links[i].dst = 0; + } + else /* if to add and already there, error */ + { + rv = -EEXIST; + } + } + else /* if not found */ + { + if (stack.conf.confmode) /* if to add link */ + { + /* look for empty location */ + for(i = 1; i <= DAHDI_MAX_CONF; i++) + { + /* if empty, exit loop */ + if ((!conf_links[i].src) && + (!conf_links[i].dst)) break; + } + /* if empty spot found */ + if (i <= DAHDI_MAX_CONF) + { + conf_links[i].src = stack.conf.chan; + conf_links[i].dst = stack.conf.confno; + } + else /* if no empties -- error */ + { + rv = -ENOSPC; + } + } + else /* if to remove, and not found -- error */ + { + rv = -ENOENT; + } + } + recalc_maxlinks(); + spin_unlock(&chan->lock); + spin_unlock_irqrestore(&bigzaplock, flags); + return(rv); + case DAHDI_CONFDIAG_V1: /* Intention fall-through */ + case DAHDI_CONFDIAG: /* output diagnostic info to console */ + if (!(chan->flags & DAHDI_FLAG_AUDIO)) return (-EINVAL); + get_user(j,(int *)data); /* get conf # */ + /* loop thru the interesting ones */ + for(i = ((j) ? j : 1); i <= ((j) ? j : DAHDI_MAX_CONF); i++) + { + c = 0; + for(k = 1; k < DAHDI_MAX_CHANNELS; k++) + { + /* skip if no pointer */ + if (!chans[k]) continue; + /* skip if not in this conf */ + if (chans[k]->confna != i) continue; + if (!c) module_printk(KERN_NOTICE, "Conf #%d:\n",i); + c = 1; + module_printk(KERN_NOTICE, "chan %d, mode %x\n", k,chans[k]->confmode); + } + rv = 0; + for(k = 1; k <= DAHDI_MAX_CONF; k++) + { + if (conf_links[k].dst == i) + { + if (!c) module_printk(KERN_NOTICE, "Conf #%d:\n",i); + c = 1; + if (!rv) module_printk(KERN_NOTICE, "Snooping on:\n"); + rv = 1; + module_printk(KERN_NOTICE, "conf %d\n",conf_links[k].src); + } + } + if (c) module_printk(KERN_NOTICE, "\n"); + } + break; + case DAHDI_CHANNO: /* get channel number of stream */ + put_user(unit,(int *)data); /* return unit/channel number */ + break; + case DAHDI_SETLAW: + get_user(j, (int *)data); + if ((j < 0) || (j > DAHDI_LAW_ALAW)) + return -EINVAL; + dahdi_set_law(chan, j); + break; + case DAHDI_SETLINEAR: + get_user(j, (int *)data); + /* Makes no sense on non-audio channels */ + if (!(chan->flags & DAHDI_FLAG_AUDIO)) + return -EINVAL; + + if (j) + chan->flags |= DAHDI_FLAG_LINEAR; + else + chan->flags &= ~DAHDI_FLAG_LINEAR; + break; + case DAHDI_SETCADENCE: + if (data) { + /* Use specific ring cadence */ + if (copy_from_user(&stack.cad, (struct dahdi_ring_cadence *)data, sizeof(stack.cad))) + return -EFAULT; + memcpy(chan->ringcadence, &stack.cad, sizeof(chan->ringcadence)); + chan->firstcadencepos = 0; + /* Looking for negative ringing time indicating where to loop back into ringcadence */ + for (i=0; iringcadence[i]<0) { + chan->ringcadence[i] *= -1; + chan->firstcadencepos = i; + break; + } + } + } else { + /* Reset to default */ + chan->firstcadencepos = 0; + if (chan->curzone) { + memcpy(chan->ringcadence, chan->curzone->ringcadence, sizeof(chan->ringcadence)); + /* Looking for negative ringing time indicating where to loop back into ringcadence */ + for (i=0; iringcadence[i]<0) { + chan->ringcadence[i] *= -1; + chan->firstcadencepos = i; + break; + } + } + } else { + memset(chan->ringcadence, 0, sizeof(chan->ringcadence)); + chan->ringcadence[0] = chan->starttime; + chan->ringcadence[1] = DAHDI_RINGOFFTIME; + } + } + break; + default: + /* Check for common ioctl's and private ones */ + rv = dahdi_common_ioctl(inode, file, cmd, data, unit); + /* if no span, just return with value */ + if (!chan->span) return rv; + if ((rv == -ENOTTY) && chan->span->ioctl) + rv = chan->span->ioctl(chan, cmd, data); + return rv; + + } + return 0; +} + +#ifdef CONFIG_DAHDI_PPP +/* + * This is called at softirq (BH) level when there are calls + * we need to make to the ppp_generic layer. We do it this + * way because the ppp_generic layer functions may not be called + * at interrupt level. + */ +static void do_ppp_calls(unsigned long data) +{ + struct dahdi_chan *chan = (struct dahdi_chan *) data; + struct sk_buff *skb; + + if (!chan->ppp) + return; + if (chan->do_ppp_wakeup) { + chan->do_ppp_wakeup = 0; + ppp_output_wakeup(chan->ppp); + } + while ((skb = skb_dequeue(&chan->ppp_rq)) != NULL) + ppp_input(chan->ppp, skb); + if (chan->do_ppp_error) { + chan->do_ppp_error = 0; + ppp_input_error(chan->ppp, 0); + } +} +#endif + +static int ioctl_echocancel(struct dahdi_chan *chan, struct dahdi_echocanparams *ecp, void *data) +{ + struct dahdi_echocan_state *ec = NULL, *ec_state; + const struct dahdi_echocan_factory *ec_current; + struct dahdi_echocanparam *params; + int ret; + unsigned long flags; + + if (ecp->param_count > DAHDI_MAX_ECHOCANPARAMS) + return -E2BIG; + + if (ecp->tap_length == 0) { + /* disable mode, don't need to inspect params */ + spin_lock_irqsave(&chan->lock, flags); + ec_state = chan->ec_state; + chan->ec_state = NULL; + ec_current = chan->ec_current; + chan->ec_current = NULL; + spin_unlock_irqrestore(&chan->lock, flags); + if (ec_state) { + ec_state->ops->echocan_free(chan, ec_state); + release_echocan(ec_current); + } + + return 0; + } + + params = kmalloc(sizeof(params[0]) * DAHDI_MAX_ECHOCANPARAMS, GFP_KERNEL); + + if (!params) + return -ENOMEM; + + /* enable mode, need the params */ + + if (copy_from_user(params, (struct dahdi_echocanparam *) data, sizeof(params[0]) * ecp->param_count)) { + ret = -EFAULT; + goto exit_with_free; + } + + /* free any echocan that may be on the channel already */ + spin_lock_irqsave(&chan->lock, flags); + ec_state = chan->ec_state; + chan->ec_state = NULL; + ec_current = chan->ec_current; + chan->ec_current = NULL; + spin_unlock_irqrestore(&chan->lock, flags); + if (ec_state) { + ec_state->ops->echocan_free(chan, ec_state); + release_echocan(ec_current); + } + + switch (ecp->tap_length) { + case 32: + case 64: + case 128: + case 256: + case 512: + case 1024: + break; + default: + ecp->tap_length = deftaps; + } + + ret = -ENODEV; + ec_current = NULL; + + /* attempt to use the span's echo canceler; fall back to built-in + if it fails (but not if an error occurs) */ + if (chan->span && chan->span->echocan_create) + ret = chan->span->echocan_create(chan, ecp, params, &ec); + + if ((ret == -ENODEV) && chan->ec_factory) { +#ifdef USE_ECHOCAN_REFCOUNT + /* try to get another reference to the module providing + this channel's echo canceler */ + if (!try_module_get(chan->ec_factory->owner)) { + module_printk(KERN_ERR, "Cannot get a reference to the '%s' echo canceler\n", chan->ec_factory->name); + goto exit_with_free; + } +#endif + + /* got the reference, copy the pointer and use it for making + an echo canceler instance if possible */ + ec_current = chan->ec_factory; + + ret = ec_current->echocan_create(chan, ecp, params, &ec); + if (ret) { + release_echocan(ec_current); + + goto exit_with_free; + } + if (!ec) { + module_printk(KERN_ERR, "%s failed to allocate an " \ + "dahdi_echocan_state instance.\n", + ec_current->name); + ret = -EFAULT; + goto exit_with_free; + } + } + + if (ec) { + spin_lock_irqsave(&chan->lock, flags); + chan->ec_current = ec_current; + chan->ec_state = ec; + ec->status.mode = ECHO_MODE_ACTIVE; + if (!ec->features.CED_tx_detect) { + echo_can_disable_detector_init(&chan->ec_state->txecdis); + } + if (!ec->features.CED_rx_detect) { + echo_can_disable_detector_init(&chan->ec_state->rxecdis); + } + spin_unlock_irqrestore(&chan->lock, flags); + } + +exit_with_free: + kfree(params); + + return ret; +} + +static void set_echocan_fax_mode(struct dahdi_chan *chan, unsigned int channo, const char *reason, unsigned int enable) +{ + if (enable) { + if (!chan->ec_state) + module_printk(KERN_NOTICE, "Ignoring FAX mode request because of %s for channel %d with no echo canceller\n", reason, channo); + else if (chan->ec_state->status.mode == ECHO_MODE_FAX) + module_printk(KERN_NOTICE, "Ignoring FAX mode request because of %s for echo canceller already in FAX mode on channel %d\n", reason, channo); + else if (chan->ec_state->status.mode != ECHO_MODE_ACTIVE) + module_printk(KERN_NOTICE, "Ignoring FAX mode request because of %s for echo canceller not in active mode on channel %d\n", reason, channo); + else if (chan->ec_state->features.NLP_automatic) { + /* for echocans that automatically do the right thing, just + * mark it as being in FAX mode without making any + * changes, as none are necessary. + */ + chan->ec_state->status.mode = ECHO_MODE_FAX; + } else if (chan->ec_state->features.NLP_toggle) { + module_printk(KERN_NOTICE, "Disabled echo canceller NLP because of %s on channel %d\n", reason, channo); + dahdi_qevent_nolock(chan, DAHDI_EVENT_EC_NLP_DISABLED); + chan->ec_state->ops->echocan_NLP_toggle(chan->ec_state, 0); + chan->ec_state->status.mode = ECHO_MODE_FAX; + } else { + module_printk(KERN_NOTICE, "Idled echo canceller because of %s on channel %d\n", reason, channo); + chan->ec_state->status.mode = ECHO_MODE_IDLE; + } + } else { + if (!chan->ec_state) + module_printk(KERN_NOTICE, "Ignoring voice mode request because of %s for channel %d with no echo canceller\n", reason, channo); + else if (chan->ec_state->status.mode == ECHO_MODE_ACTIVE) + module_printk(KERN_NOTICE, "Ignoring voice mode request because of %s for echo canceller already in voice mode on channel %d\n", reason, channo); + else if ((chan->ec_state->status.mode != ECHO_MODE_FAX) && + (chan->ec_state->status.mode != ECHO_MODE_IDLE)) + module_printk(KERN_NOTICE, "Ignoring voice mode request because of %s for echo canceller not in FAX or idle mode on channel %d\n", reason, channo); + else if (chan->ec_state->features.NLP_automatic) { + /* for echocans that automatically do the right thing, just + * mark it as being in active mode without making any + * changes, as none are necessary. + */ + chan->ec_state->status.mode = ECHO_MODE_ACTIVE; + } else if (chan->ec_state->features.NLP_toggle) { + module_printk(KERN_NOTICE, "Enabled echo canceller NLP because of %s on channel %d\n", reason, channo); + dahdi_qevent_nolock(chan, DAHDI_EVENT_EC_NLP_ENABLED); + chan->ec_state->ops->echocan_NLP_toggle(chan->ec_state, 1); + chan->ec_state->status.mode = ECHO_MODE_ACTIVE; + } else { + module_printk(KERN_NOTICE, "Activated echo canceller because of %s on channel %d\n", reason, channo); + chan->ec_state->status.mode = ECHO_MODE_ACTIVE; + } + } +} + +static int dahdi_chan_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data, int unit) +{ + struct dahdi_chan *chan = chans[unit]; + unsigned long flags; + int j, rv; + int ret; + int oldconf; + void *rxgain=NULL; + + WARN_ON(!chan->master); + if (!chan) + return -ENOSYS; + + switch(cmd) { + case DAHDI_SETSIGFREEZE: + get_user(j, (int *)data); + spin_lock_irqsave(&chan->lock, flags); + if (j) { + chan->flags |= DAHDI_FLAG_SIGFREEZE; + } else { + chan->flags &= ~DAHDI_FLAG_SIGFREEZE; + } + spin_unlock_irqrestore(&chan->lock, flags); + break; + case DAHDI_GETSIGFREEZE: + spin_lock_irqsave(&chan->lock, flags); + if (chan->flags & DAHDI_FLAG_SIGFREEZE) + j = 1; + else + j = 0; + spin_unlock_irqrestore(&chan->lock, flags); + put_user(j, (int *)data); + break; + case DAHDI_AUDIOMODE: + /* Only literal clear channels can be put in */ + if (chan->sig != DAHDI_SIG_CLEAR) return (-EINVAL); + get_user(j, (int *)data); + if (j) { + spin_lock_irqsave(&chan->lock, flags); + chan->flags |= DAHDI_FLAG_AUDIO; + chan->flags &= ~(DAHDI_FLAG_HDLC | DAHDI_FLAG_FCS); + spin_unlock_irqrestore(&chan->lock, flags); + } else { + /* Coming out of audio mode, also clear all + conferencing and gain related info as well + as echo canceller */ + struct dahdi_echocan_state *ec_state; + const struct dahdi_echocan_factory *ec_current; + + spin_lock_irqsave(&chan->lock, flags); + chan->flags &= ~DAHDI_FLAG_AUDIO; + /* save old conf number, if any */ + oldconf = chan->confna; + /* initialize conference variables */ + chan->_confn = 0; + chan->confna = 0; + if (chan->span && chan->span->dacs) + chan->span->dacs(chan, NULL); + chan->confmode = 0; + chan->confmute = 0; + memset(chan->conflast, 0, sizeof(chan->conflast)); + memset(chan->conflast1, 0, sizeof(chan->conflast1)); + memset(chan->conflast2, 0, sizeof(chan->conflast2)); + ec_state = chan->ec_state; + chan->ec_state = NULL; + ec_current = chan->ec_current; + chan->ec_current = NULL; + /* release conference resource, if any to release */ + reset_conf(chan); + if (chan->gainalloc && chan->rxgain) + rxgain = chan->rxgain; + else + rxgain = NULL; + + chan->rxgain = defgain; + chan->txgain = defgain; + chan->gainalloc = 0; + spin_unlock_irqrestore(&chan->lock, flags); + + if (ec_state) { + ec_state->ops->echocan_free(chan, ec_state); + release_echocan(ec_current); + } + + if (rxgain) + kfree(rxgain); + if (oldconf) dahdi_check_conf(oldconf); + } + break; + case DAHDI_HDLCPPP: +#ifdef CONFIG_DAHDI_PPP + if (chan->sig != DAHDI_SIG_CLEAR) return (-EINVAL); + get_user(j, (int *)data); + if (j) { + if (!chan->ppp) { + chan->ppp = kzalloc(sizeof(struct ppp_channel), GFP_KERNEL); + if (chan->ppp) { + struct dahdi_echocan_state *tec; + const struct dahdi_echocan_factory *ec_current; + + chan->ppp->private = chan; + chan->ppp->ops = &ztppp_ops; + chan->ppp->mtu = DAHDI_DEFAULT_MTU_MRU; + chan->ppp->hdrlen = 0; + skb_queue_head_init(&chan->ppp_rq); + chan->do_ppp_wakeup = 0; + tasklet_init(&chan->ppp_calls, do_ppp_calls, + (unsigned long)chan); + if ((ret = dahdi_reallocbufs(chan, DAHDI_DEFAULT_MTU_MRU, DAHDI_DEFAULT_NUM_BUFS))) { + kfree(chan->ppp); + chan->ppp = NULL; + return ret; + } + + if ((ret = ppp_register_channel(chan->ppp))) { + kfree(chan->ppp); + chan->ppp = NULL; + return ret; + } + tec = chan->ec_state; + chan->ec_state = NULL; + ec_current = chan->ec_current; + chan->ec_current = NULL; + /* Make sure there's no gain */ + if (chan->gainalloc) + kfree(chan->rxgain); + chan->rxgain = defgain; + chan->txgain = defgain; + chan->gainalloc = 0; + chan->flags &= ~DAHDI_FLAG_AUDIO; + chan->flags |= (DAHDI_FLAG_PPP | DAHDI_FLAG_HDLC | DAHDI_FLAG_FCS); + + if (tec) { + tec->ops->echocan_free(chan, tec); + release_echocan(ec_current); + } + } else + return -ENOMEM; + } + } else { + chan->flags &= ~(DAHDI_FLAG_PPP | DAHDI_FLAG_HDLC | DAHDI_FLAG_FCS); + if (chan->ppp) { + struct ppp_channel *ppp = chan->ppp; + chan->ppp = NULL; + tasklet_kill(&chan->ppp_calls); + skb_queue_purge(&chan->ppp_rq); + ppp_unregister_channel(ppp); + kfree(ppp); + } + } +#else + module_printk(KERN_NOTICE, "PPP support not compiled in\n"); + return -ENOSYS; +#endif + break; + case DAHDI_HDLCRAWMODE: + if (chan->sig != DAHDI_SIG_CLEAR) return (-EINVAL); + get_user(j, (int *)data); + chan->flags &= ~(DAHDI_FLAG_AUDIO | DAHDI_FLAG_HDLC | DAHDI_FLAG_FCS); + if (j) { + chan->flags |= DAHDI_FLAG_HDLC; + fasthdlc_init(&chan->rxhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + fasthdlc_init(&chan->txhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + } + break; + case DAHDI_HDLCFCSMODE: + if (chan->sig != DAHDI_SIG_CLEAR) return (-EINVAL); + get_user(j, (int *)data); + chan->flags &= ~(DAHDI_FLAG_AUDIO | DAHDI_FLAG_HDLC | DAHDI_FLAG_FCS); + if (j) { + chan->flags |= DAHDI_FLAG_HDLC | DAHDI_FLAG_FCS; + fasthdlc_init(&chan->rxhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + fasthdlc_init(&chan->txhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + } + break; + case DAHDI_HDLC_RATE: + get_user(j, (int *) data); + if (j == 56) { + chan->flags |= DAHDI_FLAG_HDLC56; + } else { + chan->flags &= ~DAHDI_FLAG_HDLC56; + } + + fasthdlc_init(&chan->rxhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + fasthdlc_init(&chan->txhdlc, (chan->flags & DAHDI_FLAG_HDLC56) ? FASTHDLC_MODE_56 : FASTHDLC_MODE_64); + break; + case DAHDI_ECHOCANCEL_PARAMS: + { + struct dahdi_echocanparams ecp; + + if (!(chan->flags & DAHDI_FLAG_AUDIO)) + return -EINVAL; + if (copy_from_user(&ecp, (struct dahdi_echocanparams *) data, sizeof(ecp))) + return -EFAULT; + data += sizeof(ecp); + if ((ret = ioctl_echocancel(chan, &ecp, (void *) data))) + return ret; + break; + } + case DAHDI_ECHOCANCEL: + { + struct dahdi_echocanparams ecp; + + if (!(chan->flags & DAHDI_FLAG_AUDIO)) + return -EINVAL; + get_user(j, (int *) data); + ecp.tap_length = j; + ecp.param_count = 0; + if ((ret = ioctl_echocancel(chan, &ecp, NULL))) + return ret; + break; + } + case DAHDI_ECHOTRAIN: + get_user(j, (int *)data); /* get pre-training time from user */ + if ((j < 0) || (j >= DAHDI_MAX_PRETRAINING)) + return -EINVAL; + j <<= 3; + spin_lock_irqsave(&chan->lock, flags); + if (chan->ec_state) { + /* Start pretraining stage */ + if (chan->ec_state->ops->echocan_traintap) { + chan->ec_state->status.mode = ECHO_MODE_PRETRAINING; + chan->ec_state->status.pretrain_timer = j; + } + spin_unlock_irqrestore(&chan->lock, flags); + } else { + spin_unlock_irqrestore(&chan->lock, flags); + return -EINVAL; + } + break; + case DAHDI_ECHOCANCEL_FAX_MODE: + if (!chan->ec_state) { + return -EINVAL; + } else { + get_user(j, (int *) data); + spin_lock_irqsave(&chan->lock, flags); + set_echocan_fax_mode(chan, chan->channo, "ioctl", j ? 1 : 0); + spin_unlock_irqrestore(&chan->lock, flags); + } + break; + case DAHDI_SETTXBITS: + if (chan->sig != DAHDI_SIG_CAS) + return -EINVAL; + get_user(j,(int *)data); + dahdi_cas_setbits(chan, j); + break; + case DAHDI_GETRXBITS: + put_user(chan->rxsig, (int *)data); + break; + case DAHDI_LOOPBACK: + get_user(j, (int *)data); + spin_lock_irqsave(&chan->lock, flags); + if (j) + chan->flags |= DAHDI_FLAG_LOOPED; + else + chan->flags &= ~DAHDI_FLAG_LOOPED; + spin_unlock_irqrestore(&chan->lock, flags); + break; + case DAHDI_HOOK: + get_user(j,(int *)data); + if (chan->flags & DAHDI_FLAG_CLEAR) + return -EINVAL; + if (chan->sig == DAHDI_SIG_CAS) + return -EINVAL; + /* if no span, just do nothing */ + if (!chan->span) return(0); + spin_lock_irqsave(&chan->lock, flags); + /* if dialing, stop it */ + chan->curtone = NULL; + chan->dialing = 0; + chan->txdialbuf[0] = '\0'; + chan->tonep = 0; + chan->pdialcount = 0; + spin_unlock_irqrestore(&chan->lock, flags); + if (chan->span->flags & DAHDI_FLAG_RBS) { + switch (j) { + case DAHDI_ONHOOK: + spin_lock_irqsave(&chan->lock, flags); + dahdi_hangup(chan); + spin_unlock_irqrestore(&chan->lock, flags); + break; + case DAHDI_OFFHOOK: + spin_lock_irqsave(&chan->lock, flags); + if ((chan->txstate == DAHDI_TXSTATE_KEWL) || + (chan->txstate == DAHDI_TXSTATE_AFTERKEWL)) { + spin_unlock_irqrestore(&chan->lock, flags); + return -EBUSY; + } + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_DEBOUNCE, chan->debouncetime); + spin_unlock_irqrestore(&chan->lock, flags); + break; + case DAHDI_RING: + case DAHDI_START: + spin_lock_irqsave(&chan->lock, flags); + if (!chan->curzone) { + spin_unlock_irqrestore(&chan->lock, flags); + module_printk(KERN_WARNING, "Cannot start tone until a tone zone is loaded.\n"); + return -ENODATA; + } + if (chan->txstate != DAHDI_TXSTATE_ONHOOK) { + spin_unlock_irqrestore(&chan->lock, flags); + return -EBUSY; + } + if (chan->sig & __DAHDI_SIG_FXO) { + ret = 0; + chan->cadencepos = 0; + ret = chan->ringcadence[0]; + dahdi_rbs_sethook(chan, DAHDI_TXSIG_START, DAHDI_TXSTATE_RINGON, ret); + } else + dahdi_rbs_sethook(chan, DAHDI_TXSIG_START, DAHDI_TXSTATE_START, chan->starttime); + spin_unlock_irqrestore(&chan->lock, flags); + if (file->f_flags & O_NONBLOCK) + return -EINPROGRESS; +#if 0 + rv = schluffen(&chan->txstateq); + if (rv) return rv; +#endif + break; + case DAHDI_WINK: + spin_lock_irqsave(&chan->lock, flags); + if (chan->txstate != DAHDI_TXSTATE_ONHOOK) { + spin_unlock_irqrestore(&chan->lock, flags); + return -EBUSY; + } + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_PREWINK, chan->prewinktime); + spin_unlock_irqrestore(&chan->lock, flags); + if (file->f_flags & O_NONBLOCK) + return -EINPROGRESS; + rv = schluffen(&chan->txstateq); + if (rv) return rv; + break; + case DAHDI_FLASH: + spin_lock_irqsave(&chan->lock, flags); + if (chan->txstate != DAHDI_TXSTATE_OFFHOOK) { + spin_unlock_irqrestore(&chan->lock, flags); + return -EBUSY; + } + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_PREFLASH, chan->preflashtime); + spin_unlock_irqrestore(&chan->lock, flags); + if (file->f_flags & O_NONBLOCK) + return -EINPROGRESS; + rv = schluffen(&chan->txstateq); + if (rv) return rv; + break; + case DAHDI_RINGOFF: + spin_lock_irqsave(&chan->lock, flags); + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_ONHOOK, 0); + spin_unlock_irqrestore(&chan->lock, flags); + break; + default: + return -EINVAL; + } + } else if (chan->span->sethook) { + if (chan->txhooksig != j) { + chan->txhooksig = j; + chan->span->sethook(chan, j); + } + } else + return -ENOSYS; + break; +#ifdef CONFIG_DAHDI_PPP + case PPPIOCGCHAN: + if (chan->flags & DAHDI_FLAG_PPP) + return put_user(ppp_channel_index(chan->ppp), (int *)data) ? -EFAULT : 0; + else + return -EINVAL; + break; + case PPPIOCGUNIT: + if (chan->flags & DAHDI_FLAG_PPP) + return put_user(ppp_unit_number(chan->ppp), (int *)data) ? -EFAULT : 0; + else + return -EINVAL; + break; +#endif + default: + return dahdi_chanandpseudo_ioctl(inode, file, cmd, data, unit); + } + return 0; +} + +static int dahdi_prechan_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data, int unit) +{ + struct dahdi_chan *chan = file->private_data; + int channo; + int res; + + if (chan) { + module_printk(KERN_NOTICE, "Huh? Prechan already has private data??\n"); + } + switch(cmd) { + case DAHDI_SPECIFY: + get_user(channo,(int *)data); + if (channo < 1) + return -EINVAL; + if (channo > DAHDI_MAX_CHANNELS) + return -EINVAL; + res = dahdi_specchan_open(inode, file, channo); + if (!res) { + /* Setup the pointer for future stuff */ + chan = chans[channo]; + file->private_data = chan; + /* Return success */ + return 0; + } + return res; + default: + return -ENOSYS; + } + return 0; +} + +static int dahdi_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long data) +{ + int unit = UNIT(file); + struct dahdi_chan *chan; + struct dahdi_timer *timer; + + if (!unit) + return dahdi_ctl_ioctl(inode, file, cmd, data); + + if (unit == 250) { + /* dahdi_transcode should have updated the file_operations on + * this file object on open, so we shouldn't be here. */ + WARN_ON(1); + return -EFAULT; + } + + if (unit == 253) { + timer = file->private_data; + if (timer) + return dahdi_timer_ioctl(inode, file, cmd, data, timer); + else + return -EINVAL; + } + if (unit == 254) { + chan = file->private_data; + if (chan) + return dahdi_chan_ioctl(inode, file, cmd, data, chan->channo); + else + return dahdi_prechan_ioctl(inode, file, cmd, data, unit); + } + if (unit == 255) { + chan = file->private_data; + if (!chan) { + module_printk(KERN_NOTICE, "No pseudo channel structure to read?\n"); + return -EINVAL; + } + return dahdi_chanandpseudo_ioctl(inode, file, cmd, data, chan->channo); + } + return dahdi_chan_ioctl(inode, file, cmd, data, unit); +} + +int dahdi_register(struct dahdi_span *span, int prefmaster) +{ + int x; + + if (!span) + return -EINVAL; + + if (test_bit(DAHDI_FLAGBIT_REGISTERED, &span->flags)) { + module_printk(KERN_ERR, "Span %s already appears to be registered\n", span->name); + return -EBUSY; + } + + for (x = 1; x < maxspans; x++) { + if (spans[x] == span) { + module_printk(KERN_ERR, "Span %s already in list\n", span->name); + return -EBUSY; + } + } + + for (x = 1; x < DAHDI_MAX_SPANS; x++) { + if (!spans[x]) + break; + } + + if (x < DAHDI_MAX_SPANS) { + spans[x] = span; + if (maxspans < x + 1) + maxspans = x + 1; + } else { + module_printk(KERN_ERR, "Too many DAHDI spans registered\n"); + return -EBUSY; + } + + set_bit(DAHDI_FLAGBIT_REGISTERED, &span->flags); + span->spanno = x; + + spin_lock_init(&span->lock); + + if (!span->deflaw) { + module_printk(KERN_NOTICE, "Span %s didn't specify default law. " + "Assuming mulaw, please fix driver!\n", span->name); + span->deflaw = DAHDI_LAW_MULAW; + } + + for (x = 0; x < span->channels; x++) { + span->chans[x]->span = span; + dahdi_chan_reg(span->chans[x]); + } + +#ifdef CONFIG_PROC_FS + { + char tempfile[17]; + snprintf(tempfile, sizeof(tempfile), "dahdi/%d", span->spanno); + proc_entries[span->spanno] = create_proc_read_entry(tempfile, 0444, + NULL, dahdi_proc_read, (int *) (long) span->spanno); + } +#endif + + for (x = 0; x < span->channels; x++) { + if (span->chans[x]->channo < 250) { + char chan_name[32]; + snprintf(chan_name, sizeof(chan_name), "dahdi!%d", + span->chans[x]->channo); + CLASS_DEV_CREATE(dahdi_class, MKDEV(DAHDI_MAJOR, + span->chans[x]->channo), NULL, chan_name); + } + } + + if (debug) { + module_printk(KERN_NOTICE, "Registered Span %d ('%s') with " + "%d channels\n", span->spanno, span->name, span->channels); + } + + if (!master || prefmaster) { + master = span; + if (debug) { + module_printk(KERN_NOTICE, "Span ('%s') is new master\n", + span->name); + } + } + + return 0; +} + +int dahdi_unregister(struct dahdi_span *span) +{ + int x; + int new_maxspans; + static struct dahdi_span *new_master; + +#ifdef CONFIG_PROC_FS + char tempfile[17]; +#endif /* CONFIG_PROC_FS */ + + if (!test_bit(DAHDI_FLAGBIT_REGISTERED, &span->flags)) { + module_printk(KERN_ERR, "Span %s does not appear to be registered\n", span->name); + return -1; + } + /* Shutdown the span if it's running */ + if (span->flags & DAHDI_FLAG_RUNNING) + if (span->shutdown) + span->shutdown(span); + + if (spans[span->spanno] != span) { + module_printk(KERN_ERR, "Span %s has spanno %d which is something else\n", span->name, span->spanno); + return -1; + } + if (debug) + module_printk(KERN_NOTICE, "Unregistering Span '%s' with %d channels\n", span->name, span->channels); +#ifdef CONFIG_PROC_FS + snprintf(tempfile, sizeof(tempfile)-1, "dahdi/%d", span->spanno); + remove_proc_entry(tempfile, NULL); +#endif /* CONFIG_PROC_FS */ + + for (x = 0; x < span->channels; x++) { + if (span->chans[x]->channo < 250) + CLASS_DEV_DESTROY(dahdi_class, MKDEV(DAHDI_MAJOR, span->chans[x]->channo)); + } + + spans[span->spanno] = NULL; + span->spanno = 0; + clear_bit(DAHDI_FLAGBIT_REGISTERED, &span->flags); + for (x=0;xchannels;x++) + dahdi_chan_unreg(span->chans[x]); + new_maxspans = 0; + new_master = master; /* FIXME: locking */ + if (master == span) + new_master = NULL; + for (x=1;xname: "no master"); + master = new_master; + + return 0; +} + +/* +** This routine converts from linear to ulaw +** +** Craig Reese: IDA/Supercomputing Research Center +** Joe Campbell: Department of Defense +** 29 September 1989 +** +** References: +** 1) CCITT Recommendation G.711 (very difficult to follow) +** 2) "A New Digital Technique for Implementation of Any +** Continuous PCM Companding Law," Villeret, Michel, +** et al. 1973 IEEE Int. Conf. on Communications, Vol 1, +** 1973, pg. 11.12-11.17 +** 3) MIL-STD-188-113,"Interoperability and Performance Standards +** for Analog-to_Digital Conversion Techniques," +** 17 February 1987 +** +** Input: Signed 16 bit linear sample +** Output: 8 bit ulaw sample +*/ + +#define ZEROTRAP /* turn on the trap as per the MIL-STD */ +#define BIAS 0x84 /* define the add-in bias for 16 bit samples */ +#define CLIP 32635 + +#ifdef CONFIG_CALC_XLAW +unsigned char +#else +static unsigned char __init +#endif +__dahdi_lineartoulaw(short sample) +{ + static int exp_lut[256] = {0,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7}; + int sign, exponent, mantissa; + unsigned char ulawbyte; + + /* Get the sample into sign-magnitude. */ + sign = (sample >> 8) & 0x80; /* set aside the sign */ + if (sign != 0) sample = -sample; /* get magnitude */ + if (sample > CLIP) sample = CLIP; /* clip the magnitude */ + + /* Convert from 16 bit linear to ulaw. */ + sample = sample + BIAS; + exponent = exp_lut[(sample >> 7) & 0xFF]; + mantissa = (sample >> (exponent + 3)) & 0x0F; + ulawbyte = ~(sign | (exponent << 4) | mantissa); +#ifdef ZEROTRAP + if (ulawbyte == 0) ulawbyte = 0x02; /* optional CCITT trap */ +#endif + if (ulawbyte == 0xff) ulawbyte = 0x7f; /* never return 0xff */ + return(ulawbyte); +} + +#define AMI_MASK 0x55 + +#ifdef CONFIG_CALC_XLAW +unsigned char +#else +static inline unsigned char __init +#endif +__dahdi_lineartoalaw (short linear) +{ + int mask; + int seg; + int pcm_val; + static int seg_end[8] = + { + 0xFF, 0x1FF, 0x3FF, 0x7FF, 0xFFF, 0x1FFF, 0x3FFF, 0x7FFF + }; + + pcm_val = linear; + if (pcm_val >= 0) + { + /* Sign (7th) bit = 1 */ + mask = AMI_MASK | 0x80; + } + else + { + /* Sign bit = 0 */ + mask = AMI_MASK; + pcm_val = -pcm_val; + } + + /* Convert the scaled magnitude to segment number. */ + for (seg = 0; seg < 8; seg++) + { + if (pcm_val <= seg_end[seg]) + break; + } + /* Combine the sign, segment, and quantization bits. */ + return ((seg << 4) | ((pcm_val >> ((seg) ? (seg + 3) : 4)) & 0x0F)) ^ mask; +} +/*- End of function --------------------------------------------------------*/ + +static inline short int __init alaw2linear (uint8_t alaw) +{ + int i; + int seg; + + alaw ^= AMI_MASK; + i = ((alaw & 0x0F) << 4); + seg = (((int) alaw & 0x70) >> 4); + if (seg) + i = (i + 0x100) << (seg - 1); + return (short int) ((alaw & 0x80) ? i : -i); +} +/*- End of function --------------------------------------------------------*/ +static void __init dahdi_conv_init(void) +{ + int i; + + /* + * Set up mu-law conversion table + */ + for(i = 0;i < 256;i++) + { + short mu,e,f,y; + static short etab[]={0,132,396,924,1980,4092,8316,16764}; + + mu = 255-i; + e = (mu & 0x70)/16; + f = mu & 0x0f; + y = f * (1 << (e + 3)); + y += etab[e]; + if (mu & 0x80) y = -y; + __dahdi_mulaw[i] = y; + __dahdi_alaw[i] = alaw2linear(i); + /* Default (0.0 db) gain table */ + defgain[i] = i; + } +#ifndef CONFIG_CALC_XLAW + /* set up the reverse (mu-law) conversion table */ + for(i = -32768; i < 32768; i += 4) + { + __dahdi_lin2mu[((unsigned short)(short)i) >> 2] = __dahdi_lineartoulaw(i); + __dahdi_lin2a[((unsigned short)(short)i) >> 2] = __dahdi_lineartoalaw(i); + } +#endif +} + +static inline void __dahdi_process_getaudio_chunk(struct dahdi_chan *ss, unsigned char *txb) +{ + /* We transmit data from our master channel */ + /* Called with ss->lock held */ + struct dahdi_chan *ms = ss->master; + /* Linear representation */ + short getlin[DAHDI_CHUNKSIZE], k[DAHDI_CHUNKSIZE]; + int x; + + /* Okay, now we've got something to transmit */ + for (x=0;xec_state && (ms->ec_state->status.mode == ECHO_MODE_ACTIVE) && !ms->ec_state->features.CED_tx_detect) { + for (x = 0; x < DAHDI_CHUNKSIZE; x++) { + if (echo_can_disable_detector_update(&ms->ec_state->txecdis, getlin[x])) { + set_echocan_fax_mode(ms, ss->channo, "CED tx detected", 1); + dahdi_qevent_nolock(ms, DAHDI_EVENT_TX_CED_DETECTED); + break; + } + } + } + + if ((!ms->confmute && !ms->dialing) || (ms->flags & DAHDI_FLAG_PSEUDO)) { + /* Handle conferencing on non-clear channel and non-HDLC channels */ + switch(ms->confmode & DAHDI_CONF_MODE_MASK) { + case DAHDI_CONF_NORMAL: + /* Do nuffin */ + break; + case DAHDI_CONF_MONITOR: /* Monitor a channel's rx mode */ + /* if a pseudo-channel, ignore */ + if (ms->flags & DAHDI_FLAG_PSEUDO) break; + /* Add monitored channel */ + if (chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO) { + ACSS(getlin, chans[ms->confna]->getlin); + } else { + ACSS(getlin, chans[ms->confna]->putlin); + } + for (x=0;xflags & DAHDI_FLAG_PSEUDO) break; + /* Add monitored channel */ + if (chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO) { + ACSS(getlin, chans[ms->confna]->putlin); + } else { + ACSS(getlin, chans[ms->confna]->getlin); + } + + for (x=0;xflags & DAHDI_FLAG_PSEUDO) break; + ACSS(getlin, chans[ms->confna]->putlin); + ACSS(getlin, chans[ms->confna]->getlin); + for (x=0;xflags & DAHDI_FLAG_PSEUDO) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(getlin, chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO ? + chans[ms->confna]->readchunkpreec : chans[ms->confna]->putlin); + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + txb[x] = DAHDI_LIN2X(getlin[x], ms); + + break; + case DAHDI_CONF_MONITOR_TX_PREECHO: /* Monitor a channel's tx mode */ + /* if a pseudo-channel, ignore */ + if (ms->flags & DAHDI_FLAG_PSEUDO) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(getlin, chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO ? + chans[ms->confna]->putlin : chans[ms->confna]->readchunkpreec); + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + txb[x] = DAHDI_LIN2X(getlin[x], ms); + + break; + case DAHDI_CONF_MONITORBOTH_PREECHO: /* monitor a channel's rx and tx mode */ + /* if a pseudo-channel, ignore */ + if (ms->flags & DAHDI_FLAG_PSEUDO) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + ACSS(getlin, chans[ms->confna]->putlin); + ACSS(getlin, chans[ms->confna]->readchunkpreec); + + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + txb[x] = DAHDI_LIN2X(getlin[x], ms); + + break; + case DAHDI_CONF_REALANDPSEUDO: + /* This strange mode takes the transmit buffer and + puts it on the conference, minus its last sample, + then outputs from the conference minus the + real channel's last sample. */ + /* if to talk on conf */ + if (ms->confmode & DAHDI_CONF_PSEUDO_TALKER) { + /* Store temp value */ + memcpy(k, getlin, DAHDI_CHUNKSIZE * sizeof(short)); + /* Add conf value */ + ACSS(k, conf_sums_next[ms->_confn]); + /* save last one */ + memcpy(ms->conflast2, ms->conflast1, DAHDI_CHUNKSIZE * sizeof(short)); + memcpy(ms->conflast1, k, DAHDI_CHUNKSIZE * sizeof(short)); + /* get amount actually added */ + SCSS(ms->conflast1, conf_sums_next[ms->_confn]); + /* Really add in new value */ + ACSS(conf_sums_next[ms->_confn], ms->conflast1); + } else { + memset(ms->conflast1, 0, DAHDI_CHUNKSIZE * sizeof(short)); + memset(ms->conflast2, 0, DAHDI_CHUNKSIZE * sizeof(short)); + } + memset(getlin, 0, DAHDI_CHUNKSIZE * sizeof(short)); + txb[0] = DAHDI_LIN2X(0, ms); + memset(txb + 1, txb[0], DAHDI_CHUNKSIZE - 1); + /* fall through to normal conf mode */ + case DAHDI_CONF_CONF: /* Normal conference mode */ + if (ms->flags & DAHDI_FLAG_PSEUDO) /* if pseudo-channel */ + { + /* if to talk on conf */ + if (ms->confmode & DAHDI_CONF_TALKER) { + /* Store temp value */ + memcpy(k, getlin, DAHDI_CHUNKSIZE * sizeof(short)); + /* Add conf value */ + ACSS(k, conf_sums[ms->_confn]); + /* get amount actually added */ + memcpy(ms->conflast, k, DAHDI_CHUNKSIZE * sizeof(short)); + SCSS(ms->conflast, conf_sums[ms->_confn]); + /* Really add in new value */ + ACSS(conf_sums[ms->_confn], ms->conflast); + memcpy(ms->getlin, getlin, DAHDI_CHUNKSIZE * sizeof(short)); + } else { + memset(ms->conflast, 0, DAHDI_CHUNKSIZE * sizeof(short)); + memcpy(getlin, ms->getlin, DAHDI_CHUNKSIZE * sizeof(short)); + } + txb[0] = DAHDI_LIN2X(0, ms); + memset(txb + 1, txb[0], DAHDI_CHUNKSIZE - 1); + break; + } + /* fall through */ + case DAHDI_CONF_CONFMON: /* Conference monitor mode */ + if (ms->confmode & DAHDI_CONF_LISTENER) { + /* Subtract out last sample written to conf */ + SCSS(getlin, ms->conflast); + /* Add in conference */ + ACSS(getlin, conf_sums[ms->_confn]); + } + for (x=0;x_confn], getlin); + /* Start with silence */ + memset(getlin, 0, DAHDI_CHUNKSIZE * sizeof(short)); + /* If a listener on the conf... */ + if (ms->confmode & DAHDI_CONF_LISTENER) { + /* Subtract last value written */ + SCSS(getlin, ms->conflast); + /* Add in conf */ + ACSS(getlin, conf_sums[ms->_confn]); + } + for (x=0;xconfna]) + break; + if (chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO) { + if (ms->ec_state) { + for (x=0;xconfna]->getlin[x], ms); + } else { + memcpy(txb, chans[ms->confna]->getraw, DAHDI_CHUNKSIZE); + } + } else { + if (ms->ec_state) { + for (x=0;xconfna]->putlin[x], ms); + } else { + memcpy(txb, chans[ms->confna]->putraw, DAHDI_CHUNKSIZE); + } + } + for (x=0;xconfmute || (ms->ec_state && (ms->ec_state->status.mode) & __ECHO_MODE_MUTE)) { + txb[0] = DAHDI_LIN2X(0, ms); + memset(txb + 1, txb[0], DAHDI_CHUNKSIZE - 1); + if (ms->ec_state && (ms->ec_state->status.mode == ECHO_MODE_STARTTRAINING)) { + /* Transmit impulse now */ + txb[0] = DAHDI_LIN2X(16384, ms); + ms->ec_state->status.mode = ECHO_MODE_AWAITINGECHO; + } + } + /* save value from last chunk */ + memcpy(ms->getlin_lastchunk, ms->getlin, DAHDI_CHUNKSIZE * sizeof(short)); + /* save value from current */ + memcpy(ms->getlin, getlin, DAHDI_CHUNKSIZE * sizeof(short)); + /* save value from current */ + memcpy(ms->getraw, txb, DAHDI_CHUNKSIZE); + /* if to make tx tone */ + if (ms->v1_1 || ms->v2_1 || ms->v3_1) + { + for (x=0;xtxgain[txb[x]]; +} + +static inline void __dahdi_getbuf_chunk(struct dahdi_chan *ss, unsigned char *txb) +{ + /* Called with ss->lock held */ + /* We transmit data from our master channel */ + struct dahdi_chan *ms = ss->master; + /* Buffer we're using */ + unsigned char *buf; + /* Old buffer number */ + int oldbuf; + /* Linear representation */ + int getlin; + /* How many bytes we need to process */ + int bytes = DAHDI_CHUNKSIZE, left; + int x; + + /* Let's pick something to transmit. First source to + try is our write-out buffer. Always check it first because + its our 'fast path' for whatever that's worth. */ + while(bytes) { + if ((ms->outwritebuf > -1) && !ms->txdisable) { + buf= ms->writebuf[ms->outwritebuf]; + left = ms->writen[ms->outwritebuf] - ms->writeidx[ms->outwritebuf]; + if (left > bytes) + left = bytes; + if (ms->flags & DAHDI_FLAG_HDLC) { + /* If this is an HDLC channel we only send a byte of + HDLC. */ + for(x=0;xtxhdlc)) + /* Load a byte of data only if needed */ + fasthdlc_tx_load_nocheck(&ms->txhdlc, buf[ms->writeidx[ms->outwritebuf]++]); + *(txb++) = fasthdlc_tx_run_nocheck(&ms->txhdlc); + } + bytes -= left; + } else { + memcpy(txb, buf + ms->writeidx[ms->outwritebuf], left); + ms->writeidx[ms->outwritebuf]+=left; + txb += left; + bytes -= left; + } + /* Check buffer status */ + if (ms->writeidx[ms->outwritebuf] >= ms->writen[ms->outwritebuf]) { + /* We've reached the end of our buffer. Go to the next. */ + oldbuf = ms->outwritebuf; + /* Clear out write index and such */ + ms->writeidx[oldbuf] = 0; + ms->outwritebuf = (ms->outwritebuf + 1) % ms->numbufs; + + if (!(ms->flags & DAHDI_FLAG_MTP2)) { + ms->writen[oldbuf] = 0; + if (ms->outwritebuf == ms->inwritebuf) { + /* Whoopsies, we're run out of buffers. Mark ours + as -1 and wait for the filler to notify us that + there is something to write */ + ms->outwritebuf = -1; + if (ms->iomask & (DAHDI_IOMUX_WRITE | DAHDI_IOMUX_WRITEEMPTY)) + wake_up_interruptible(&ms->eventbufq); + /* If we're only supposed to start when full, disable the transmitter */ + if ((ms->txbufpolicy == DAHDI_POLICY_WHEN_FULL) || + (ms->txbufpolicy == DAHDI_POLICY_HALF_FULL)) + ms->txdisable = 1; + } + } else { + if (ms->outwritebuf == ms->inwritebuf) { + ms->outwritebuf = oldbuf; + if (ms->iomask & (DAHDI_IOMUX_WRITE | DAHDI_IOMUX_WRITEEMPTY)) + wake_up_interruptible(&ms->eventbufq); + /* If we're only supposed to start when full, disable the transmitter */ + if ((ms->txbufpolicy == DAHDI_POLICY_WHEN_FULL) || + (ms->txbufpolicy == DAHDI_POLICY_HALF_FULL)) + ms->txdisable = 1; + } + } + if (ms->inwritebuf < 0) { + /* The filler doesn't have a place to put data. Now + that we're done with this buffer, notify them. */ + ms->inwritebuf = oldbuf; + } +/* In the very orignal driver, it was quite well known to me (Jim) that there +was a possibility that a channel sleeping on a write block needed to +be potentially woken up EVERY time a buffer was emptied, not just on the first +one, because if only done on the first one there is a slight timing potential +of missing the wakeup (between where it senses the (lack of) active condition +(with interrupts disabled) and where it does the sleep (interrupts enabled) +in the read or iomux call, etc). That is why the write and iomux calls start +with an infinite loop that gets broken out of upon an active condition, +otherwise keeps sleeping and looking. The part in this code got "optimized" +out in the later versions, and is put back now. */ + if (!(ms->flags & (DAHDI_FLAG_NETDEV | DAHDI_FLAG_PPP))) { + wake_up_interruptible(&ms->writebufq); + wake_up_interruptible(&ms->sel); + if (ms->iomask & DAHDI_IOMUX_WRITE) + wake_up_interruptible(&ms->eventbufq); + } + /* Transmit a flag if this is an HDLC channel */ + if (ms->flags & DAHDI_FLAG_HDLC) + fasthdlc_tx_frame_nocheck(&ms->txhdlc); +#ifdef CONFIG_DAHDI_NET + if (ms->flags & DAHDI_FLAG_NETDEV) + netif_wake_queue(ztchan_to_dev(ms)); +#endif +#ifdef CONFIG_DAHDI_PPP + if (ms->flags & DAHDI_FLAG_PPP) { + ms->do_ppp_wakeup = 1; + tasklet_schedule(&ms->ppp_calls); + } +#endif + } + } else if (ms->curtone && !(ms->flags & DAHDI_FLAG_PSEUDO)) { + left = ms->curtone->tonesamples - ms->tonep; + if (left > bytes) + left = bytes; + for (x=0;xts, ms->curtone); + *(txb++) = DAHDI_LIN2X(getlin, ms); + } + ms->tonep+=left; + bytes -= left; + if (ms->tonep >= ms->curtone->tonesamples) { + struct dahdi_tone *last; + /* Go to the next sample of the tone */ + ms->tonep = 0; + last = ms->curtone; + ms->curtone = ms->curtone->next; + if (!ms->curtone) { + /* No more tones... Is this dtmf or mf? If so, go to the next digit */ + if (ms->dialing) + __do_dtmf(ms); + } else { + if (last != ms->curtone) + dahdi_init_tone_state(&ms->ts, ms->curtone); + } + } + } else if (ms->flags & DAHDI_FLAG_LOOPED) { + for (x = 0; x < bytes; x++) + txb[x] = ms->readchunk[x]; + bytes = 0; + } else if (ms->flags & DAHDI_FLAG_HDLC) { + for (x=0;xtxhdlc)) + fasthdlc_tx_frame_nocheck(&ms->txhdlc); + *(txb++) = fasthdlc_tx_run_nocheck(&ms->txhdlc); + } + bytes = 0; + } else if (ms->flags & DAHDI_FLAG_CLEAR) { + /* Clear channels that are idle in audio mode need + to send silence; in non-audio mode, always send 0xff + so stupid switches won't consider the channel active + */ + if (ms->flags & DAHDI_FLAG_AUDIO) { + memset(txb, DAHDI_LIN2X(0, ms), bytes); + } else { + memset(txb, 0xFF, bytes); + } + bytes = 0; + } else { + memset(txb, DAHDI_LIN2X(0, ms), bytes); /* Lastly we use silence on telephony channels */ + bytes = 0; + } + } +} + +static inline void rbs_itimer_expire(struct dahdi_chan *chan) +{ + /* the only way this could have gotten here, is if a channel + went onf hook longer then the wink or flash detect timeout */ + /* Called with chan->lock held */ + switch(chan->sig) + { + case DAHDI_SIG_FXOLS: /* if FXO, its definitely on hook */ + case DAHDI_SIG_FXOGS: + case DAHDI_SIG_FXOKS: + __qevent(chan,DAHDI_EVENT_ONHOOK); + chan->gotgs = 0; + break; +#if defined(EMFLASH) || defined(EMPULSE) + case DAHDI_SIG_EM: + case DAHDI_SIG_EM_E1: + if (chan->rxhooksig == DAHDI_RXSIG_ONHOOK) { + __qevent(chan,DAHDI_EVENT_ONHOOK); + break; + } + __qevent(chan,DAHDI_EVENT_RINGOFFHOOK); + break; +#endif +#ifdef FXSFLASH + case DAHDI_SIG_FXSKS: + if (chan->rxhooksig == DAHDI_RXSIG_ONHOOK) { + __qevent(chan, DAHDI_EVENT_ONHOOK); + break; + } +#endif + /* fall thru intentionally */ + default: /* otherwise, its definitely off hook */ + __qevent(chan,DAHDI_EVENT_RINGOFFHOOK); + break; + } +} + +static inline void __rbs_otimer_expire(struct dahdi_chan *chan) +{ + int len = 0; + /* Called with chan->lock held */ + + chan->otimer = 0; + /* Move to the next timer state */ + switch(chan->txstate) { + case DAHDI_TXSTATE_RINGOFF: + /* Turn on the ringer now that the silent time has passed */ + ++chan->cadencepos; + if (chan->cadencepos >= DAHDI_MAX_CADENCE) + chan->cadencepos = chan->firstcadencepos; + len = chan->ringcadence[chan->cadencepos]; + + if (!len) { + chan->cadencepos = chan->firstcadencepos; + len = chan->ringcadence[chan->cadencepos]; + } + + dahdi_rbs_sethook(chan, DAHDI_TXSIG_START, DAHDI_TXSTATE_RINGON, len); + __qevent(chan, DAHDI_EVENT_RINGERON); + break; + + case DAHDI_TXSTATE_RINGON: + /* Turn off the ringer now that the loud time has passed */ + ++chan->cadencepos; + if (chan->cadencepos >= DAHDI_MAX_CADENCE) + chan->cadencepos = 0; + len = chan->ringcadence[chan->cadencepos]; + + if (!len) { + chan->cadencepos = 0; + len = chan->curzone->ringcadence[chan->cadencepos]; + } + + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_RINGOFF, len); + __qevent(chan, DAHDI_EVENT_RINGEROFF); + break; + + case DAHDI_TXSTATE_START: + /* If we were starting, go off hook now ready to debounce */ + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_AFTERSTART, DAHDI_AFTERSTART_TIME); + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_PREWINK: + /* Actually wink */ + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_WINK, chan->winktime); + break; + + case DAHDI_TXSTATE_WINK: + /* Wink complete, go on hook and stabalize */ + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_ONHOOK, 0); + if (chan->file && (chan->file->f_flags & O_NONBLOCK)) + __qevent(chan, DAHDI_EVENT_HOOKCOMPLETE); + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_PREFLASH: + /* Actually flash */ + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_FLASH, chan->flashtime); + break; + + case DAHDI_TXSTATE_FLASH: + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_OFFHOOK, 0); + if (chan->file && (chan->file->f_flags & O_NONBLOCK)) + __qevent(chan, DAHDI_EVENT_HOOKCOMPLETE); + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_DEBOUNCE: + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_OFFHOOK, 0); + /* See if we've gone back on hook */ + if ((chan->rxhooksig == DAHDI_RXSIG_ONHOOK) && (chan->rxflashtime > 2)) + chan->itimerset = chan->itimer = chan->rxflashtime * DAHDI_CHUNKSIZE; + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_AFTERSTART: + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_OFFHOOK, 0); + if (chan->file && (chan->file->f_flags & O_NONBLOCK)) + __qevent(chan, DAHDI_EVENT_HOOKCOMPLETE); + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_KEWL: + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, DAHDI_TXSTATE_AFTERKEWL, DAHDI_AFTERKEWLTIME); + if (chan->file && (chan->file->f_flags & O_NONBLOCK)) + __qevent(chan, DAHDI_EVENT_HOOKCOMPLETE); + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_AFTERKEWL: + if (chan->kewlonhook) { + __qevent(chan,DAHDI_EVENT_ONHOOK); + } + chan->txstate = DAHDI_TXSTATE_ONHOOK; + chan->gotgs = 0; + break; + + case DAHDI_TXSTATE_PULSEBREAK: + dahdi_rbs_sethook(chan, DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_PULSEMAKE, + chan->pulsemaketime); + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_PULSEMAKE: + if (chan->pdialcount) + chan->pdialcount--; + if (chan->pdialcount) + { + dahdi_rbs_sethook(chan, DAHDI_TXSIG_ONHOOK, + DAHDI_TXSTATE_PULSEBREAK, chan->pulsebreaktime); + break; + } + chan->txstate = DAHDI_TXSTATE_PULSEAFTER; + chan->otimer = chan->pulseaftertime * DAHDI_CHUNKSIZE; + wake_up_interruptible(&chan->txstateq); + break; + + case DAHDI_TXSTATE_PULSEAFTER: + chan->txstate = DAHDI_TXSTATE_OFFHOOK; + __do_dtmf(chan); + wake_up_interruptible(&chan->txstateq); + break; + + default: + break; + } +} + +static void __dahdi_hooksig_pvt(struct dahdi_chan *chan, enum dahdi_rxsig rxsig) +{ + + /* State machines for receive hookstate transitions + called with chan->lock held */ + + if ((chan->rxhooksig) == rxsig) return; + + if ((chan->flags & DAHDI_FLAG_SIGFREEZE)) return; + + chan->rxhooksig = rxsig; +#ifdef RINGBEGIN + if ((chan->sig & __DAHDI_SIG_FXS) && (rxsig == DAHDI_RXSIG_RING) && + (!chan->ringdebtimer)) + __qevent(chan,DAHDI_EVENT_RINGBEGIN); +#endif + switch(chan->sig) { + case DAHDI_SIG_EM: /* E and M */ + case DAHDI_SIG_EM_E1: + switch(rxsig) { + case DAHDI_RXSIG_OFFHOOK: /* went off hook */ + /* The interface is going off hook */ +#ifdef EMFLASH + if (chan->itimer) + { + __qevent(chan,DAHDI_EVENT_WINKFLASH); + chan->itimerset = chan->itimer = 0; + break; + } +#endif +#ifdef EMPULSE + if (chan->itimer) /* if timer still running */ + { + int plen = chan->itimerset - chan->itimer; + if (plen <= DAHDI_MAXPULSETIME) + { + if (plen >= DAHDI_MINPULSETIME) + { + chan->pulsecount++; + + chan->pulsetimer = DAHDI_PULSETIMEOUT; + chan->itimerset = chan->itimer = 0; + if (chan->pulsecount == 1) + __qevent(chan,DAHDI_EVENT_PULSE_START); + } + } + break; + } +#endif + /* set wink timer */ + chan->itimerset = chan->itimer = chan->rxwinktime * DAHDI_CHUNKSIZE; + break; + case DAHDI_RXSIG_ONHOOK: /* went on hook */ + /* This interface is now going on hook. + Check for WINK, etc */ + if (chan->itimer) + __qevent(chan,DAHDI_EVENT_WINKFLASH); +#if defined(EMFLASH) || defined(EMPULSE) + else { +#ifdef EMFLASH + chan->itimerset = chan->itimer = chan->rxflashtime * DAHDI_CHUNKSIZE; + +#else /* EMFLASH */ + chan->itimerset = chan->itimer = chan->rxwinktime * DAHDI_CHUNKSIZE; + +#endif /* EMFLASH */ + chan->gotgs = 0; + break; + } +#else /* EMFLASH || EMPULSE */ + else { + __qevent(chan,DAHDI_EVENT_ONHOOK); + chan->gotgs = 0; + } +#endif + chan->itimerset = chan->itimer = 0; + break; + default: + break; + } + break; + case DAHDI_SIG_FXSKS: /* FXS Kewlstart */ + /* ignore a bit error if loop not closed and stable */ + if (chan->txstate != DAHDI_TXSTATE_OFFHOOK) break; +#ifdef FXSFLASH + if (rxsig == DAHDI_RXSIG_ONHOOK) { + chan->itimer = DAHDI_FXSFLASHMAXTIME * DAHDI_CHUNKSIZE; + break; + } else if (rxsig == DAHDI_RXSIG_OFFHOOK) { + if (chan->itimer) { + /* did the offhook occur in the window? if not, ignore both events */ + if (chan->itimer <= ((DAHDI_FXSFLASHMAXTIME - DAHDI_FXSFLASHMINTIME) * DAHDI_CHUNKSIZE)) + __qevent(chan, DAHDI_EVENT_WINKFLASH); + } + chan->itimer = 0; + break; + } +#endif + /* fall through intentionally */ + case DAHDI_SIG_FXSGS: /* FXS Groundstart */ + if (rxsig == DAHDI_RXSIG_ONHOOK) { + chan->ringdebtimer = RING_DEBOUNCE_TIME; + chan->ringtrailer = 0; + if (chan->txstate != DAHDI_TXSTATE_DEBOUNCE) { + chan->gotgs = 0; + __qevent(chan,DAHDI_EVENT_ONHOOK); + } + } + break; + case DAHDI_SIG_FXOGS: /* FXO Groundstart */ + if (rxsig == DAHDI_RXSIG_START) { + /* if havent got gs, report it */ + if (!chan->gotgs) { + __qevent(chan,DAHDI_EVENT_RINGOFFHOOK); + chan->gotgs = 1; + } + } + /* fall through intentionally */ + case DAHDI_SIG_FXOLS: /* FXO Loopstart */ + case DAHDI_SIG_FXOKS: /* FXO Kewlstart */ + switch(rxsig) { + case DAHDI_RXSIG_OFFHOOK: /* went off hook */ + /* if asserti ng ring, stop it */ + if (chan->txstate == DAHDI_TXSTATE_START) { + dahdi_rbs_sethook(chan,DAHDI_TXSIG_OFFHOOK, DAHDI_TXSTATE_AFTERSTART, DAHDI_AFTERSTART_TIME); + } + chan->kewlonhook = 0; +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Off hook on channel %d, itimer = %d, gotgs = %d\n", chan->channo, chan->itimer, chan->gotgs); +#endif + if (chan->itimer) /* if timer still running */ + { + int plen = chan->itimerset - chan->itimer; + if (plen <= DAHDI_MAXPULSETIME) + { + if (plen >= DAHDI_MINPULSETIME) + { + chan->pulsecount++; + chan->pulsetimer = DAHDI_PULSETIMEOUT; + chan->itimer = chan->itimerset; + if (chan->pulsecount == 1) + __qevent(chan,DAHDI_EVENT_PULSE_START); + } + } else + __qevent(chan,DAHDI_EVENT_WINKFLASH); + } else { + /* if havent got GS detect */ + if (!chan->gotgs) { + __qevent(chan,DAHDI_EVENT_RINGOFFHOOK); + chan->gotgs = 1; + chan->itimerset = chan->itimer = 0; + } + } + chan->itimerset = chan->itimer = 0; + break; + case DAHDI_RXSIG_ONHOOK: /* went on hook */ + /* if not during offhook debounce time */ + if ((chan->txstate != DAHDI_TXSTATE_DEBOUNCE) && + (chan->txstate != DAHDI_TXSTATE_KEWL) && + (chan->txstate != DAHDI_TXSTATE_AFTERKEWL)) { + chan->itimerset = chan->itimer = chan->rxflashtime * DAHDI_CHUNKSIZE; + } + if (chan->txstate == DAHDI_TXSTATE_KEWL) + chan->kewlonhook = 1; + break; + default: + break; + } + default: + break; + } +} + +void dahdi_hooksig(struct dahdi_chan *chan, enum dahdi_rxsig rxsig) +{ + /* skip if no change */ + unsigned long flags; + spin_lock_irqsave(&chan->lock, flags); + __dahdi_hooksig_pvt(chan,rxsig); + spin_unlock_irqrestore(&chan->lock, flags); +} + +void dahdi_rbsbits(struct dahdi_chan *chan, int cursig) +{ + unsigned long flags; + if (cursig == chan->rxsig) + return; + + if ((chan->flags & DAHDI_FLAG_SIGFREEZE)) return; + + spin_lock_irqsave(&chan->lock, flags); + switch(chan->sig) { + case DAHDI_SIG_FXOGS: /* FXO Groundstart */ + /* B-bit only matters for FXO GS */ + if (!(cursig & DAHDI_BBIT)) { + __dahdi_hooksig_pvt(chan, DAHDI_RXSIG_START); + break; + } + /* Fall through */ + case DAHDI_SIG_EM: /* E and M */ + case DAHDI_SIG_EM_E1: + case DAHDI_SIG_FXOLS: /* FXO Loopstart */ + case DAHDI_SIG_FXOKS: /* FXO Kewlstart */ + if (cursig & DAHDI_ABIT) /* off hook */ + __dahdi_hooksig_pvt(chan,DAHDI_RXSIG_OFFHOOK); + else /* on hook */ + __dahdi_hooksig_pvt(chan,DAHDI_RXSIG_ONHOOK); + break; + + case DAHDI_SIG_FXSKS: /* FXS Kewlstart */ + case DAHDI_SIG_FXSGS: /* FXS Groundstart */ + /* Fall through */ + case DAHDI_SIG_FXSLS: + if (!(cursig & DAHDI_BBIT)) { + /* Check for ringing first */ + __dahdi_hooksig_pvt(chan, DAHDI_RXSIG_RING); + break; + } + if ((chan->sig != DAHDI_SIG_FXSLS) && (cursig & DAHDI_ABIT)) { + /* if went on hook */ + __dahdi_hooksig_pvt(chan, DAHDI_RXSIG_ONHOOK); + } else { + __dahdi_hooksig_pvt(chan, DAHDI_RXSIG_OFFHOOK); + } + break; + case DAHDI_SIG_CAS: + /* send event that something changed */ + __qevent(chan, DAHDI_EVENT_BITSCHANGED); + break; + + default: + break; + } + /* Keep track of signalling for next time */ + chan->rxsig = cursig; + spin_unlock_irqrestore(&chan->lock, flags); +} + +static void process_echocan_events(struct dahdi_chan *chan) +{ + union dahdi_echocan_events events = chan->ec_state->events; + + if (events.CED_tx_detected) { + dahdi_qevent_nolock(chan, DAHDI_EVENT_TX_CED_DETECTED); + if (chan->ec_state) { + if (chan->ec_state->status.mode == ECHO_MODE_ACTIVE) + set_echocan_fax_mode(chan, chan->channo, "CED tx detected", 1); + else + module_printk(KERN_NOTICE, "Detected CED tone (tx) on channel %d\n", chan->channo); + } + } + + if (events.CED_rx_detected) { + dahdi_qevent_nolock(chan, DAHDI_EVENT_RX_CED_DETECTED); + if (chan->ec_state) { + if (chan->ec_state->status.mode == ECHO_MODE_ACTIVE) + set_echocan_fax_mode(chan, chan->channo, "CED rx detected", 1); + else + module_printk(KERN_NOTICE, "Detected CED tone (rx) on channel %d\n", chan->channo); + } + } + + if (events.CNG_tx_detected) + dahdi_qevent_nolock(chan, DAHDI_EVENT_TX_CNG_DETECTED); + + if (events.CNG_rx_detected) + dahdi_qevent_nolock(chan, DAHDI_EVENT_RX_CNG_DETECTED); + + if (events.NLP_auto_disabled) { + dahdi_qevent_nolock(chan, DAHDI_EVENT_EC_NLP_DISABLED); + chan->ec_state->status.mode = ECHO_MODE_FAX; + } + + if (events.NLP_auto_enabled) { + dahdi_qevent_nolock(chan, DAHDI_EVENT_EC_NLP_ENABLED); + chan->ec_state->status.mode = ECHO_MODE_ACTIVE; + } +} + +static inline void __dahdi_ec_chunk(struct dahdi_chan *ss, unsigned char *rxchunk, const unsigned char *txchunk) +{ + short rxlin, txlin; + int x; + unsigned long flags; + + spin_lock_irqsave(&ss->lock, flags); + + if (ss->readchunkpreec) { + /* Save a copy of the audio before the echo can has its way with it */ + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + /* We only ever really need to deal with signed linear - let's just convert it now */ + ss->readchunkpreec[x] = DAHDI_XLAW(rxchunk[x], ss); + } + + /* Perform echo cancellation on a chunk if necessary */ + if (ss->ec_state) { +#if defined(CONFIG_DAHDI_MMX) || defined(ECHO_CAN_FP) + dahdi_kernel_fpu_begin(); +#endif + if (ss->ec_state->status.mode & __ECHO_MODE_MUTE) { + /* Special stuff for training the echo can */ + for (x=0;xec_state->status.mode == ECHO_MODE_PRETRAINING) { + if (--ss->ec_state->status.pretrain_timer <= 0) { + ss->ec_state->status.pretrain_timer = 0; + ss->ec_state->status.mode = ECHO_MODE_STARTTRAINING; + } + } + if ((ss->ec_state->status.mode == ECHO_MODE_AWAITINGECHO) && (txlin > 8000)) { + ss->ec_state->status.last_train_tap = 0; + ss->ec_state->status.mode = ECHO_MODE_TRAINING; + } + if ((ss->ec_state->status.mode == ECHO_MODE_TRAINING) && + (ss->ec_state->ops->echocan_traintap)) { + if (ss->ec_state->ops->echocan_traintap(ss->ec_state, ss->ec_state->status.last_train_tap++, rxlin)) { +#if 0 + module_printk(KERN_NOTICE, "Finished training (%d taps trained)!\n", ss->ec_state->status.last_train_tap); +#endif + ss->ec_state->status.mode = ECHO_MODE_ACTIVE; + } + } + rxlin = 0; + rxchunk[x] = DAHDI_LIN2X((int)rxlin, ss); + } + } else if (ss->ec_state->status.mode != ECHO_MODE_IDLE) { + ss->ec_state->events.all = 0; + + if (ss->ec_state->ops->echocan_process) { + short rxlins[DAHDI_CHUNKSIZE], txlins[DAHDI_CHUNKSIZE]; + + for (x = 0; x < DAHDI_CHUNKSIZE; x++) { + rxlins[x] = DAHDI_XLAW(rxchunk[x], ss); + txlins[x] = DAHDI_XLAW(txchunk[x], ss); + } + ss->ec_state->ops->echocan_process(ss->ec_state, rxlins, txlins, DAHDI_CHUNKSIZE); + + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + rxchunk[x] = DAHDI_LIN2X((int) rxlins[x], ss); + } else if (ss->ec_state->ops->echocan_events) + ss->ec_state->ops->echocan_events(ss->ec_state); + + if (ss->ec_state->events.all) + process_echocan_events(ss); + + } +#if defined(CONFIG_DAHDI_MMX) || defined(ECHO_CAN_FP) + kernel_fpu_end(); +#endif + } + spin_unlock_irqrestore(&ss->lock, flags); +} + +void dahdi_ec_chunk(struct dahdi_chan *ss, unsigned char *rxchunk, const unsigned char *txchunk) +{ + __dahdi_ec_chunk(ss, rxchunk, txchunk); +} + +void dahdi_ec_span(struct dahdi_span *span) +{ + int x; + for (x = 0; x < span->channels; x++) { + if (span->chans[x]->ec_current) + __dahdi_ec_chunk(span->chans[x], span->chans[x]->readchunk, span->chans[x]->writechunk); + } +} + +/* return 0 if nothing detected, 1 if lack of tone, 2 if presence of tone */ +/* modifies buffer pointed to by 'amp' with notched-out values */ +static inline int sf_detect(struct sf_detect_state *s, + short *amp, + int samples,long p1, long p2, long p3) +{ +int i,rv = 0; +long x,y; + +#define SF_DETECT_SAMPLES (DAHDI_CHUNKSIZE * 5) +#define SF_DETECT_MIN_ENERGY 500 +#define NB 14 /* number of bits to shift left */ + + /* determine energy level before filtering */ + for(i = 0; i < samples; i++) + { + if (amp[i] < 0) s->e1 -= amp[i]; + else s->e1 += amp[i]; + } + /* do 2nd order IIR notch filter at given freq. and calculate + energy */ + for(i = 0; i < samples; i++) + { + x = amp[i] << NB; + y = s->x2 + (p1 * (s->x1 >> NB)) + x; + y += (p2 * (s->y2 >> NB)) + + (p3 * (s->y1 >> NB)); + s->x2 = s->x1; + s->x1 = x; + s->y2 = s->y1; + s->y1 = y; + amp[i] = y >> NB; + if (amp[i] < 0) s->e2 -= amp[i]; + else s->e2 += amp[i]; + } + s->samps += i; + /* if time to do determination */ + if ((s->samps) >= SF_DETECT_SAMPLES) + { + rv = 1; /* default to no tone */ + /* if enough energy, it is determined to be a tone */ + if (((s->e1 - s->e2) / s->samps) > SF_DETECT_MIN_ENERGY) rv = 2; + /* reset energy processing variables */ + s->samps = 0; + s->e1 = s->e2 = 0; + } + return(rv); +} + +static inline void __dahdi_process_putaudio_chunk(struct dahdi_chan *ss, unsigned char *rxb) +{ + /* We transmit data from our master channel */ + /* Called with ss->lock held */ + struct dahdi_chan *ms = ss->master; + /* Linear version of received data */ + short putlin[DAHDI_CHUNKSIZE],k[DAHDI_CHUNKSIZE]; + int x,r; + + if (ms->dialing) ms->afterdialingtimer = 50; + else if (ms->afterdialingtimer) ms->afterdialingtimer--; + if (ms->afterdialingtimer && (!(ms->flags & DAHDI_FLAG_PSEUDO))) { + /* Be careful since memset is likely a macro */ + rxb[0] = DAHDI_LIN2X(0, ms); + memset(&rxb[1], rxb[0], DAHDI_CHUNKSIZE - 1); /* receive as silence if dialing */ + } + for (x=0;xrxgain[rxb[x]]; + putlin[x] = DAHDI_XLAW(rxb[x], ms); + } + + if (ms->ec_state && (ms->ec_state->status.mode == ECHO_MODE_ACTIVE) && !ms->ec_state->features.CED_rx_detect) { + for (x = 0; x < DAHDI_CHUNKSIZE; x++) { + if (echo_can_disable_detector_update(&ms->ec_state->rxecdis, putlin[x])) { + set_echocan_fax_mode(ms, ss->channo, "CED rx detected", 1); + dahdi_qevent_nolock(ms, DAHDI_EVENT_RX_CED_DETECTED); + break; + } + } + } + + /* if doing rx tone decoding */ + if (ms->rxp1 && ms->rxp2 && ms->rxp3) + { + r = sf_detect(&ms->rd,putlin,DAHDI_CHUNKSIZE,ms->rxp1, + ms->rxp2,ms->rxp3); + /* Convert back */ + for(x=0;xrd.lastdetect) + { + if (((r == 2) && !(ms->toneflags & DAHDI_REVERSE_RXTONE)) || + ((r == 1) && (ms->toneflags & DAHDI_REVERSE_RXTONE))) + { + __qevent(ms,DAHDI_EVENT_RINGOFFHOOK); + } + else + { + __qevent(ms,DAHDI_EVENT_ONHOOK); + } + ms->rd.lastdetect = r; + } + } + } + + if (!(ms->flags & DAHDI_FLAG_PSEUDO)) { + memcpy(ms->putlin, putlin, DAHDI_CHUNKSIZE * sizeof(short)); + memcpy(ms->putraw, rxb, DAHDI_CHUNKSIZE); + } + + /* Take the rxc, twiddle it for conferencing if appropriate and put it + back */ + if ((!ms->confmute && !ms->afterdialingtimer) || + (ms->flags & DAHDI_FLAG_PSEUDO)) { + switch(ms->confmode & DAHDI_CONF_MODE_MASK) { + case DAHDI_CONF_NORMAL: /* Normal mode */ + /* Do nothing. rx goes output */ + break; + case DAHDI_CONF_MONITOR: /* Monitor a channel's rx mode */ + /* if not a pseudo-channel, ignore */ + if (!(ms->flags & DAHDI_FLAG_PSEUDO)) break; + /* Add monitored channel */ + if (chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO) { + ACSS(putlin, chans[ms->confna]->getlin); + } else { + ACSS(putlin, chans[ms->confna]->putlin); + } + /* Convert back */ + for(x=0;xflags & DAHDI_FLAG_PSEUDO)) break; + /* Add monitored channel */ + if (chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO) { + ACSS(putlin, chans[ms->confna]->putlin); + } else { + ACSS(putlin, chans[ms->confna]->getlin); + } + /* Convert back */ + for(x=0;xflags & DAHDI_FLAG_PSEUDO)) break; + /* Note: Technically, saturation should be done at + the end of the whole addition, but for performance + reasons, we don't do that. Besides, it only matters + when you're so loud you're clipping anyway */ + ACSS(putlin, chans[ms->confna]->getlin); + ACSS(putlin, chans[ms->confna]->putlin); + /* Convert back */ + for(x=0;xflags & DAHDI_FLAG_PSEUDO)) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(putlin, chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO ? + chans[ms->confna]->getlin : chans[ms->confna]->readchunkpreec); + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + rxb[x] = DAHDI_LIN2X(putlin[x], ms); + + break; + case DAHDI_CONF_MONITOR_TX_PREECHO: /* Monitor a channel's tx mode */ + /* if not a pseudo-channel, ignore */ + if (!(ms->flags & DAHDI_FLAG_PSEUDO)) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Add monitored channel */ + ACSS(putlin, chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO ? + chans[ms->confna]->readchunkpreec : chans[ms->confna]->getlin); + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + rxb[x] = DAHDI_LIN2X(putlin[x], ms); + + break; + case DAHDI_CONF_MONITORBOTH_PREECHO: /* Monitor a channel's tx and rx mode */ + /* if not a pseudo-channel, ignore */ + if (!(ms->flags & DAHDI_FLAG_PSEUDO)) + break; + + if (!chans[ms->confna]->readchunkpreec) + break; + + /* Note: Technically, saturation should be done at + the end of the whole addition, but for performance + reasons, we don't do that. Besides, it only matters + when you're so loud you're clipping anyway */ + ACSS(putlin, chans[ms->confna]->getlin); + ACSS(putlin, chans[ms->confna]->readchunkpreec); + for (x = 0; x < DAHDI_CHUNKSIZE; x++) + rxb[x] = DAHDI_LIN2X(putlin[x], ms); + + break; + case DAHDI_CONF_REALANDPSEUDO: + /* do normal conf mode processing */ + if (ms->confmode & DAHDI_CONF_TALKER) { + /* Store temp value */ + memcpy(k, putlin, DAHDI_CHUNKSIZE * sizeof(short)); + /* Add conf value */ + ACSS(k, conf_sums_next[ms->_confn]); + /* get amount actually added */ + memcpy(ms->conflast, k, DAHDI_CHUNKSIZE * sizeof(short)); + SCSS(ms->conflast, conf_sums_next[ms->_confn]); + /* Really add in new value */ + ACSS(conf_sums_next[ms->_confn], ms->conflast); + } else memset(ms->conflast, 0, DAHDI_CHUNKSIZE * sizeof(short)); + /* do the pseudo-channel part processing */ + memset(putlin, 0, DAHDI_CHUNKSIZE * sizeof(short)); + if (ms->confmode & DAHDI_CONF_PSEUDO_LISTENER) { + /* Subtract out previous last sample written to conf */ + SCSS(putlin, ms->conflast2); + /* Add in conference */ + ACSS(putlin, conf_sums[ms->_confn]); + } + /* Convert back */ + for(x=0;xflags & DAHDI_FLAG_PSEUDO) /* if a pseudo-channel */ + { + if (ms->confmode & DAHDI_CONF_LISTENER) { + /* Subtract out last sample written to conf */ + SCSS(putlin, ms->conflast); + /* Add in conference */ + ACSS(putlin, conf_sums[ms->_confn]); + } + /* Convert back */ + for(x=0;xputlin, putlin, DAHDI_CHUNKSIZE * sizeof(short)); + break; + } + /* fall through */ + case DAHDI_CONF_CONFANN: /* Conference with announce */ + if (ms->confmode & DAHDI_CONF_TALKER) { + /* Store temp value */ + memcpy(k, putlin, DAHDI_CHUNKSIZE * sizeof(short)); + /* Add conf value */ + ACSS(k, conf_sums_next[ms->_confn]); + /* get amount actually added */ + memcpy(ms->conflast, k, DAHDI_CHUNKSIZE * sizeof(short)); + SCSS(ms->conflast, conf_sums_next[ms->_confn]); + /* Really add in new value */ + ACSS(conf_sums_next[ms->_confn], ms->conflast); + } else + memset(ms->conflast, 0, DAHDI_CHUNKSIZE * sizeof(short)); + /* rxc unmodified */ + break; + case DAHDI_CONF_CONFMON: + case DAHDI_CONF_CONFANNMON: + if (ms->confmode & DAHDI_CONF_TALKER) { + /* Store temp value */ + memcpy(k, putlin, DAHDI_CHUNKSIZE * sizeof(short)); + /* Subtract last value */ + SCSS(conf_sums[ms->_confn], ms->conflast); + /* Add conf value */ + ACSS(k, conf_sums[ms->_confn]); + /* get amount actually added */ + memcpy(ms->conflast, k, DAHDI_CHUNKSIZE * sizeof(short)); + SCSS(ms->conflast, conf_sums[ms->_confn]); + /* Really add in new value */ + ACSS(conf_sums[ms->_confn], ms->conflast); + } else + memset(ms->conflast, 0, DAHDI_CHUNKSIZE * sizeof(short)); + for (x=0;x_confn][x], ms); + break; + case DAHDI_CONF_DIGITALMON: + /* if not a pseudo-channel, ignore */ + if (!(ms->flags & DAHDI_FLAG_PSEUDO)) break; + /* Add monitored channel */ + if (chans[ms->confna]->flags & DAHDI_FLAG_PSEUDO) { + memcpy(rxb, chans[ms->confna]->getraw, DAHDI_CHUNKSIZE); + } else { + memcpy(rxb, chans[ms->confna]->putraw, DAHDI_CHUNKSIZE); + } + break; + } + } +} + +/* HDLC (or other) receiver buffer functions for read side */ +static inline void __putbuf_chunk(struct dahdi_chan *ss, unsigned char *rxb, int bytes) +{ + /* We transmit data from our master channel */ + /* Called with ss->lock held */ + struct dahdi_chan *ms = ss->master; + /* Our receive buffer */ + unsigned char *buf; +#if defined(CONFIG_DAHDI_NET) || defined(CONFIG_DAHDI_PPP) + /* SKB for receiving network stuff */ + struct sk_buff *skb=NULL; +#endif + int oldbuf; + int eof=0; + int abort=0; + int res; + int left, x; + + while(bytes) { +#if defined(CONFIG_DAHDI_NET) || defined(CONFIG_DAHDI_PPP) + skb = NULL; +#endif + abort = 0; + eof = 0; + /* Next, figure out if we've got a buffer to receive into */ + if (ms->inreadbuf > -1) { + /* Read into the current buffer */ + buf = ms->readbuf[ms->inreadbuf]; + left = ms->blocksize - ms->readidx[ms->inreadbuf]; + if (left > bytes) + left = bytes; + if (ms->flags & DAHDI_FLAG_HDLC) { + for (x=0;xrxhdlc, *(rxb++)); + bytes--; + res = fasthdlc_rx_run(&ms->rxhdlc); + /* If there is nothing there, continue */ + if (res & RETURN_EMPTY_FLAG) + continue; + else if (res & RETURN_COMPLETE_FLAG) { + /* Only count this if it's a non-empty frame */ + if (ms->readidx[ms->inreadbuf]) { + if ((ms->flags & DAHDI_FLAG_FCS) && (ms->infcs != PPP_GOODFCS)) { + abort = DAHDI_EVENT_BADFCS; + } else + eof=1; + break; + } + continue; + } else if (res & RETURN_DISCARD_FLAG) { + /* This could be someone idling with + "idle" instead of "flag" */ + if (!ms->readidx[ms->inreadbuf]) + continue; + abort = DAHDI_EVENT_ABORT; + break; + } else { + unsigned char rxc; + rxc = res; + ms->infcs = PPP_FCS(ms->infcs, rxc); + buf[ms->readidx[ms->inreadbuf]++] = rxc; + /* Pay attention to the possibility of an overrun */ + if (ms->readidx[ms->inreadbuf] >= ms->blocksize) { + if (!ss->span->alarms) + module_printk(KERN_WARNING, "HDLC Receiver overrun on channel %s (master=%s)\n", ss->name, ss->master->name); + abort=DAHDI_EVENT_OVERRUN; + /* Force the HDLC state back to frame-search mode */ + ms->rxhdlc.state = 0; + ms->rxhdlc.bits = 0; + ms->readidx[ms->inreadbuf]=0; + break; + } + } + } + } else { + /* Not HDLC */ + memcpy(buf + ms->readidx[ms->inreadbuf], rxb, left); + rxb += left; + ms->readidx[ms->inreadbuf] += left; + bytes -= left; + /* End of frame is decided by block size of 'N' */ + eof = (ms->readidx[ms->inreadbuf] >= ms->blocksize); + if (eof && (ss->flags & DAHDI_FLAG_NOSTDTXRX)) { + eof = 0; + abort = DAHDI_EVENT_OVERRUN; + } + } + if (eof) { + /* Finished with this buffer, try another. */ + oldbuf = ms->inreadbuf; + ms->infcs = PPP_INITFCS; + ms->readn[ms->inreadbuf] = ms->readidx[ms->inreadbuf]; +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "EOF, len is %d\n", ms->readn[ms->inreadbuf]); +#endif +#if defined(CONFIG_DAHDI_NET) || defined(CONFIG_DAHDI_PPP) + if (ms->flags & (DAHDI_FLAG_NETDEV | DAHDI_FLAG_PPP)) { +#ifdef CONFIG_DAHDI_NET +#endif /* CONFIG_DAHDI_NET */ + /* Our network receiver logic is MUCH + different. We actually only use a single + buffer */ + if (ms->readn[ms->inreadbuf] > 1) { + /* Drop the FCS */ + ms->readn[ms->inreadbuf] -= 2; + /* Allocate an SKB */ +#ifdef CONFIG_DAHDI_PPP + if (!ms->do_ppp_error) +#endif + skb = dev_alloc_skb(ms->readn[ms->inreadbuf]); + if (skb) { + /* XXX Get rid of this memcpy XXX */ + memcpy(skb->data, ms->readbuf[ms->inreadbuf], ms->readn[ms->inreadbuf]); + skb_put(skb, ms->readn[ms->inreadbuf]); +#ifdef CONFIG_DAHDI_NET + if (ms->flags & DAHDI_FLAG_NETDEV) { + struct net_device_stats *stats = hdlc_stats(ms->hdlcnetdev->netdev); + stats->rx_packets++; + stats->rx_bytes += ms->readn[ms->inreadbuf]; + } +#endif + + } else { +#ifdef CONFIG_DAHDI_NET + if (ms->flags & DAHDI_FLAG_NETDEV) { + struct net_device_stats *stats = hdlc_stats(ms->hdlcnetdev->netdev); + stats->rx_dropped++; + } +#endif +#ifdef CONFIG_DAHDI_PPP + if (ms->flags & DAHDI_FLAG_PPP) { + abort = DAHDI_EVENT_OVERRUN; + } +#endif +#if 1 +#ifdef CONFIG_DAHDI_PPP + if (!ms->do_ppp_error) +#endif + module_printk(KERN_NOTICE, "Memory squeeze, dropped one\n"); +#endif + } + } + /* We don't cycle through buffers, just + reuse the same one */ + ms->readn[ms->inreadbuf] = 0; + ms->readidx[ms->inreadbuf] = 0; + } else +#endif + { + /* This logic might confuse and astound. Basically we need to find + * the previous buffer index. It should be safe because, regardless + * of whether or not it has been copied to user space, nothing should + * have messed around with it since then */ + + int comparemessage; + /* Shut compiler up */ + int myres = 0; + + if (ms->flags & DAHDI_FLAG_MTP2) { + comparemessage = (ms->inreadbuf - 1) & (ms->numbufs - 1); + + myres = memcmp(ms->readbuf[comparemessage], ms->readbuf[ms->inreadbuf], ms->readn[ms->inreadbuf]); + } + + if ((ms->flags & DAHDI_FLAG_MTP2) && !myres) { + /* Our messages are the same, so discard - + * Don't advance buffers, reset indexes and buffer sizes. */ + ms->readn[ms->inreadbuf] = 0; + ms->readidx[ms->inreadbuf] = 0; + } else { + ms->inreadbuf = (ms->inreadbuf + 1) % ms->numbufs; + if (ms->inreadbuf == ms->outreadbuf) { + /* Whoops, we're full, and have no where else + to store into at the moment. We'll drop it + until there's a buffer available */ +#ifdef BUFFER_DEBUG + module_printk(KERN_NOTICE, "Out of storage space\n"); +#endif + ms->inreadbuf = -1; + /* Enable the receiver in case they've got POLICY_WHEN_FULL */ + ms->rxdisable = 0; + } + if (ms->outreadbuf < 0) { /* start out buffer if not already */ + ms->outreadbuf = oldbuf; + /* if there are processes waiting in poll() on this channel, + wake them up */ + if (!ms->rxdisable) { + wake_up_interruptible(&ms->sel); + } + } +/* In the very orignal driver, it was quite well known to me (Jim) that there +was a possibility that a channel sleeping on a receive block needed to +be potentially woken up EVERY time a buffer was filled, not just on the first +one, because if only done on the first one there is a slight timing potential +of missing the wakeup (between where it senses the (lack of) active condition +(with interrupts disabled) and where it does the sleep (interrupts enabled) +in the read or iomux call, etc). That is why the read and iomux calls start +with an infinite loop that gets broken out of upon an active condition, +otherwise keeps sleeping and looking. The part in this code got "optimized" +out in the later versions, and is put back now. Note that this is *NOT* +needed for poll() waiters, because the poll_wait() function that is used there +is atomic enough for this purpose; it will not go to sleep before ensuring +that the waitqueue is empty. */ + if (!ms->rxdisable) { /* if receiver enabled */ + /* Notify a blocked reader that there is data available + to be read, unless we're waiting for it to be full */ +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Notifying reader data in block %d\n", oldbuf); +#endif + wake_up_interruptible(&ms->readbufq); + if (ms->iomask & DAHDI_IOMUX_READ) + wake_up_interruptible(&ms->eventbufq); + } + } + } + } + if (abort) { + /* Start over reading frame */ + ms->readidx[ms->inreadbuf] = 0; + ms->infcs = PPP_INITFCS; + +#ifdef CONFIG_DAHDI_NET + if (ms->flags & DAHDI_FLAG_NETDEV) { + struct net_device_stats *stats = hdlc_stats(ms->hdlcnetdev->netdev); + stats->rx_errors++; + if (abort == DAHDI_EVENT_OVERRUN) + stats->rx_over_errors++; + if (abort == DAHDI_EVENT_BADFCS) + stats->rx_crc_errors++; + if (abort == DAHDI_EVENT_ABORT) + stats->rx_frame_errors++; + } else +#endif +#ifdef CONFIG_DAHDI_PPP + if (ms->flags & DAHDI_FLAG_PPP) { + ms->do_ppp_error = 1; + tasklet_schedule(&ms->ppp_calls); + } else +#endif + if (test_bit(DAHDI_FLAGBIT_OPEN, &ms->flags) && !ss->span->alarms) { + /* Notify the receiver... */ + __qevent(ss->master, abort); + } +#if 0 + module_printk(KERN_NOTICE, "torintr_receive: Aborted %d bytes of frame on %d\n", amt, ss->master); +#endif + + } + } else /* No place to receive -- drop on the floor */ + break; +#ifdef CONFIG_DAHDI_NET + if (skb && (ms->flags & DAHDI_FLAG_NETDEV)) +#ifdef NEW_HDLC_INTERFACE + { +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,22) + skb->mac.raw = skb->data; +#else + skb_reset_mac_header(skb); +#endif + skb->dev = ztchan_to_dev(ms); +#ifdef DAHDI_HDLC_TYPE_TRANS + skb->protocol = hdlc_type_trans(skb, ztchan_to_dev(ms)); +#else + skb->protocol = htons (ETH_P_HDLC); +#endif + netif_rx(skb); + } +#else + hdlc_netif_rx(&ms->hdlcnetdev->netdev, skb); +#endif +#endif +#ifdef CONFIG_DAHDI_PPP + if (skb && (ms->flags & DAHDI_FLAG_PPP)) { + unsigned char *tmp; + tmp = skb->data; + skb_pull(skb, 2); + /* Make sure that it's addressed to ALL STATIONS and UNNUMBERED */ + if (!tmp || (tmp[0] != 0xff) || (tmp[1] != 0x03)) { + /* Invalid SKB -- drop */ + if (tmp) + module_printk(KERN_NOTICE, "Received invalid SKB (%02x, %02x)\n", tmp[0], tmp[1]); + dev_kfree_skb_irq(skb); + } else { + skb_queue_tail(&ms->ppp_rq, skb); + tasklet_schedule(&ms->ppp_calls); + } + } +#endif + } +} + +static inline void __dahdi_putbuf_chunk(struct dahdi_chan *ss, unsigned char *rxb) +{ + __putbuf_chunk(ss, rxb, DAHDI_CHUNKSIZE); +} + +static void __dahdi_hdlc_abort(struct dahdi_chan *ss, int event) +{ + if (ss->inreadbuf >= 0) + ss->readidx[ss->inreadbuf] = 0; + if (test_bit(DAHDI_FLAGBIT_OPEN, &ss->flags) && !ss->span->alarms) + __qevent(ss->master, event); +} + +void dahdi_hdlc_abort(struct dahdi_chan *ss, int event) +{ + unsigned long flags; + spin_lock_irqsave(&ss->lock, flags); + __dahdi_hdlc_abort(ss, event); + spin_unlock_irqrestore(&ss->lock, flags); +} + +void dahdi_hdlc_putbuf(struct dahdi_chan *ss, unsigned char *rxb, int bytes) +{ + unsigned long flags; + int res; + int left; + + spin_lock_irqsave(&ss->lock, flags); + if (ss->inreadbuf < 0) { +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "No place to receive HDLC frame\n"); +#endif + spin_unlock_irqrestore(&ss->lock, flags); + return; + } + /* Read into the current buffer */ + left = ss->blocksize - ss->readidx[ss->inreadbuf]; + if (left > bytes) + left = bytes; + if (left > 0) { + memcpy(ss->readbuf[ss->inreadbuf] + ss->readidx[ss->inreadbuf], rxb, left); + rxb += left; + ss->readidx[ss->inreadbuf] += left; + bytes -= left; + } + /* Something isn't fit into buffer */ + if (bytes) { +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "HDLC frame isn't fit into buffer space\n"); +#endif + __dahdi_hdlc_abort(ss, DAHDI_EVENT_OVERRUN); + } + res = left; + spin_unlock_irqrestore(&ss->lock, flags); +} + +void dahdi_hdlc_finish(struct dahdi_chan *ss) +{ + int oldreadbuf; + unsigned long flags; + + spin_lock_irqsave(&ss->lock, flags); + + if ((oldreadbuf = ss->inreadbuf) < 0) { +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "No buffers to finish\n"); +#endif + spin_unlock_irqrestore(&ss->lock, flags); + return; + } + + if (!ss->readidx[ss->inreadbuf]) { +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Empty HDLC frame received\n"); +#endif + spin_unlock_irqrestore(&ss->lock, flags); + return; + } + + ss->readn[ss->inreadbuf] = ss->readidx[ss->inreadbuf]; + ss->inreadbuf = (ss->inreadbuf + 1) % ss->numbufs; + if (ss->inreadbuf == ss->outreadbuf) { + ss->inreadbuf = -1; +#ifdef CONFIG_DAHDI_DEBUG + module_printk(KERN_NOTICE, "Notifying reader data in block %d\n", oldreadbuf); +#endif + ss->rxdisable = 0; + } + if (ss->outreadbuf < 0) { + ss->outreadbuf = oldreadbuf; + } + + if (!ss->rxdisable) { + wake_up_interruptible(&ss->readbufq); + wake_up_interruptible(&ss->sel); + if (ss->iomask & DAHDI_IOMUX_READ) + wake_up_interruptible(&ss->eventbufq); + } + spin_unlock_irqrestore(&ss->lock, flags); +} + +/* Returns 1 if EOF, 0 if data is still in frame, -1 if EOF and no buffers left */ +int dahdi_hdlc_getbuf(struct dahdi_chan *ss, unsigned char *bufptr, unsigned int *size) +{ + unsigned char *buf; + unsigned long flags; + int left = 0; + int res; + int oldbuf; + + spin_lock_irqsave(&ss->lock, flags); + if (ss->outwritebuf > -1) { + buf = ss->writebuf[ss->outwritebuf]; + left = ss->writen[ss->outwritebuf] - ss->writeidx[ss->outwritebuf]; + /* Strip off the empty HDLC CRC end */ + left -= 2; + if (left <= *size) { + *size = left; + res = 1; + } else + res = 0; + + memcpy(bufptr, &buf[ss->writeidx[ss->outwritebuf]], *size); + ss->writeidx[ss->outwritebuf] += *size; + + if (res) { + /* Rotate buffers */ + oldbuf = ss->outwritebuf; + ss->writeidx[oldbuf] = 0; + ss->writen[oldbuf] = 0; + ss->outwritebuf = (ss->outwritebuf + 1) % ss->numbufs; + if (ss->outwritebuf == ss->inwritebuf) { + ss->outwritebuf = -1; + if (ss->iomask & (DAHDI_IOMUX_WRITE | DAHDI_IOMUX_WRITEEMPTY)) + wake_up_interruptible(&ss->eventbufq); + /* If we're only supposed to start when full, disable the transmitter */ + if ((ss->txbufpolicy == DAHDI_POLICY_WHEN_FULL) || (ss->txbufpolicy == DAHDI_POLICY_HALF_FULL)) + ss->txdisable = 1; + res = -1; + } + + if (ss->inwritebuf < 0) + ss->inwritebuf = oldbuf; + + if (!(ss->flags & (DAHDI_FLAG_NETDEV | DAHDI_FLAG_PPP))) { + wake_up_interruptible(&ss->writebufq); + wake_up_interruptible(&ss->sel); + if ((ss->iomask & DAHDI_IOMUX_WRITE) && (res >= 0)) + wake_up_interruptible(&ss->eventbufq); + } + } + } else { + res = -1; + *size = 0; + } + spin_unlock_irqrestore(&ss->lock, flags); + + return res; +} + + +static void process_timers(void) +{ + unsigned long flags; + struct dahdi_timer *cur; + + spin_lock_irqsave(&zaptimerlock, flags); + + list_for_each_entry(cur, &zaptimers, list) { + if (cur->ms) { + cur->pos -= DAHDI_CHUNKSIZE; + if (cur->pos <= 0) { + cur->tripped++; + cur->pos = cur->ms; + wake_up_interruptible(&cur->sel); + } + } + } + + spin_unlock_irqrestore(&zaptimerlock, flags); +} + +static unsigned int dahdi_timer_poll(struct file *file, struct poll_table_struct *wait_table) +{ + struct dahdi_timer *timer = file->private_data; + unsigned long flags; + int ret = 0; + if (timer) { + poll_wait(file, &timer->sel, wait_table); + spin_lock_irqsave(&zaptimerlock, flags); + if (timer->tripped || timer->ping) + ret |= POLLPRI; + spin_unlock_irqrestore(&zaptimerlock, flags); + } else + ret = -EINVAL; + return ret; +} + +/* device poll routine */ +static unsigned int +dahdi_chan_poll(struct file *file, struct poll_table_struct *wait_table, int unit) +{ + + struct dahdi_chan *chan = chans[unit]; + int ret; + unsigned long flags; + + /* do the poll wait */ + if (chan) { + poll_wait(file, &chan->sel, wait_table); + ret = 0; /* start with nothing to return */ + spin_lock_irqsave(&chan->lock, flags); + /* if at least 1 write buffer avail */ + if (chan->inwritebuf > -1) { + ret |= POLLOUT | POLLWRNORM; + } + if ((chan->outreadbuf > -1) && !chan->rxdisable) { + ret |= POLLIN | POLLRDNORM; + } + if (chan->eventoutidx != chan->eventinidx) + { + /* Indicate an exception */ + ret |= POLLPRI; + } + spin_unlock_irqrestore(&chan->lock, flags); + } else + ret = -EINVAL; + return(ret); /* return what we found */ +} + +static int dahdi_mmap(struct file *file, struct vm_area_struct *vm) +{ + int unit = UNIT(file); + if (unit == 250) + return dahdi_transcode_fops->mmap(file, vm); + return -ENOSYS; +} + +static unsigned int dahdi_poll(struct file *file, struct poll_table_struct *wait_table) +{ + int unit = UNIT(file); + struct dahdi_chan *chan; + + if (!unit) + return -EINVAL; + + if (unit == 250) + return dahdi_transcode_fops->poll(file, wait_table); + + if (unit == 253) + return dahdi_timer_poll(file, wait_table); + + if (unit == 254) { + chan = file->private_data; + if (!chan) + return -EINVAL; + return dahdi_chan_poll(file, wait_table,chan->channo); + } + if (unit == 255) { + chan = file->private_data; + if (!chan) { + module_printk(KERN_NOTICE, "No pseudo channel structure to read?\n"); + return -EINVAL; + } + return dahdi_chan_poll(file, wait_table, chan->channo); + } + return dahdi_chan_poll(file, wait_table, unit); +} + +static void __dahdi_transmit_chunk(struct dahdi_chan *chan, unsigned char *buf) +{ + unsigned char silly[DAHDI_CHUNKSIZE]; + /* Called with chan->lock locked */ +#ifdef OPTIMIZE_CHANMUTE + if(likely(chan->chanmute)) + return; +#endif + if (!buf) + buf = silly; + __dahdi_getbuf_chunk(chan, buf); + + if ((chan->flags & DAHDI_FLAG_AUDIO) || (chan->confmode)) { +#ifdef CONFIG_DAHDI_MMX + dahdi_kernel_fpu_begin(); +#endif + __dahdi_process_getaudio_chunk(chan, buf); +#ifdef CONFIG_DAHDI_MMX + kernel_fpu_end(); +#endif + } +} + +static inline void __dahdi_real_transmit(struct dahdi_chan *chan) +{ + /* Called with chan->lock held */ +#ifdef OPTIMIZE_CHANMUTE + if(likely(chan->chanmute)) + return; +#endif + if (chan->confmode) { + /* Pull queued data off the conference */ + __buf_pull(&chan->confout, chan->writechunk, chan, "dahdi_real_transmit"); + } else { + __dahdi_transmit_chunk(chan, chan->writechunk); + } +} + +static void __dahdi_getempty(struct dahdi_chan *ms, unsigned char *buf) +{ + int bytes = DAHDI_CHUNKSIZE; + int left; + unsigned char *txb = buf; + int x; + short getlin; + /* Called with ms->lock held */ + + while(bytes) { + /* Receive silence, or tone */ + if (ms->curtone) { + left = ms->curtone->tonesamples - ms->tonep; + if (left > bytes) + left = bytes; + for (x=0;xts, ms->curtone); + *(txb++) = DAHDI_LIN2X(getlin, ms); + } + ms->tonep+=left; + bytes -= left; + if (ms->tonep >= ms->curtone->tonesamples) { + struct dahdi_tone *last; + /* Go to the next sample of the tone */ + ms->tonep = 0; + last = ms->curtone; + ms->curtone = ms->curtone->next; + if (!ms->curtone) { + /* No more tones... Is this dtmf or mf? If so, go to the next digit */ + if (ms->dialing) + __do_dtmf(ms); + } else { + if (last != ms->curtone) + dahdi_init_tone_state(&ms->ts, ms->curtone); + } + } + } else { + /* Use silence */ + memset(txb, DAHDI_LIN2X(0, ms), bytes); + bytes = 0; + } + } + +} + +static void __dahdi_receive_chunk(struct dahdi_chan *chan, unsigned char *buf) +{ + /* Receive chunk of audio -- called with chan->lock held */ + unsigned char waste[DAHDI_CHUNKSIZE]; + +#ifdef OPTIMIZE_CHANMUTE + if(likely(chan->chanmute)) + return; +#endif + if (!buf) { + memset(waste, DAHDI_LIN2X(0, chan), sizeof(waste)); + buf = waste; + } + if ((chan->flags & DAHDI_FLAG_AUDIO) || (chan->confmode)) { +#ifdef CONFIG_DAHDI_MMX + dahdi_kernel_fpu_begin(); +#endif + __dahdi_process_putaudio_chunk(chan, buf); +#ifdef CONFIG_DAHDI_MMX + kernel_fpu_end(); +#endif + } + __dahdi_putbuf_chunk(chan, buf); +} + +static inline void __dahdi_real_receive(struct dahdi_chan *chan) +{ + /* Called with chan->lock held */ +#ifdef OPTIMIZE_CHANMUTE + if(likely(chan->chanmute)) + return; +#endif + if (chan->confmode) { + /* Load into queue if we have space */ + __buf_push(&chan->confin, chan->readchunk, "dahdi_real_receive"); + } else { + __dahdi_receive_chunk(chan, chan->readchunk); + } +} + +int dahdi_transmit(struct dahdi_span *span) +{ + int x,y,z; + unsigned long flags; + +#if 1 + for (x=0;xchannels;x++) { + spin_lock_irqsave(&span->chans[x]->lock, flags); + if (span->chans[x]->flags & DAHDI_FLAG_NOSTDTXRX) { + spin_unlock_irqrestore(&span->chans[x]->lock, flags); + continue; + } + if (span->chans[x] == span->chans[x]->master) { + if (span->chans[x]->otimer) { + span->chans[x]->otimer -= DAHDI_CHUNKSIZE; + if (span->chans[x]->otimer <= 0) { + __rbs_otimer_expire(span->chans[x]); + } + } + if (span->chans[x]->flags & DAHDI_FLAG_AUDIO) { + __dahdi_real_transmit(span->chans[x]); + } else { + if (span->chans[x]->nextslave) { + u_char data[DAHDI_CHUNKSIZE]; + int pos=DAHDI_CHUNKSIZE; + /* Process master/slaves one way */ + for (y=0;ychans[x], data); + pos = 0; + } + span->chans[z]->writechunk[y] = data[pos++]; + z = span->chans[z]->nextslave; + } while(z); + } + } else { + /* Process independents elsewise */ + __dahdi_real_transmit(span->chans[x]); + } + } + if (span->chans[x]->sig == DAHDI_SIG_DACS_RBS) { + if (chans[span->chans[x]->confna]) { + /* Just set bits for our destination */ + if (span->chans[x]->txsig != chans[span->chans[x]->confna]->rxsig) { + span->chans[x]->txsig = chans[span->chans[x]->confna]->rxsig; + span->rbsbits(span->chans[x], chans[span->chans[x]->confna]->rxsig); + } + } + } + + } + spin_unlock_irqrestore(&span->chans[x]->lock, flags); + } + if (span->mainttimer) { + span->mainttimer -= DAHDI_CHUNKSIZE; + if (span->mainttimer <= 0) { + span->mainttimer = 0; + if (span->maint) + span->maint(span, DAHDI_MAINT_LOOPSTOP); + span->maintstat = 0; + wake_up_interruptible(&span->maintq); + } + } +#endif + return 0; +} + +static void process_masterspan(void) +{ + unsigned long flags; + int x, y, z; + +#ifdef CONFIG_DAHDI_CORE_TIMER + /* We increment the calls since start here, so that if we switch over + * to the core timer, we know how many times we need to call + * process_masterspan in order to catch up since this function needs + * to be called 1000 times per second. */ + atomic_inc(&core_timer.count); +#endif + /* Hold the big zap lock for the duration of major + activities which touch all sorts of channels */ + spin_lock_irqsave(&bigzaplock, flags); + read_lock(&chan_lock); + /* Process any timers */ + process_timers(); + /* If we have dynamic stuff, call the ioctl with 0,0 parameters to + make it run */ + if (dahdi_dynamic_ioctl) + dahdi_dynamic_ioctl(0, 0); + + for (x = 1; x < maxchans; x++) { + if (chans[x] && chans[x]->confmode && + !(chans[x]->flags & DAHDI_FLAG_PSEUDO)) { + u_char *data; + spin_lock(&chans[x]->lock); + data = __buf_peek(&chans[x]->confin); + __dahdi_receive_chunk(chans[x], data); + if (data) { + __buf_pull(&chans[x]->confin, NULL, chans[x], + "confreceive"); + } + spin_unlock(&chans[x]->lock); + } + } + /* This is the master channel, so make things switch over */ + rotate_sums(); + /* do all the pseudo and/or conferenced channel receives (getbuf's) */ + for (x = 1; x < maxchans; x++) { + if (chans[x] && (chans[x]->flags & DAHDI_FLAG_PSEUDO)) { + spin_lock(&chans[x]->lock); + __dahdi_transmit_chunk(chans[x], NULL); + spin_unlock(&chans[x]->lock); + } + } + if (maxlinks) { +#ifdef CONFIG_DAHDI_MMX + dahdi_kernel_fpu_begin(); +#endif + /* process all the conf links */ + for (x = 1; x <= maxlinks; x++) { + /* if we have a destination conf */ + z = confalias[conf_links[x].dst]; + if (z) { + y = confalias[conf_links[x].src]; + if (y) + ACSS(conf_sums[z], conf_sums[y]); + } + } +#ifdef CONFIG_DAHDI_MMX + dahdi_kernel_fpu_end(); +#endif + } + /* do all the pseudo/conferenced channel transmits (putbuf's) */ + for (x = 1; x < maxchans; x++) { + if (chans[x] && (chans[x]->flags & DAHDI_FLAG_PSEUDO)) { + unsigned char tmp[DAHDI_CHUNKSIZE]; + spin_lock(&chans[x]->lock); + __dahdi_getempty(chans[x], tmp); + __dahdi_receive_chunk(chans[x], tmp); + spin_unlock(&chans[x]->lock); + } + } + for (x = 1; x < maxchans; x++) { + if (chans[x] && chans[x]->confmode && + !(chans[x]->flags & DAHDI_FLAG_PSEUDO)) { + u_char *data; + spin_lock(&chans[x]->lock); + data = __buf_pushpeek(&chans[x]->confout); + __dahdi_transmit_chunk(chans[x], data); + if (data) + __buf_push(&chans[x]->confout, NULL, + "conftransmit"); + spin_unlock(&chans[x]->lock); + } + } +#ifdef DAHDI_SYNC_TICK + for (x = 0; x < maxspans; x++) { + struct dahdi_span *const s = spans[x]; + if (s && s->sync_tick) + s->sync_tick(s, s == master); + } +#endif + read_unlock(&chan_lock); + spin_unlock_irqrestore(&bigzaplock, flags); +} + +#ifndef CONFIG_DAHDI_CORE_TIMER + +static void coretimer_init(void) +{ + return; +} + +static void coretimer_cleanup(void) +{ + return; +} + +#else + +static unsigned long core_diff_ms(struct timespec *t0, struct timespec *t1) +{ + long nanosec, sec; + unsigned long ms; + sec = (t1->tv_sec - t0->tv_sec); + nanosec = (t1->tv_nsec - t0->tv_nsec); + while (nanosec >= NSEC_PER_SEC) { + nanosec -= NSEC_PER_SEC; + ++sec; + } + while (nanosec < 0) { + nanosec += NSEC_PER_SEC; + --sec; + } + ms = (sec * 1000) + (nanosec / 1000000L); + return ms; +} + +static void coretimer_func(unsigned long param) +{ + unsigned long ms_since_start; + struct timespec now; + const unsigned long MAX_INTERVAL = 100000L; + const unsigned long FOURMS_INTERVAL = HZ/250; + const unsigned long ONESEC_INTERVAL = HZ; + + now = current_kernel_time(); + + if (atomic_read(&core_timer.count) == + atomic_read(&core_timer.last_count)) { + + /* This is the code path if a board driver is not calling + * dahdi_receive, and therefore the core of dahdi needs to + * perform the master span processing itself. */ + + if (!atomic_read(&core_timer.shutdown)) + mod_timer(&core_timer.timer, jiffies + FOURMS_INTERVAL); + + ms_since_start = core_diff_ms(&core_timer.start_interval, &now); + while (ms_since_start > atomic_read(&core_timer.count)) + process_masterspan(); + + if (ms_since_start > MAX_INTERVAL) { + atomic_set(&core_timer.count, 0); + atomic_set(&core_timer.last_count, 0); + core_timer.start_interval = now; + } else { + atomic_set(&core_timer.last_count, + atomic_read(&core_timer.count)); + } + + } else { + + /* It looks like a board driver is calling dahdi_receive. We + * will just check again in a second. */ + atomic_set(&core_timer.count, 0); + atomic_set(&core_timer.last_count, 0); + core_timer.start_interval = now; + if (!atomic_read(&core_timer.shutdown)) + mod_timer(&core_timer.timer, jiffies + ONESEC_INTERVAL); + } +} + +static void coretimer_init(void) +{ + init_timer(&core_timer.timer); + core_timer.timer.function = coretimer_func; + core_timer.start_interval = current_kernel_time(); + core_timer.timer.expires = jiffies + HZ; + atomic_set(&core_timer.count, 0); + atomic_set(&core_timer.shutdown, 0); + add_timer(&core_timer.timer); +} + +static void coretimer_cleanup(void) +{ + atomic_set(&core_timer.shutdown, 1); + del_timer_sync(&core_timer.timer); +} + +#endif /* CONFIG_DAHDI_CORE_TIMER */ + + +int dahdi_receive(struct dahdi_span *span) +{ + int x,y,z; + unsigned long flags; + +#ifdef CONFIG_DAHDI_WATCHDOG + span->watchcounter--; +#endif + for (x=0;xchannels;x++) { + if (span->chans[x]->master == span->chans[x]) { + spin_lock_irqsave(&span->chans[x]->lock, flags); + if (span->chans[x]->nextslave) { + /* Must process each slave at the same time */ + u_char data[DAHDI_CHUNKSIZE]; + int pos = 0; + for (y=0;ychans[z]->readchunk[y]; + if (pos == DAHDI_CHUNKSIZE) { + if(!(span->chans[x]->flags & DAHDI_FLAG_NOSTDTXRX)) + __dahdi_receive_chunk(span->chans[x], data); + pos = 0; + } + z=span->chans[z]->nextslave; + } while(z); + } + } else { + /* Process a normal channel */ + if (!(span->chans[x]->flags & DAHDI_FLAG_NOSTDTXRX)) + __dahdi_real_receive(span->chans[x]); + } + if (span->chans[x]->itimer) { + span->chans[x]->itimer -= DAHDI_CHUNKSIZE; + if (span->chans[x]->itimer <= 0) { + rbs_itimer_expire(span->chans[x]); + } + } + if (span->chans[x]->ringdebtimer) + span->chans[x]->ringdebtimer--; + if (span->chans[x]->sig & __DAHDI_SIG_FXS) { + if (span->chans[x]->rxhooksig == DAHDI_RXSIG_RING) + span->chans[x]->ringtrailer = DAHDI_RINGTRAILER; + else if (span->chans[x]->ringtrailer) { + span->chans[x]->ringtrailer-= DAHDI_CHUNKSIZE; + /* See if RING trailer is expired */ + if (!span->chans[x]->ringtrailer && !span->chans[x]->ringdebtimer) + __qevent(span->chans[x],DAHDI_EVENT_RINGOFFHOOK); + } + } + if (span->chans[x]->pulsetimer) + { + span->chans[x]->pulsetimer--; + if (span->chans[x]->pulsetimer <= 0) + { + if (span->chans[x]->pulsecount) + { + if (span->chans[x]->pulsecount > 12) { + + module_printk(KERN_NOTICE, "Got pulse digit %d on %s???\n", + span->chans[x]->pulsecount, + span->chans[x]->name); + } else if (span->chans[x]->pulsecount > 11) { + __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | '#'); + } else if (span->chans[x]->pulsecount > 10) { + __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | '*'); + } else if (span->chans[x]->pulsecount > 9) { + __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | '0'); + } else { + __qevent(span->chans[x], DAHDI_EVENT_PULSEDIGIT | ('0' + + span->chans[x]->pulsecount)); + } + span->chans[x]->pulsecount = 0; + } + } + } +#ifdef BUFFER_DEBUG + span->chans[x]->statcount -= DAHDI_CHUNKSIZE; +#endif + spin_unlock_irqrestore(&span->chans[x]->lock, flags); + } + } + + if (span == master) + process_masterspan(); + + return 0; +} + +MODULE_AUTHOR("Mark Spencer "); +MODULE_DESCRIPTION("DAHDI Telephony Interface"); +MODULE_LICENSE("GPL v2"); +MODULE_VERSION(DAHDI_VERSION); + +module_param(debug, int, 0644); +module_param(deftaps, int, 0644); + +static struct file_operations dahdi_fops = { + .owner = THIS_MODULE, + .llseek = NULL, + .open = dahdi_open, + .release = dahdi_release, + .ioctl = dahdi_ioctl, + .read = dahdi_read, + .write = dahdi_write, + .poll = dahdi_poll, + .mmap = dahdi_mmap, + .flush = NULL, + .fsync = NULL, + .fasync = NULL, +}; + +#ifdef CONFIG_DAHDI_WATCHDOG +static struct timer_list watchdogtimer; + +static void watchdog_check(unsigned long ignored) +{ + int x; + unsigned long flags; + static int wdcheck=0; + + local_irq_save(flags); + for (x=0;xflags & DAHDI_FLAG_RUNNING)) { + if (spans[x]->watchcounter == DAHDI_WATCHDOG_INIT) { + /* Whoops, dead card */ + if ((spans[x]->watchstate == DAHDI_WATCHSTATE_OK) || + (spans[x]->watchstate == DAHDI_WATCHSTATE_UNKNOWN)) { + spans[x]->watchstate = DAHDI_WATCHSTATE_RECOVERING; + if (spans[x]->watchdog) { + module_printk(KERN_NOTICE, "Kicking span %s\n", spans[x]->name); + spans[x]->watchdog(spans[x], DAHDI_WATCHDOG_NOINTS); + } else { + module_printk(KERN_NOTICE, "Span %s is dead with no revival\n", spans[x]->name); + spans[x]->watchstate = DAHDI_WATCHSTATE_FAILED; + } + } + } else { + if ((spans[x]->watchstate != DAHDI_WATCHSTATE_OK) && + (spans[x]->watchstate != DAHDI_WATCHSTATE_UNKNOWN)) + module_printk(KERN_NOTICE, "Span %s is alive!\n", spans[x]->name); + spans[x]->watchstate = DAHDI_WATCHSTATE_OK; + } + spans[x]->watchcounter = DAHDI_WATCHDOG_INIT; + } + } + local_irq_restore(flags); + if (!wdcheck) { + module_printk(KERN_NOTICE, "watchdog on duty!\n"); + wdcheck=1; + } + mod_timer(&watchdogtimer, jiffies + 2); +} + +static int __init watchdog_init(void) +{ + init_timer(&watchdogtimer); + watchdogtimer.expires = 0; + watchdogtimer.data =0; + watchdogtimer.function = watchdog_check; + /* Run every couple of jiffy or so */ + mod_timer(&watchdogtimer, jiffies + 2); + return 0; +} + +static void __exit watchdog_cleanup(void) +{ + del_timer(&watchdogtimer); +} + +#endif + +int dahdi_register_chardev(struct dahdi_chardev *dev) +{ + char udevname[strlen(dev->name) + sizeof("dahdi!")]; + + strcpy(udevname, "dahdi!"); + strcat(udevname, dev->name); + CLASS_DEV_CREATE(dahdi_class, MKDEV(DAHDI_MAJOR, dev->minor), NULL, udevname); + + return 0; +} + +int dahdi_unregister_chardev(struct dahdi_chardev *dev) +{ + CLASS_DEV_DESTROY(dahdi_class, MKDEV(DAHDI_MAJOR, dev->minor)); + + return 0; +} + +static int __init dahdi_init(void) +{ + int res = 0; + +#ifdef CONFIG_PROC_FS + proc_entries[0] = proc_mkdir("dahdi", NULL); +#endif + + if ((res = register_chrdev(DAHDI_MAJOR, "dahdi", &dahdi_fops))) { + module_printk(KERN_ERR, "Unable to register DAHDI character device handler on %d\n", DAHDI_MAJOR); + return res; + } + + dahdi_class = class_create(THIS_MODULE, "dahdi"); + CLASS_DEV_CREATE(dahdi_class, MKDEV(DAHDI_MAJOR, 253), NULL, "dahdi!timer"); + CLASS_DEV_CREATE(dahdi_class, MKDEV(DAHDI_MAJOR, 254), NULL, "dahdi!channel"); + CLASS_DEV_CREATE(dahdi_class, MKDEV(DAHDI_MAJOR, 255), NULL, "dahdi!pseudo"); + CLASS_DEV_CREATE(dahdi_class, MKDEV(DAHDI_MAJOR, 0), NULL, "dahdi!ctl"); + + module_printk(KERN_INFO, "Telephony Interface Registered on major %d\n", DAHDI_MAJOR); + module_printk(KERN_INFO, "Version: %s\n", DAHDI_VERSION); + dahdi_conv_init(); + fasthdlc_precalc(); + rotate_sums(); +#ifdef CONFIG_DAHDI_WATCHDOG + watchdog_init(); +#endif + coretimer_init(); + return res; +} + +static void __exit dahdi_cleanup(void) +{ + int x; + + coretimer_cleanup(); + + CLASS_DEV_DESTROY(dahdi_class, MKDEV(DAHDI_MAJOR, 253)); /* timer */ + CLASS_DEV_DESTROY(dahdi_class, MKDEV(DAHDI_MAJOR, 254)); /* channel */ + CLASS_DEV_DESTROY(dahdi_class, MKDEV(DAHDI_MAJOR, 255)); /* pseudo */ + CLASS_DEV_DESTROY(dahdi_class, MKDEV(DAHDI_MAJOR, 0)); /* ctl */ + class_destroy(dahdi_class); + + unregister_chrdev(DAHDI_MAJOR, "dahdi"); + +#ifdef CONFIG_PROC_FS + remove_proc_entry("dahdi", NULL); +#endif + + module_printk(KERN_INFO, "Telephony Interface Unloaded\n"); + for (x = 0; x < DAHDI_TONE_ZONE_MAX; x++) { + if (tone_zones[x]) + kfree(tone_zones[x]); + } + +#ifdef CONFIG_DAHDI_WATCHDOG + watchdog_cleanup(); +#endif +} + +module_init(dahdi_init); +module_exit(dahdi_cleanup); diff -Nru dahdi-linux-2.2.0.2/drivers/dahdi/fxo_modes.h dahdi-cnet-linux-2.2.0.2/drivers/dahdi/fxo_modes.h --- dahdi-linux-2.2.0.2/drivers/dahdi/fxo_modes.h 2009-02-04 11:38:52.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/dahdi/fxo_modes.h 2009-07-24 11:35:11.000000000 -0500 @@ -50,6 +50,97 @@ .battalarm = 1000, .battthresh = 3, }, + /* (CNET) Special "FCC" zones for frequency-selective ringers */ + { .name = "FCC17", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x7f4c, /* 16.67 Hz */ + }, + { .name = "FCC25", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x7e6c, + }, + { .name = "FCC30", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x7dbb, + }, + { .name = "FCC33", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x7d34, /* 33.33 Hz */ + }, + { .name = "FCC40", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x7bfa, + }, + { .name = "FCC42", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x7b91, + }, + { .name = "FCC50", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x79bc, + }, + { .name = "FCC54", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x78b3, + }, + { .name = "FCC60", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x7702, + }, + { .name = "FCC67", + .rt = 1, + .dcv = 0x3, + .battdebounce = 64, + .battalarm = 1000, + .battthresh = 3, + .acim = 0x2, + .ring_osc = 0x74ef, /* 66.66 Hz */ + }, /* Austria, Belgium, Denmark, Finland, France, Germany, Greece, Iceland, Ireland, Italy, Luxembourg, Netherlands, Norway, Portugal, Spain, Sweden, Switzerland, and UK */ diff -Nru dahdi-linux-2.2.0.2/drivers/dahdi/Kbuild dahdi-cnet-linux-2.2.0.2/drivers/dahdi/Kbuild --- dahdi-linux-2.2.0.2/drivers/dahdi/Kbuild 2009-06-12 17:30:02.000000000 -0500 +++ dahdi-cnet-linux-2.2.0.2/drivers/dahdi/Kbuild 2009-07-24 11:35:11.000000000 -0500 @@ -32,12 +32,12 @@ # Only enable this if you think you know what you're doing. This is not # supported yet: -#obj-m += dahdi_echocan_oslec.o +obj-m += dahdi_echocan_oslec.o # # A quick and dirty way to build OSLEC, if you happened to place it # yourself in the dahdi source tree. This is experimental. See README # regarding OSLEC. -#obj-m += ../staging/echo/ +obj-m += ../staging/echo/ CFLAGS_MODULE += -I$(DAHDI_INCLUDE) -I$(src) diff -Nru dahdi-linux-2.2.0.2/drivers/dahdi/wctdm.c dahdi-cnet-linux-2.2.0.2/drivers/dahdi/wctdm.c --- dahdi-linux-2.2.0.2/drivers/dahdi/wctdm.c 2009-06-18 13:03:26.000000000 -0500 +++ dahdi-cnet-linux-2.2.0.2/drivers/dahdi/wctdm.c 2009-07-24 11:35:11.000000000 -0500 @@ -975,7 +975,10 @@ hook = (res & 1); if (hook != wc->mod[card].fxs.lastrxhook) { /* Reset the debounce (must be multiple of 4ms) */ - wc->mod[card].fxs.debounce = 8 * (4 * 8); + /* (CNET) PULSE DIALING FIX! SEE: + http://www.voip-info.org/wiki/index.php?page=Asterisk+zaptel+pulse+dialing */ + /*wc->mod[card].fxs.debounce = 8 * (4 * 8);*/ + wc->mod[card].fxs.debounce = 4 * (4 * 8); #if 0 printk(KERN_DEBUG "Resetting debounce card %d hook %d, %d\n", card, hook, wc->mod[card].fxs.debounce); #endif diff -Nru dahdi-linux-2.2.0.2/drivers/dahdi/xpp/init_card_2_30 dahdi-cnet-linux-2.2.0.2/drivers/dahdi/xpp/init_card_2_30 --- dahdi-linux-2.2.0.2/drivers/dahdi/xpp/init_card_2_30 2009-04-16 14:35:48.000000000 -0500 +++ dahdi-cnet-linux-2.2.0.2/drivers/dahdi/xpp/init_card_2_30 2009-07-24 11:35:11.000000000 -0500 @@ -138,6 +138,16 @@ # compare this data to a (possibly updated) utils/init_fxo_modes file. my $OPERMODE_DATA = " FCC reg16=01 reg26=C0 reg30=00 reg31=20 +FCC17 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=7F4C +FCC25 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=7E6C +FCC30 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=7DBB +FCC33 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=7D34 +FCC40 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=7BFA +FCC42 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=7B91 +FCC50 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=79BC +FCC54 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=78B3 +FCC60 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=7702 +FCC67 reg16=01 reg26=C0 reg30=02 reg31=20 ring_osc=74EF TBR21 reg16=00 reg26=C2 reg30=02 reg31=20 ring_osc=7E6C ring_x=023A ARGENTINA reg16=00 reg26=C0 reg30=00 reg31=20 AUSTRALIA reg16=40 reg26=30 reg30=03 reg31=20 diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/bit_operations.h dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/bit_operations.h --- dahdi-linux-2.2.0.2/drivers/staging/echo/bit_operations.h 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/bit_operations.h 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,226 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * bit_operations.h - Various bit level operations, such as bit reversal + * + * Written by Steve Underwood + * + * Copyright (C) 2006 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/*! \file */ + +#if !defined(_BIT_OPERATIONS_H_) +#define _BIT_OPERATIONS_H_ + +#if defined(__i386__) || defined(__x86_64__) +/*! \brief Find the bit position of the highest set bit in a word + \param bits The word to be searched + \return The bit number of the highest set bit, or -1 if the word is zero. */ +static inline int top_bit(unsigned int bits) +{ + int res; + + __asm__(" xorl %[res],%[res];\n" + " decl %[res];\n" + " bsrl %[bits],%[res]\n" + :[res] "=&r" (res) + :[bits] "rm"(bits) + ); + return res; +} + +/*! \brief Find the bit position of the lowest set bit in a word + \param bits The word to be searched + \return The bit number of the lowest set bit, or -1 if the word is zero. */ +static inline int bottom_bit(unsigned int bits) +{ + int res; + + __asm__(" xorl %[res],%[res];\n" + " decl %[res];\n" + " bsfl %[bits],%[res]\n" + :[res] "=&r" (res) + :[bits] "rm"(bits) + ); + return res; +} +#else +static inline int top_bit(unsigned int bits) +{ + int i; + + if (bits == 0) + return -1; + i = 0; + if (bits & 0xFFFF0000) { + bits &= 0xFFFF0000; + i += 16; + } + if (bits & 0xFF00FF00) { + bits &= 0xFF00FF00; + i += 8; + } + if (bits & 0xF0F0F0F0) { + bits &= 0xF0F0F0F0; + i += 4; + } + if (bits & 0xCCCCCCCC) { + bits &= 0xCCCCCCCC; + i += 2; + } + if (bits & 0xAAAAAAAA) { + bits &= 0xAAAAAAAA; + i += 1; + } + return i; +} + +static inline int bottom_bit(unsigned int bits) +{ + int i; + + if (bits == 0) + return -1; + i = 32; + if (bits & 0x0000FFFF) { + bits &= 0x0000FFFF; + i -= 16; + } + if (bits & 0x00FF00FF) { + bits &= 0x00FF00FF; + i -= 8; + } + if (bits & 0x0F0F0F0F) { + bits &= 0x0F0F0F0F; + i -= 4; + } + if (bits & 0x33333333) { + bits &= 0x33333333; + i -= 2; + } + if (bits & 0x55555555) { + bits &= 0x55555555; + i -= 1; + } + return i; +} +#endif + +/*! \brief Bit reverse a byte. + \param data The byte to be reversed. + \return The bit reversed version of data. */ +static inline uint8_t bit_reverse8(uint8_t x) +{ +#if defined(__i386__) || defined(__x86_64__) + /* If multiply is fast */ + return ((x * 0x0802U & 0x22110U) | (x * 0x8020U & 0x88440U)) * + 0x10101U >> 16; +#else + /* If multiply is slow, but we have a barrel shifter */ + x = (x >> 4) | (x << 4); + x = ((x & 0xCC) >> 2) | ((x & 0x33) << 2); + return ((x & 0xAA) >> 1) | ((x & 0x55) << 1); +#endif +} + +/*! \brief Bit reverse a 16 bit word. + \param data The word to be reversed. + \return The bit reversed version of data. */ +uint16_t bit_reverse16(uint16_t data); + +/*! \brief Bit reverse a 32 bit word. + \param data The word to be reversed. + \return The bit reversed version of data. */ +uint32_t bit_reverse32(uint32_t data); + +/*! \brief Bit reverse each of the four bytes in a 32 bit word. + \param data The word to be reversed. + \return The bit reversed version of data. */ +uint32_t bit_reverse_4bytes(uint32_t data); + +/*! \brief Find the number of set bits in a 32 bit word. + \param x The word to be searched. + \return The number of set bits. */ +int one_bits32(uint32_t x); + +/*! \brief Create a mask as wide as the number in a 32 bit word. + \param x The word to be searched. + \return The mask. */ +uint32_t make_mask32(uint32_t x); + +/*! \brief Create a mask as wide as the number in a 16 bit word. + \param x The word to be searched. + \return The mask. */ +uint16_t make_mask16(uint16_t x); + +/*! \brief Find the least significant one in a word, and return a word + with just that bit set. + \param x The word to be searched. + \return The word with the single set bit. */ +static inline uint32_t least_significant_one32(uint32_t x) +{ + return x & (-(int32_t) x); +} + +/*! \brief Find the most significant one in a word, and return a word + with just that bit set. + \param x The word to be searched. + \return The word with the single set bit. */ +static inline uint32_t most_significant_one32(uint32_t x) +{ +#if defined(__i386__) || defined(__x86_64__) + return 1 << top_bit(x); +#else + x = make_mask32(x); + return x ^ (x >> 1); +#endif +} + +/*! \brief Find the parity of a byte. + \param x The byte to be checked. + \return 1 for odd, or 0 for even. */ +static inline int parity8(uint8_t x) +{ + x = (x ^ (x >> 4)) & 0x0F; + return (0x6996 >> x) & 1; +} + +/*! \brief Find the parity of a 16 bit word. + \param x The word to be checked. + \return 1 for odd, or 0 for even. */ +static inline int parity16(uint16_t x) +{ + x ^= (x >> 8); + x = (x ^ (x >> 4)) & 0x0F; + return (0x6996 >> x) & 1; +} + +/*! \brief Find the parity of a 32 bit word. + \param x The word to be checked. + \return 1 for odd, or 0 for even. */ +static inline int parity32(uint32_t x) +{ + x ^= (x >> 16); + x ^= (x >> 8); + x = (x ^ (x >> 4)) & 0x0F; + return (0x6996 >> x) & 1; +} + +#endif +/*- End of file ------------------------------------------------------------*/ diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/echo.c dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/echo.c --- dahdi-linux-2.2.0.2/drivers/staging/echo/echo.c 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/echo.c 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,628 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * echo.c - A line echo canceller. This code is being developed + * against and partially complies with G168. + * + * Written by Steve Underwood + * and David Rowe + * + * Copyright (C) 2001, 2003 Steve Underwood, 2007 David Rowe + * + * Based on a bit from here, a bit from there, eye of toad, ear of + * bat, 15 years of failed attempts by David and a few fried brain + * cells. + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/*! \file */ + +/* Implementation Notes + David Rowe + April 2007 + + This code started life as Steve's NLMS algorithm with a tap + rotation algorithm to handle divergence during double talk. I + added a Geigel Double Talk Detector (DTD) [2] and performed some + G168 tests. However I had trouble meeting the G168 requirements, + especially for double talk - there were always cases where my DTD + failed, for example where near end speech was under the 6dB + threshold required for declaring double talk. + + So I tried a two path algorithm [1], which has so far given better + results. The original tap rotation/Geigel algorithm is available + in SVN http://svn.rowetel.com/software/oslec/tags/before_16bit. + It's probably possible to make it work if some one wants to put some + serious work into it. + + At present no special treatment is provided for tones, which + generally cause NLMS algorithms to diverge. Initial runs of a + subset of the G168 tests for tones (e.g ./echo_test 6) show the + current algorithm is passing OK, which is kind of surprising. The + full set of tests needs to be performed to confirm this result. + + One other interesting change is that I have managed to get the NLMS + code to work with 16 bit coefficients, rather than the original 32 + bit coefficents. This reduces the MIPs and storage required. + I evaulated the 16 bit port using g168_tests.sh and listening tests + on 4 real-world samples. + + I also attempted the implementation of a block based NLMS update + [2] but although this passes g168_tests.sh it didn't converge well + on the real-world samples. I have no idea why, perhaps a scaling + problem. The block based code is also available in SVN + http://svn.rowetel.com/software/oslec/tags/before_16bit. If this + code can be debugged, it will lead to further reduction in MIPS, as + the block update code maps nicely onto DSP instruction sets (it's a + dot product) compared to the current sample-by-sample update. + + Steve also has some nice notes on echo cancellers in echo.h + + References: + + [1] Ochiai, Areseki, and Ogihara, "Echo Canceller with Two Echo + Path Models", IEEE Transactions on communications, COM-25, + No. 6, June + 1977. + http://www.rowetel.com/images/echo/dual_path_paper.pdf + + [2] The classic, very useful paper that tells you how to + actually build a real world echo canceller: + Messerschmitt, Hedberg, Cole, Haoui, Winship, "Digital Voice + Echo Canceller with a TMS320020, + http://www.rowetel.com/images/echo/spra129.pdf + + [3] I have written a series of blog posts on this work, here is + Part 1: http://www.rowetel.com/blog/?p=18 + + [4] The source code http://svn.rowetel.com/software/oslec/ + + [5] A nice reference on LMS filters: + http://en.wikipedia.org/wiki/Least_mean_squares_filter + + Credits: + + Thanks to Steve Underwood, Jean-Marc Valin, and Ramakrishnan + Muthukrishnan for their suggestions and email discussions. Thanks + also to those people who collected echo samples for me such as + Mark, Pawel, and Pavel. +*/ + +#include /* We're doing kernel work */ +#include +#include + +#include "bit_operations.h" +#include "echo.h" + +#define MIN_TX_POWER_FOR_ADAPTION 64 +#define MIN_RX_POWER_FOR_ADAPTION 64 +#define DTD_HANGOVER 600 /* 600 samples, or 75ms */ +#define DC_LOG2BETA 3 /* log2() of DC filter Beta */ + +/*-----------------------------------------------------------------------*\ + FUNCTIONS +\*-----------------------------------------------------------------------*/ + +/* adapting coeffs using the traditional stochastic descent (N)LMS algorithm */ + +#ifdef __bfin__ +static inline void lms_adapt_bg(struct oslec_state *ec, int clean, + int shift) +{ + int i, j; + int offset1; + int offset2; + int factor; + int exp; + int16_t *phist; + int n; + + if (shift > 0) + factor = clean << shift; + else + factor = clean >> -shift; + + /* Update the FIR taps */ + + offset2 = ec->curr_pos; + offset1 = ec->taps - offset2; + phist = &ec->fir_state_bg.history[offset2]; + + /* st: and en: help us locate the assembler in echo.s */ + + /* asm("st:"); */ + n = ec->taps; + for (i = 0, j = offset2; i < n; i++, j++) { + exp = *phist++ * factor; + ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15); + } + /* asm("en:"); */ + + /* Note the asm for the inner loop above generated by Blackfin gcc + 4.1.1 is pretty good (note even parallel instructions used): + + R0 = W [P0++] (X); + R0 *= R2; + R0 = R0 + R3 (NS) || + R1 = W [P1] (X) || + nop; + R0 >>>= 15; + R0 = R0 + R1; + W [P1++] = R0; + + A block based update algorithm would be much faster but the + above can't be improved on much. Every instruction saved in + the loop above is 2 MIPs/ch! The for loop above is where the + Blackfin spends most of it's time - about 17 MIPs/ch measured + with speedtest.c with 256 taps (32ms). Write-back and + Write-through cache gave about the same performance. + */ +} + +/* + IDEAS for further optimisation of lms_adapt_bg(): + + 1/ The rounding is quite costly. Could we keep as 32 bit coeffs + then make filter pluck the MS 16-bits of the coeffs when filtering? + However this would lower potential optimisation of filter, as I + think the dual-MAC architecture requires packed 16 bit coeffs. + + 2/ Block based update would be more efficient, as per comments above, + could use dual MAC architecture. + + 3/ Look for same sample Blackfin LMS code, see if we can get dual-MAC + packing. + + 4/ Execute the whole e/c in a block of say 20ms rather than sample + by sample. Processing a few samples every ms is inefficient. +*/ + +#else +static inline void lms_adapt_bg(struct oslec_state *ec, int clean, + int shift) +{ + int i; + + int offset1; + int offset2; + int factor; + int exp; + + if (shift > 0) + factor = clean << shift; + else + factor = clean >> -shift; + + /* Update the FIR taps */ + + offset2 = ec->curr_pos; + offset1 = ec->taps - offset2; + + for (i = ec->taps - 1; i >= offset1; i--) { + exp = (ec->fir_state_bg.history[i - offset1] * factor); + ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15); + } + for (; i >= 0; i--) { + exp = (ec->fir_state_bg.history[i + offset2] * factor); + ec->fir_taps16[1][i] += (int16_t) ((exp + (1 << 14)) >> 15); + } +} +#endif + +struct oslec_state *oslec_create(int len, int adaption_mode) +{ + struct oslec_state *ec; + int i; + + ec = kzalloc(sizeof(*ec), GFP_KERNEL); + if (!ec) + return NULL; + + ec->taps = len; + ec->log2taps = top_bit(len); + ec->curr_pos = ec->taps - 1; + + for (i = 0; i < 2; i++) { + ec->fir_taps16[i] = + kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL); + if (!ec->fir_taps16[i]) + goto error_oom; + } + + fir16_create(&ec->fir_state, ec->fir_taps16[0], ec->taps); + fir16_create(&ec->fir_state_bg, ec->fir_taps16[1], ec->taps); + + for (i = 0; i < 5; i++) + ec->xvtx[i] = ec->yvtx[i] = ec->xvrx[i] = ec->yvrx[i] = 0; + + ec->cng_level = 1000; + oslec_adaption_mode(ec, adaption_mode); + + ec->snapshot = kcalloc(ec->taps, sizeof(int16_t), GFP_KERNEL); + if (!ec->snapshot) + goto error_oom; + + ec->cond_met = 0; + ec->Pstates = 0; + ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0; + ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0; + ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0; + ec->Lbgn = ec->Lbgn_acc = 0; + ec->Lbgn_upper = 200; + ec->Lbgn_upper_acc = ec->Lbgn_upper << 13; + + return ec; + +error_oom: + for (i = 0; i < 2; i++) + kfree(ec->fir_taps16[i]); + + kfree(ec); + return NULL; +} +EXPORT_SYMBOL_GPL(oslec_create); + +void oslec_free(struct oslec_state *ec) +{ + int i; + + fir16_free(&ec->fir_state); + fir16_free(&ec->fir_state_bg); + for (i = 0; i < 2; i++) + kfree(ec->fir_taps16[i]); + kfree(ec->snapshot); + kfree(ec); +} +EXPORT_SYMBOL_GPL(oslec_free); + +void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode) +{ + ec->adaption_mode = adaption_mode; +} +EXPORT_SYMBOL_GPL(oslec_adaption_mode); + +void oslec_flush(struct oslec_state *ec) +{ + int i; + + ec->Ltxacc = ec->Lrxacc = ec->Lcleanacc = ec->Lclean_bgacc = 0; + ec->Ltx = ec->Lrx = ec->Lclean = ec->Lclean_bg = 0; + ec->tx_1 = ec->tx_2 = ec->rx_1 = ec->rx_2 = 0; + + ec->Lbgn = ec->Lbgn_acc = 0; + ec->Lbgn_upper = 200; + ec->Lbgn_upper_acc = ec->Lbgn_upper << 13; + + ec->nonupdate_dwell = 0; + + fir16_flush(&ec->fir_state); + fir16_flush(&ec->fir_state_bg); + ec->fir_state.curr_pos = ec->taps - 1; + ec->fir_state_bg.curr_pos = ec->taps - 1; + for (i = 0; i < 2; i++) + memset(ec->fir_taps16[i], 0, ec->taps * sizeof(int16_t)); + + ec->curr_pos = ec->taps - 1; + ec->Pstates = 0; +} +EXPORT_SYMBOL_GPL(oslec_flush); + +void oslec_snapshot(struct oslec_state *ec) +{ + memcpy(ec->snapshot, ec->fir_taps16[0], ec->taps * sizeof(int16_t)); +} +EXPORT_SYMBOL_GPL(oslec_snapshot); + +/* Dual Path Echo Canceller ------------------------------------------------*/ + +int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx) +{ + int32_t echo_value; + int clean_bg; + int tmp, tmp1; + + /* Input scaling was found be required to prevent problems when tx + starts clipping. Another possible way to handle this would be the + filter coefficent scaling. */ + + ec->tx = tx; + ec->rx = rx; + tx >>= 1; + rx >>= 1; + + /* + Filter DC, 3dB point is 160Hz (I think), note 32 bit precision required + otherwise values do not track down to 0. Zero at DC, Pole at (1-Beta) + only real axis. Some chip sets (like Si labs) don't need + this, but something like a $10 X100P card does. Any DC really slows + down convergence. + + Note: removes some low frequency from the signal, this reduces + the speech quality when listening to samples through headphones + but may not be obvious through a telephone handset. + + Note that the 3dB frequency in radians is approx Beta, e.g. for + Beta = 2^(-3) = 0.125, 3dB freq is 0.125 rads = 159Hz. + */ + + if (ec->adaption_mode & ECHO_CAN_USE_RX_HPF) { + tmp = rx << 15; +#if 1 + /* Make sure the gain of the HPF is 1.0. This can still saturate a little under + impulse conditions, and it might roll to 32768 and need clipping on sustained peak + level signals. However, the scale of such clipping is small, and the error due to + any saturation should not markedly affect the downstream processing. */ + tmp -= (tmp >> 4); +#endif + ec->rx_1 += -(ec->rx_1 >> DC_LOG2BETA) + tmp - ec->rx_2; + + /* hard limit filter to prevent clipping. Note that at this stage + rx should be limited to +/- 16383 due to right shift above */ + tmp1 = ec->rx_1 >> 15; + if (tmp1 > 16383) + tmp1 = 16383; + if (tmp1 < -16383) + tmp1 = -16383; + rx = tmp1; + ec->rx_2 = tmp; + } + + /* Block average of power in the filter states. Used for + adaption power calculation. */ + + { + int new, old; + + /* efficient "out with the old and in with the new" algorithm so + we don't have to recalculate over the whole block of + samples. */ + new = (int)tx * (int)tx; + old = (int)ec->fir_state.history[ec->fir_state.curr_pos] * + (int)ec->fir_state.history[ec->fir_state.curr_pos]; + ec->Pstates += + ((new - old) + (1 << ec->log2taps)) >> ec->log2taps; + if (ec->Pstates < 0) + ec->Pstates = 0; + } + + /* Calculate short term average levels using simple single pole IIRs */ + + ec->Ltxacc += abs(tx) - ec->Ltx; + ec->Ltx = (ec->Ltxacc + (1 << 4)) >> 5; + ec->Lrxacc += abs(rx) - ec->Lrx; + ec->Lrx = (ec->Lrxacc + (1 << 4)) >> 5; + + /* Foreground filter --------------------------------------------------- */ + + ec->fir_state.coeffs = ec->fir_taps16[0]; + echo_value = fir16(&ec->fir_state, tx); + ec->clean = rx - echo_value; + ec->Lcleanacc += abs(ec->clean) - ec->Lclean; + ec->Lclean = (ec->Lcleanacc + (1 << 4)) >> 5; + + /* Background filter --------------------------------------------------- */ + + echo_value = fir16(&ec->fir_state_bg, tx); + clean_bg = rx - echo_value; + ec->Lclean_bgacc += abs(clean_bg) - ec->Lclean_bg; + ec->Lclean_bg = (ec->Lclean_bgacc + (1 << 4)) >> 5; + + /* Background Filter adaption ----------------------------------------- */ + + /* Almost always adap bg filter, just simple DT and energy + detection to minimise adaption in cases of strong double talk. + However this is not critical for the dual path algorithm. + */ + ec->factor = 0; + ec->shift = 0; + if ((ec->nonupdate_dwell == 0)) { + int P, logP, shift; + + /* Determine: + + f = Beta * clean_bg_rx/P ------ (1) + + where P is the total power in the filter states. + + The Boffins have shown that if we obey (1) we converge + quickly and avoid instability. + + The correct factor f must be in Q30, as this is the fixed + point format required by the lms_adapt_bg() function, + therefore the scaled version of (1) is: + + (2^30) * f = (2^30) * Beta * clean_bg_rx/P + factor = (2^30) * Beta * clean_bg_rx/P ----- (2) + + We have chosen Beta = 0.25 by experiment, so: + + factor = (2^30) * (2^-2) * clean_bg_rx/P + + (30 - 2 - log2(P)) + factor = clean_bg_rx 2 ----- (3) + + To avoid a divide we approximate log2(P) as top_bit(P), + which returns the position of the highest non-zero bit in + P. This approximation introduces an error as large as a + factor of 2, but the algorithm seems to handle it OK. + + Come to think of it a divide may not be a big deal on a + modern DSP, so its probably worth checking out the cycles + for a divide versus a top_bit() implementation. + */ + + P = MIN_TX_POWER_FOR_ADAPTION + ec->Pstates; + logP = top_bit(P) + ec->log2taps; + shift = 30 - 2 - logP; + ec->shift = shift; + + lms_adapt_bg(ec, clean_bg, shift); + } + + /* very simple DTD to make sure we dont try and adapt with strong + near end speech */ + + ec->adapt = 0; + if ((ec->Lrx > MIN_RX_POWER_FOR_ADAPTION) && (ec->Lrx > ec->Ltx)) + ec->nonupdate_dwell = DTD_HANGOVER; + if (ec->nonupdate_dwell) + ec->nonupdate_dwell--; + + /* Transfer logic ------------------------------------------------------ */ + + /* These conditions are from the dual path paper [1], I messed with + them a bit to improve performance. */ + + if ((ec->adaption_mode & ECHO_CAN_USE_ADAPTION) && + (ec->nonupdate_dwell == 0) && + /* (ec->Lclean_bg < 0.875*ec->Lclean) */ + (8 * ec->Lclean_bg < 7 * ec->Lclean) && + /* (ec->Lclean_bg < 0.125*ec->Ltx) */ + (8 * ec->Lclean_bg < ec->Ltx)) { + if (ec->cond_met == 6) { + /* BG filter has had better results for 6 consecutive samples */ + ec->adapt = 1; + memcpy(ec->fir_taps16[0], ec->fir_taps16[1], + ec->taps * sizeof(int16_t)); + } else + ec->cond_met++; + } else + ec->cond_met = 0; + + /* Non-Linear Processing --------------------------------------------------- */ + + ec->clean_nlp = ec->clean; + if (ec->adaption_mode & ECHO_CAN_USE_NLP) { + /* Non-linear processor - a fancy way to say "zap small signals, to avoid + residual echo due to (uLaw/ALaw) non-linearity in the channel.". */ + + if ((16 * ec->Lclean < ec->Ltx)) { + /* Our e/c has improved echo by at least 24 dB (each factor of 2 is 6dB, + so 2*2*2*2=16 is the same as 6+6+6+6=24dB) */ + if (ec->adaption_mode & ECHO_CAN_USE_CNG) { + ec->cng_level = ec->Lbgn; + + /* Very elementary comfort noise generation. Just random + numbers rolled off very vaguely Hoth-like. DR: This + noise doesn't sound quite right to me - I suspect there + are some overlfow issues in the filtering as it's too + "crackly". TODO: debug this, maybe just play noise at + high level or look at spectrum. + */ + + ec->cng_rndnum = + 1664525U * ec->cng_rndnum + 1013904223U; + ec->cng_filter = + ((ec->cng_rndnum & 0xFFFF) - 32768 + + 5 * ec->cng_filter) >> 3; + ec->clean_nlp = + (ec->cng_filter * ec->cng_level * 8) >> 14; + + } else if (ec->adaption_mode & ECHO_CAN_USE_CLIP) { + /* This sounds much better than CNG */ + if (ec->clean_nlp > ec->Lbgn) + ec->clean_nlp = ec->Lbgn; + if (ec->clean_nlp < -ec->Lbgn) + ec->clean_nlp = -ec->Lbgn; + } else { + /* just mute the residual, doesn't sound very good, used mainly + in G168 tests */ + ec->clean_nlp = 0; + } + } else { + /* Background noise estimator. I tried a few algorithms + here without much luck. This very simple one seems to + work best, we just average the level using a slow (1 sec + time const) filter if the current level is less than a + (experimentally derived) constant. This means we dont + include high level signals like near end speech. When + combined with CNG or especially CLIP seems to work OK. + */ + if (ec->Lclean < 40) { + ec->Lbgn_acc += abs(ec->clean) - ec->Lbgn; + ec->Lbgn = (ec->Lbgn_acc + (1 << 11)) >> 12; + } + } + } + + /* Roll around the taps buffer */ + if (ec->curr_pos <= 0) + ec->curr_pos = ec->taps; + ec->curr_pos--; + + if (ec->adaption_mode & ECHO_CAN_DISABLE) + ec->clean_nlp = rx; + + /* Output scaled back up again to match input scaling */ + + return (int16_t) ec->clean_nlp << 1; +} +EXPORT_SYMBOL_GPL(oslec_update); + +/* This function is seperated from the echo canceller is it is usually called + as part of the tx process. See rx HP (DC blocking) filter above, it's + the same design. + + Some soft phones send speech signals with a lot of low frequency + energy, e.g. down to 20Hz. This can make the hybrid non-linear + which causes the echo canceller to fall over. This filter can help + by removing any low frequency before it gets to the tx port of the + hybrid. + + It can also help by removing and DC in the tx signal. DC is bad + for LMS algorithms. + + This is one of the classic DC removal filters, adjusted to provide sufficient + bass rolloff to meet the above requirement to protect hybrids from things that + upset them. The difference between successive samples produces a lousy HPF, and + then a suitably placed pole flattens things out. The final result is a nicely + rolled off bass end. The filtering is implemented with extended fractional + precision, which noise shapes things, giving very clean DC removal. +*/ + +int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx) +{ + int tmp, tmp1; + + if (ec->adaption_mode & ECHO_CAN_USE_TX_HPF) { + tmp = tx << 15; +#if 1 + /* Make sure the gain of the HPF is 1.0. The first can still saturate a little under + impulse conditions, and it might roll to 32768 and need clipping on sustained peak + level signals. However, the scale of such clipping is small, and the error due to + any saturation should not markedly affect the downstream processing. */ + tmp -= (tmp >> 4); +#endif + ec->tx_1 += -(ec->tx_1 >> DC_LOG2BETA) + tmp - ec->tx_2; + tmp1 = ec->tx_1 >> 15; + if (tmp1 > 32767) + tmp1 = 32767; + if (tmp1 < -32767) + tmp1 = -32767; + tx = tmp1; + ec->tx_2 = tmp; + } + + return tx; +} +EXPORT_SYMBOL_GPL(oslec_hpf_tx); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("David Rowe"); +MODULE_DESCRIPTION("Open Source Line Echo Canceller"); +MODULE_VERSION("0.3.0"); diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/echo.h dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/echo.h --- dahdi-linux-2.2.0.2/drivers/staging/echo/echo.h 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/echo.h 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,170 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * echo.c - A line echo canceller. This code is being developed + * against and partially complies with G168. + * + * Written by Steve Underwood + * and David Rowe + * + * Copyright (C) 2001 Steve Underwood and 2007 David Rowe + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __ECHO_H +#define __ECHO_H + +/*! \page echo_can_page Line echo cancellation for voice + +\section echo_can_page_sec_1 What does it do? +This module aims to provide G.168-2002 compliant echo cancellation, to remove +electrical echoes (e.g. from 2-4 wire hybrids) from voice calls. + +\section echo_can_page_sec_2 How does it work? +The heart of the echo cancellor is FIR filter. This is adapted to match the +echo impulse response of the telephone line. It must be long enough to +adequately cover the duration of that impulse response. The signal transmitted +to the telephone line is passed through the FIR filter. Once the FIR is +properly adapted, the resulting output is an estimate of the echo signal +received from the line. This is subtracted from the received signal. The result +is an estimate of the signal which originated at the far end of the line, free +from echos of our own transmitted signal. + +The least mean squares (LMS) algorithm is attributed to Widrow and Hoff, and +was introduced in 1960. It is the commonest form of filter adaption used in +things like modem line equalisers and line echo cancellers. There it works very +well. However, it only works well for signals of constant amplitude. It works +very poorly for things like speech echo cancellation, where the signal level +varies widely. This is quite easy to fix. If the signal level is normalised - +similar to applying AGC - LMS can work as well for a signal of varying +amplitude as it does for a modem signal. This normalised least mean squares +(NLMS) algorithm is the commonest one used for speech echo cancellation. Many +other algorithms exist - e.g. RLS (essentially the same as Kalman filtering), +FAP, etc. Some perform significantly better than NLMS. However, factors such +as computational complexity and patents favour the use of NLMS. + +A simple refinement to NLMS can improve its performance with speech. NLMS tends +to adapt best to the strongest parts of a signal. If the signal is white noise, +the NLMS algorithm works very well. However, speech has more low frequency than +high frequency content. Pre-whitening (i.e. filtering the signal to flatten its +spectrum) the echo signal improves the adapt rate for speech, and ensures the +final residual signal is not heavily biased towards high frequencies. A very +low complexity filter is adequate for this, so pre-whitening adds little to the +compute requirements of the echo canceller. + +An FIR filter adapted using pre-whitened NLMS performs well, provided certain +conditions are met: + + - The transmitted signal has poor self-correlation. + - There is no signal being generated within the environment being + cancelled. + +The difficulty is that neither of these can be guaranteed. + +If the adaption is performed while transmitting noise (or something fairly +noise like, such as voice) the adaption works very well. If the adaption is +performed while transmitting something highly correlative (typically narrow +band energy such as signalling tones or DTMF), the adaption can go seriously +wrong. The reason is there is only one solution for the adaption on a near +random signal - the impulse response of the line. For a repetitive signal, +there are any number of solutions which converge the adaption, and nothing +guides the adaption to choose the generalised one. Allowing an untrained +canceller to converge on this kind of narrowband energy probably a good thing, +since at least it cancels the tones. Allowing a well converged canceller to +continue converging on such energy is just a way to ruin its generalised +adaption. A narrowband detector is needed, so adapation can be suspended at +appropriate times. + +The adaption process is based on trying to eliminate the received signal. When +there is any signal from within the environment being cancelled it may upset +the adaption process. Similarly, if the signal we are transmitting is small, +noise may dominate and disturb the adaption process. If we can ensure that the +adaption is only performed when we are transmitting a significant signal level, +and the environment is not, things will be OK. Clearly, it is easy to tell when +we are sending a significant signal. Telling, if the environment is generating +a significant signal, and doing it with sufficient speed that the adaption will +not have diverged too much more we stop it, is a little harder. + +The key problem in detecting when the environment is sourcing significant +energy is that we must do this very quickly. Given a reasonably long sample of +the received signal, there are a number of strategies which may be used to +assess whether that signal contains a strong far end component. However, by the +time that assessment is complete the far end signal will have already caused +major mis-convergence in the adaption process. An assessment algorithm is +needed which produces a fairly accurate result from a very short burst of far +end energy. + +\section echo_can_page_sec_3 How do I use it? +The echo cancellor processes both the transmit and receive streams sample by +sample. The processing function is not declared inline. Unfortunately, +cancellation requires many operations per sample, so the call overhead is only +a minor burden. +*/ + +#include "fir.h" +#include "oslec.h" + +/*! + G.168 echo canceller descriptor. This defines the working state for a line + echo canceller. +*/ +struct oslec_state { + int16_t tx, rx; + int16_t clean; + int16_t clean_nlp; + + int nonupdate_dwell; + int curr_pos; + int taps; + int log2taps; + int adaption_mode; + + int cond_met; + int32_t Pstates; + int16_t adapt; + int32_t factor; + int16_t shift; + + /* Average levels and averaging filter states */ + int Ltxacc, Lrxacc, Lcleanacc, Lclean_bgacc; + int Ltx, Lrx; + int Lclean; + int Lclean_bg; + int Lbgn, Lbgn_acc, Lbgn_upper, Lbgn_upper_acc; + + /* foreground and background filter states */ + struct fir16_state_t fir_state; + struct fir16_state_t fir_state_bg; + int16_t *fir_taps16[2]; + + /* DC blocking filter states */ + int tx_1, tx_2, rx_1, rx_2; + + /* optional High Pass Filter states */ + int32_t xvtx[5], yvtx[5]; + int32_t xvrx[5], yvrx[5]; + + /* Parameters for the optional Hoth noise generator */ + int cng_level; + int cng_rndnum; + int cng_filter; + + /* snapshot sample of coeffs used for development */ + int16_t *snapshot; +}; + +#endif /* __ECHO_H */ diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/fir.h dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/fir.h --- dahdi-linux-2.2.0.2/drivers/staging/echo/fir.h 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/fir.h 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,293 @@ +/* + * SpanDSP - a series of DSP components for telephony + * + * fir.h - General telephony FIR routines + * + * Written by Steve Underwood + * + * Copyright (C) 2002 Steve Underwood + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +/*! \page fir_page FIR filtering +\section fir_page_sec_1 What does it do? +???. + +\section fir_page_sec_2 How does it work? +???. +*/ + +#if !defined(_FIR_H_) +#define _FIR_H_ + +/* + Blackfin NOTES & IDEAS: + + A simple dot product function is used to implement the filter. This performs + just one MAC/cycle which is inefficient but was easy to implement as a first + pass. The current Blackfin code also uses an unrolled form of the filter + history to avoid 0 length hardware loop issues. This is wasteful of + memory. + + Ideas for improvement: + + 1/ Rewrite filter for dual MAC inner loop. The issue here is handling + history sample offsets that are 16 bit aligned - the dual MAC needs + 32 bit aligmnent. There are some good examples in libbfdsp. + + 2/ Use the hardware circular buffer facility tohalve memory usage. + + 3/ Consider using internal memory. + + Using less memory might also improve speed as cache misses will be + reduced. A drop in MIPs and memory approaching 50% should be + possible. + + The foreground and background filters currenlty use a total of + about 10 MIPs/ch as measured with speedtest.c on a 256 TAP echo + can. +*/ + +#if defined(USE_MMX) || defined(USE_SSE2) +#include "mmx.h" +#endif + +/*! + 16 bit integer FIR descriptor. This defines the working state for a single + instance of an FIR filter using 16 bit integer coefficients. +*/ +struct fir16_state_t { + int taps; + int curr_pos; + const int16_t *coeffs; + int16_t *history; +}; + +/*! + 32 bit integer FIR descriptor. This defines the working state for a single + instance of an FIR filter using 32 bit integer coefficients, and filtering + 16 bit integer data. +*/ +struct fir32_state_t { + int taps; + int curr_pos; + const int32_t *coeffs; + int16_t *history; +}; + +/*! + Floating point FIR descriptor. This defines the working state for a single + instance of an FIR filter using floating point coefficients and data. +*/ +struct fir_float_state_t { + int taps; + int curr_pos; + const float *coeffs; + float *history; +}; + +static inline const int16_t *fir16_create(struct fir16_state_t *fir, + const int16_t *coeffs, int taps) +{ + fir->taps = taps; + fir->curr_pos = taps - 1; + fir->coeffs = coeffs; +#if defined(USE_MMX) || defined(USE_SSE2) || defined(__bfin__) + fir->history = kcalloc(2 * taps, sizeof(int16_t), GFP_KERNEL); +#else + fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL); +#endif + return fir->history; +} + +static inline void fir16_flush(struct fir16_state_t *fir) +{ +#if defined(USE_MMX) || defined(USE_SSE2) || defined(__bfin__) + memset(fir->history, 0, 2 * fir->taps * sizeof(int16_t)); +#else + memset(fir->history, 0, fir->taps * sizeof(int16_t)); +#endif +} + +static inline void fir16_free(struct fir16_state_t *fir) +{ + kfree(fir->history); +} + +#ifdef __bfin__ +static inline int32_t dot_asm(short *x, short *y, int len) +{ + int dot; + + len--; + + __asm__("I0 = %1;\n\t" + "I1 = %2;\n\t" + "A0 = 0;\n\t" + "R0.L = W[I0++] || R1.L = W[I1++];\n\t" + "LOOP dot%= LC0 = %3;\n\t" + "LOOP_BEGIN dot%=;\n\t" + "A0 += R0.L * R1.L (IS) || R0.L = W[I0++] || R1.L = W[I1++];\n\t" + "LOOP_END dot%=;\n\t" + "A0 += R0.L*R1.L (IS);\n\t" + "R0 = A0;\n\t" + "%0 = R0;\n\t" + : "=&d"(dot) + : "a"(x), "a"(y), "a"(len) + : "I0", "I1", "A1", "A0", "R0", "R1" + ); + + return dot; +} +#endif + +static inline int16_t fir16(struct fir16_state_t *fir, int16_t sample) +{ + int32_t y; +#if defined(USE_MMX) + int i; + union mmx_t *mmx_coeffs; + union mmx_t *mmx_hist; + + fir->history[fir->curr_pos] = sample; + fir->history[fir->curr_pos + fir->taps] = sample; + + mmx_coeffs = (union mmx_t *)fir->coeffs; + mmx_hist = (union mmx_t *)&fir->history[fir->curr_pos]; + i = fir->taps; + pxor_r2r(mm4, mm4); + /* 8 samples per iteration, so the filter must be a multiple of 8 long. */ + while (i > 0) { + movq_m2r(mmx_coeffs[0], mm0); + movq_m2r(mmx_coeffs[1], mm2); + movq_m2r(mmx_hist[0], mm1); + movq_m2r(mmx_hist[1], mm3); + mmx_coeffs += 2; + mmx_hist += 2; + pmaddwd_r2r(mm1, mm0); + pmaddwd_r2r(mm3, mm2); + paddd_r2r(mm0, mm4); + paddd_r2r(mm2, mm4); + i -= 8; + } + movq_r2r(mm4, mm0); + psrlq_i2r(32, mm0); + paddd_r2r(mm0, mm4); + movd_r2m(mm4, y); + emms(); +#elif defined(USE_SSE2) + int i; + union xmm_t *xmm_coeffs; + union xmm_t *xmm_hist; + + fir->history[fir->curr_pos] = sample; + fir->history[fir->curr_pos + fir->taps] = sample; + + xmm_coeffs = (union xmm_t *)fir->coeffs; + xmm_hist = (union xmm_t *)&fir->history[fir->curr_pos]; + i = fir->taps; + pxor_r2r(xmm4, xmm4); + /* 16 samples per iteration, so the filter must be a multiple of 16 long. */ + while (i > 0) { + movdqu_m2r(xmm_coeffs[0], xmm0); + movdqu_m2r(xmm_coeffs[1], xmm2); + movdqu_m2r(xmm_hist[0], xmm1); + movdqu_m2r(xmm_hist[1], xmm3); + xmm_coeffs += 2; + xmm_hist += 2; + pmaddwd_r2r(xmm1, xmm0); + pmaddwd_r2r(xmm3, xmm2); + paddd_r2r(xmm0, xmm4); + paddd_r2r(xmm2, xmm4); + i -= 16; + } + movdqa_r2r(xmm4, xmm0); + psrldq_i2r(8, xmm0); + paddd_r2r(xmm0, xmm4); + movdqa_r2r(xmm4, xmm0); + psrldq_i2r(4, xmm0); + paddd_r2r(xmm0, xmm4); + movd_r2m(xmm4, y); +#elif defined(__bfin__) + fir->history[fir->curr_pos] = sample; + fir->history[fir->curr_pos + fir->taps] = sample; + y = dot_asm((int16_t *) fir->coeffs, &fir->history[fir->curr_pos], + fir->taps); +#else + int i; + int offset1; + int offset2; + + fir->history[fir->curr_pos] = sample; + + offset2 = fir->curr_pos; + offset1 = fir->taps - offset2; + y = 0; + for (i = fir->taps - 1; i >= offset1; i--) + y += fir->coeffs[i] * fir->history[i - offset1]; + for (; i >= 0; i--) + y += fir->coeffs[i] * fir->history[i + offset2]; +#endif + if (fir->curr_pos <= 0) + fir->curr_pos = fir->taps; + fir->curr_pos--; + return (int16_t) (y >> 15); +} + +static inline const int16_t *fir32_create(struct fir32_state_t *fir, + const int32_t *coeffs, int taps) +{ + fir->taps = taps; + fir->curr_pos = taps - 1; + fir->coeffs = coeffs; + fir->history = kcalloc(taps, sizeof(int16_t), GFP_KERNEL); + return fir->history; +} + +static inline void fir32_flush(struct fir32_state_t *fir) +{ + memset(fir->history, 0, fir->taps * sizeof(int16_t)); +} + +static inline void fir32_free(struct fir32_state_t *fir) +{ + kfree(fir->history); +} + +static inline int16_t fir32(struct fir32_state_t *fir, int16_t sample) +{ + int i; + int32_t y; + int offset1; + int offset2; + + fir->history[fir->curr_pos] = sample; + offset2 = fir->curr_pos; + offset1 = fir->taps - offset2; + y = 0; + for (i = fir->taps - 1; i >= offset1; i--) + y += fir->coeffs[i] * fir->history[i - offset1]; + for (; i >= 0; i--) + y += fir->coeffs[i] * fir->history[i + offset2]; + if (fir->curr_pos <= 0) + fir->curr_pos = fir->taps; + fir->curr_pos--; + return (int16_t) (y >> 15); +} + +#endif +/*- End of file ------------------------------------------------------------*/ diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/Kbuild dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/Kbuild --- dahdi-linux-2.2.0.2/drivers/staging/echo/Kbuild 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/Kbuild 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1 @@ +obj-m += echo.o diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/Kconfig dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/Kconfig --- dahdi-linux-2.2.0.2/drivers/staging/echo/Kconfig 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/Kconfig 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,9 @@ +config ECHO + tristate "Line Echo Canceller support" + default n + ---help--- + This driver provides line echo cancelling support for mISDN and + Zaptel drivers. + + To compile this driver as a module, choose M here. The module + will be called echo. diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/Makefile dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/Makefile --- dahdi-linux-2.2.0.2/drivers/staging/echo/Makefile 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/Makefile 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1 @@ +obj-$(CONFIG_ECHO) += echo.o diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/mmx.h dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/mmx.h --- dahdi-linux-2.2.0.2/drivers/staging/echo/mmx.h 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/mmx.h 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,281 @@ +/* + * mmx.h + * Copyright (C) 1997-2001 H. Dietz and R. Fisher + * + * This file is part of FFmpeg. + * + * FFmpeg is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * FFmpeg is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with FFmpeg; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef AVCODEC_I386MMX_H +#define AVCODEC_I386MMX_H + +/* + * The type of an value that fits in an MMX register (note that long + * long constant values MUST be suffixed by LL and unsigned long long + * values by ULL, lest they be truncated by the compiler) + */ + +union mmx_t { + long long q; /* Quadword (64-bit) value */ + unsigned long long uq; /* Unsigned Quadword */ + int d[2]; /* 2 Doubleword (32-bit) values */ + unsigned int ud[2]; /* 2 Unsigned Doubleword */ + short w[4]; /* 4 Word (16-bit) values */ + unsigned short uw[4]; /* 4 Unsigned Word */ + char b[8]; /* 8 Byte (8-bit) values */ + unsigned char ub[8]; /* 8 Unsigned Byte */ + float s[2]; /* Single-precision (32-bit) value */ +}; /* On an 8-byte (64-bit) boundary */ + +/* SSE registers */ +union xmm_t { + char b[16]; +}; + +#define mmx_i2r(op, imm, reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "i" (imm)) + +#define mmx_m2r(op, mem, reg) \ + __asm__ __volatile__ (#op " %0, %%" #reg \ + : /* nothing */ \ + : "m" (mem)) + +#define mmx_r2m(op, reg, mem) \ + __asm__ __volatile__ (#op " %%" #reg ", %0" \ + : "=m" (mem) \ + : /* nothing */) + +#define mmx_r2r(op, regs, regd) \ + __asm__ __volatile__ (#op " %" #regs ", %" #regd) + +#define emms() __asm__ __volatile__ ("emms") + +#define movd_m2r(var, reg) mmx_m2r(movd, var, reg) +#define movd_r2m(reg, var) mmx_r2m(movd, reg, var) +#define movd_r2r(regs, regd) mmx_r2r(movd, regs, regd) + +#define movq_m2r(var, reg) mmx_m2r(movq, var, reg) +#define movq_r2m(reg, var) mmx_r2m(movq, reg, var) +#define movq_r2r(regs, regd) mmx_r2r(movq, regs, regd) + +#define packssdw_m2r(var, reg) mmx_m2r(packssdw, var, reg) +#define packssdw_r2r(regs, regd) mmx_r2r(packssdw, regs, regd) +#define packsswb_m2r(var, reg) mmx_m2r(packsswb, var, reg) +#define packsswb_r2r(regs, regd) mmx_r2r(packsswb, regs, regd) + +#define packuswb_m2r(var, reg) mmx_m2r(packuswb, var, reg) +#define packuswb_r2r(regs, regd) mmx_r2r(packuswb, regs, regd) + +#define paddb_m2r(var, reg) mmx_m2r(paddb, var, reg) +#define paddb_r2r(regs, regd) mmx_r2r(paddb, regs, regd) +#define paddd_m2r(var, reg) mmx_m2r(paddd, var, reg) +#define paddd_r2r(regs, regd) mmx_r2r(paddd, regs, regd) +#define paddw_m2r(var, reg) mmx_m2r(paddw, var, reg) +#define paddw_r2r(regs, regd) mmx_r2r(paddw, regs, regd) + +#define paddsb_m2r(var, reg) mmx_m2r(paddsb, var, reg) +#define paddsb_r2r(regs, regd) mmx_r2r(paddsb, regs, regd) +#define paddsw_m2r(var, reg) mmx_m2r(paddsw, var, reg) +#define paddsw_r2r(regs, regd) mmx_r2r(paddsw, regs, regd) + +#define paddusb_m2r(var, reg) mmx_m2r(paddusb, var, reg) +#define paddusb_r2r(regs, regd) mmx_r2r(paddusb, regs, regd) +#define paddusw_m2r(var, reg) mmx_m2r(paddusw, var, reg) +#define paddusw_r2r(regs, regd) mmx_r2r(paddusw, regs, regd) + +#define pand_m2r(var, reg) mmx_m2r(pand, var, reg) +#define pand_r2r(regs, regd) mmx_r2r(pand, regs, regd) + +#define pandn_m2r(var, reg) mmx_m2r(pandn, var, reg) +#define pandn_r2r(regs, regd) mmx_r2r(pandn, regs, regd) + +#define pcmpeqb_m2r(var, reg) mmx_m2r(pcmpeqb, var, reg) +#define pcmpeqb_r2r(regs, regd) mmx_r2r(pcmpeqb, regs, regd) +#define pcmpeqd_m2r(var, reg) mmx_m2r(pcmpeqd, var, reg) +#define pcmpeqd_r2r(regs, regd) mmx_r2r(pcmpeqd, regs, regd) +#define pcmpeqw_m2r(var, reg) mmx_m2r(pcmpeqw, var, reg) +#define pcmpeqw_r2r(regs, regd) mmx_r2r(pcmpeqw, regs, regd) + +#define pcmpgtb_m2r(var, reg) mmx_m2r(pcmpgtb, var, reg) +#define pcmpgtb_r2r(regs, regd) mmx_r2r(pcmpgtb, regs, regd) +#define pcmpgtd_m2r(var, reg) mmx_m2r(pcmpgtd, var, reg) +#define pcmpgtd_r2r(regs, regd) mmx_r2r(pcmpgtd, regs, regd) +#define pcmpgtw_m2r(var, reg) mmx_m2r(pcmpgtw, var, reg) +#define pcmpgtw_r2r(regs, regd) mmx_r2r(pcmpgtw, regs, regd) + +#define pmaddwd_m2r(var, reg) mmx_m2r(pmaddwd, var, reg) +#define pmaddwd_r2r(regs, regd) mmx_r2r(pmaddwd, regs, regd) + +#define pmulhw_m2r(var, reg) mmx_m2r(pmulhw, var, reg) +#define pmulhw_r2r(regs, regd) mmx_r2r(pmulhw, regs, regd) + +#define pmullw_m2r(var, reg) mmx_m2r(pmullw, var, reg) +#define pmullw_r2r(regs, regd) mmx_r2r(pmullw, regs, regd) + +#define por_m2r(var, reg) mmx_m2r(por, var, reg) +#define por_r2r(regs, regd) mmx_r2r(por, regs, regd) + +#define pslld_i2r(imm, reg) mmx_i2r(pslld, imm, reg) +#define pslld_m2r(var, reg) mmx_m2r(pslld, var, reg) +#define pslld_r2r(regs, regd) mmx_r2r(pslld, regs, regd) +#define psllq_i2r(imm, reg) mmx_i2r(psllq, imm, reg) +#define psllq_m2r(var, reg) mmx_m2r(psllq, var, reg) +#define psllq_r2r(regs, regd) mmx_r2r(psllq, regs, regd) +#define psllw_i2r(imm, reg) mmx_i2r(psllw, imm, reg) +#define psllw_m2r(var, reg) mmx_m2r(psllw, var, reg) +#define psllw_r2r(regs, regd) mmx_r2r(psllw, regs, regd) + +#define psrad_i2r(imm, reg) mmx_i2r(psrad, imm, reg) +#define psrad_m2r(var, reg) mmx_m2r(psrad, var, reg) +#define psrad_r2r(regs, regd) mmx_r2r(psrad, regs, regd) +#define psraw_i2r(imm, reg) mmx_i2r(psraw, imm, reg) +#define psraw_m2r(var, reg) mmx_m2r(psraw, var, reg) +#define psraw_r2r(regs, regd) mmx_r2r(psraw, regs, regd) + +#define psrld_i2r(imm, reg) mmx_i2r(psrld, imm, reg) +#define psrld_m2r(var, reg) mmx_m2r(psrld, var, reg) +#define psrld_r2r(regs, regd) mmx_r2r(psrld, regs, regd) +#define psrlq_i2r(imm, reg) mmx_i2r(psrlq, imm, reg) +#define psrlq_m2r(var, reg) mmx_m2r(psrlq, var, reg) +#define psrlq_r2r(regs, regd) mmx_r2r(psrlq, regs, regd) +#define psrlw_i2r(imm, reg) mmx_i2r(psrlw, imm, reg) +#define psrlw_m2r(var, reg) mmx_m2r(psrlw, var, reg) +#define psrlw_r2r(regs, regd) mmx_r2r(psrlw, regs, regd) + +#define psubb_m2r(var, reg) mmx_m2r(psubb, var, reg) +#define psubb_r2r(regs, regd) mmx_r2r(psubb, regs, regd) +#define psubd_m2r(var, reg) mmx_m2r(psubd, var, reg) +#define psubd_r2r(regs, regd) mmx_r2r(psubd, regs, regd) +#define psubw_m2r(var, reg) mmx_m2r(psubw, var, reg) +#define psubw_r2r(regs, regd) mmx_r2r(psubw, regs, regd) + +#define psubsb_m2r(var, reg) mmx_m2r(psubsb, var, reg) +#define psubsb_r2r(regs, regd) mmx_r2r(psubsb, regs, regd) +#define psubsw_m2r(var, reg) mmx_m2r(psubsw, var, reg) +#define psubsw_r2r(regs, regd) mmx_r2r(psubsw, regs, regd) + +#define psubusb_m2r(var, reg) mmx_m2r(psubusb, var, reg) +#define psubusb_r2r(regs, regd) mmx_r2r(psubusb, regs, regd) +#define psubusw_m2r(var, reg) mmx_m2r(psubusw, var, reg) +#define psubusw_r2r(regs, regd) mmx_r2r(psubusw, regs, regd) + +#define punpckhbw_m2r(var, reg) mmx_m2r(punpckhbw, var, reg) +#define punpckhbw_r2r(regs, regd) mmx_r2r(punpckhbw, regs, regd) +#define punpckhdq_m2r(var, reg) mmx_m2r(punpckhdq, var, reg) +#define punpckhdq_r2r(regs, regd) mmx_r2r(punpckhdq, regs, regd) +#define punpckhwd_m2r(var, reg) mmx_m2r(punpckhwd, var, reg) +#define punpckhwd_r2r(regs, regd) mmx_r2r(punpckhwd, regs, regd) + +#define punpcklbw_m2r(var, reg) mmx_m2r(punpcklbw, var, reg) +#define punpcklbw_r2r(regs, regd) mmx_r2r(punpcklbw, regs, regd) +#define punpckldq_m2r(var, reg) mmx_m2r(punpckldq, var, reg) +#define punpckldq_r2r(regs, regd) mmx_r2r(punpckldq, regs, regd) +#define punpcklwd_m2r(var, reg) mmx_m2r(punpcklwd, var, reg) +#define punpcklwd_r2r(regs, regd) mmx_r2r(punpcklwd, regs, regd) + +#define pxor_m2r(var, reg) mmx_m2r(pxor, var, reg) +#define pxor_r2r(regs, regd) mmx_r2r(pxor, regs, regd) + +/* 3DNOW extensions */ + +#define pavgusb_m2r(var, reg) mmx_m2r(pavgusb, var, reg) +#define pavgusb_r2r(regs, regd) mmx_r2r(pavgusb, regs, regd) + +/* AMD MMX extensions - also available in intel SSE */ + +#define mmx_m2ri(op, mem, reg, imm) \ + __asm__ __volatile__ (#op " %1, %0, %%" #reg \ + : /* nothing */ \ + : "m" (mem), "i" (imm)) +#define mmx_r2ri(op, regs, regd, imm) \ + __asm__ __volatile__ (#op " %0, %%" #regs ", %%" #regd \ + : /* nothing */ \ + : "i" (imm)) + +#define mmx_fetch(mem, hint) \ + __asm__ __volatile__ ("prefetch" #hint " %0" \ + : /* nothing */ \ + : "m" (mem)) + +#define maskmovq(regs, maskreg) mmx_r2ri(maskmovq, regs, maskreg) + +#define movntq_r2m(mmreg, var) mmx_r2m(movntq, mmreg, var) + +#define pavgb_m2r(var, reg) mmx_m2r(pavgb, var, reg) +#define pavgb_r2r(regs, regd) mmx_r2r(pavgb, regs, regd) +#define pavgw_m2r(var, reg) mmx_m2r(pavgw, var, reg) +#define pavgw_r2r(regs, regd) mmx_r2r(pavgw, regs, regd) + +#define pextrw_r2r(mmreg, reg, imm) mmx_r2ri(pextrw, mmreg, reg, imm) + +#define pinsrw_r2r(reg, mmreg, imm) mmx_r2ri(pinsrw, reg, mmreg, imm) + +#define pmaxsw_m2r(var, reg) mmx_m2r(pmaxsw, var, reg) +#define pmaxsw_r2r(regs, regd) mmx_r2r(pmaxsw, regs, regd) + +#define pmaxub_m2r(var, reg) mmx_m2r(pmaxub, var, reg) +#define pmaxub_r2r(regs, regd) mmx_r2r(pmaxub, regs, regd) + +#define pminsw_m2r(var, reg) mmx_m2r(pminsw, var, reg) +#define pminsw_r2r(regs, regd) mmx_r2r(pminsw, regs, regd) + +#define pminub_m2r(var, reg) mmx_m2r(pminub, var, reg) +#define pminub_r2r(regs, regd) mmx_r2r(pminub, regs, regd) + +#define pmovmskb(mmreg, reg) \ + __asm__ __volatile__ ("movmskps %" #mmreg ", %" #reg) + +#define pmulhuw_m2r(var, reg) mmx_m2r(pmulhuw, var, reg) +#define pmulhuw_r2r(regs, regd) mmx_r2r(pmulhuw, regs, regd) + +#define prefetcht0(mem) mmx_fetch(mem, t0) +#define prefetcht1(mem) mmx_fetch(mem, t1) +#define prefetcht2(mem) mmx_fetch(mem, t2) +#define prefetchnta(mem) mmx_fetch(mem, nta) + +#define psadbw_m2r(var, reg) mmx_m2r(psadbw, var, reg) +#define psadbw_r2r(regs, regd) mmx_r2r(psadbw, regs, regd) + +#define pshufw_m2r(var, reg, imm) mmx_m2ri(pshufw, var, reg, imm) +#define pshufw_r2r(regs, regd, imm) mmx_r2ri(pshufw, regs, regd, imm) + +#define sfence() __asm__ __volatile__ ("sfence\n\t") + +/* SSE2 */ +#define pshufhw_m2r(var, reg, imm) mmx_m2ri(pshufhw, var, reg, imm) +#define pshufhw_r2r(regs, regd, imm) mmx_r2ri(pshufhw, regs, regd, imm) +#define pshuflw_m2r(var, reg, imm) mmx_m2ri(pshuflw, var, reg, imm) +#define pshuflw_r2r(regs, regd, imm) mmx_r2ri(pshuflw, regs, regd, imm) + +#define pshufd_r2r(regs, regd, imm) mmx_r2ri(pshufd, regs, regd, imm) + +#define movdqa_m2r(var, reg) mmx_m2r(movdqa, var, reg) +#define movdqa_r2m(reg, var) mmx_r2m(movdqa, reg, var) +#define movdqa_r2r(regs, regd) mmx_r2r(movdqa, regs, regd) +#define movdqu_m2r(var, reg) mmx_m2r(movdqu, var, reg) +#define movdqu_r2m(reg, var) mmx_r2m(movdqu, reg, var) +#define movdqu_r2r(regs, regd) mmx_r2r(movdqu, regs, regd) + +#define pmullw_r2m(reg, var) mmx_r2m(pmullw, reg, var) + +#define pslldq_i2r(imm, reg) mmx_i2r(pslldq, imm, reg) +#define psrldq_i2r(imm, reg) mmx_i2r(psrldq, imm, reg) + +#define punpcklqdq_r2r(regs, regd) mmx_r2r(punpcklqdq, regs, regd) +#define punpckhqdq_r2r(regs, regd) mmx_r2r(punpckhqdq, regs, regd) + +#endif /* AVCODEC_I386MMX_H */ diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/oslec.h dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/oslec.h --- dahdi-linux-2.2.0.2/drivers/staging/echo/oslec.h 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/oslec.h 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,86 @@ +/* + * OSLEC - A line echo canceller. This code is being developed + * against and partially complies with G168. Using code from SpanDSP + * + * Written by Steve Underwood + * and David Rowe + * + * Copyright (C) 2001 Steve Underwood and 2007-2008 David Rowe + * + * All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + */ + +#ifndef __OSLEC_H +#define __OSLEC_H + +/* TODO: document interface */ + +/* Mask bits for the adaption mode */ +#define ECHO_CAN_USE_ADAPTION 0x01 +#define ECHO_CAN_USE_NLP 0x02 +#define ECHO_CAN_USE_CNG 0x04 +#define ECHO_CAN_USE_CLIP 0x08 +#define ECHO_CAN_USE_TX_HPF 0x10 +#define ECHO_CAN_USE_RX_HPF 0x20 +#define ECHO_CAN_DISABLE 0x40 + +/*! + G.168 echo canceller descriptor. This defines the working state for a line + echo canceller. +*/ +struct oslec_state; + +/*! Create a voice echo canceller context. + \param len The length of the canceller, in samples. + \return The new canceller context, or NULL if the canceller could not be created. +*/ +struct oslec_state *oslec_create(int len, int adaption_mode); + +/*! Free a voice echo canceller context. + \param ec The echo canceller context. +*/ +void oslec_free(struct oslec_state *ec); + +/*! Flush (reinitialise) a voice echo canceller context. + \param ec The echo canceller context. +*/ +void oslec_flush(struct oslec_state *ec); + +/*! Set the adaption mode of a voice echo canceller context. + \param ec The echo canceller context. + \param adapt The mode. +*/ +void oslec_adaption_mode(struct oslec_state *ec, int adaption_mode); + +void oslec_snapshot(struct oslec_state *ec); + +/*! Process a sample through a voice echo canceller. + \param ec The echo canceller context. + \param tx The transmitted audio sample. + \param rx The received audio sample. + \return The clean (echo cancelled) received sample. +*/ +int16_t oslec_update(struct oslec_state *ec, int16_t tx, int16_t rx); + +/*! Process to high pass filter the tx signal. + \param ec The echo canceller context. + \param tx The transmitted auio sample. + \return The HP filtered transmit sample, send this to your D/A. +*/ +int16_t oslec_hpf_tx(struct oslec_state *ec, int16_t tx); + +#endif /* __OSLEC_H */ diff -Nru dahdi-linux-2.2.0.2/drivers/staging/echo/TODO dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/TODO --- dahdi-linux-2.2.0.2/drivers/staging/echo/TODO 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/drivers/staging/echo/TODO 2009-07-24 11:35:11.000000000 -0500 @@ -0,0 +1,8 @@ +TODO: + - checkpatch.pl cleanups + - handle bit_operations.h (merge in or make part of common code?) + - remove proc interface, only use echo.h interface (proc interface is + racy and not correct.) + +Please send patches to Greg Kroah-Hartman and Cc: Steve +Underwood and David Rowe diff -Nru dahdi-linux-2.2.0.2/include/dahdi/kernel.h dahdi-cnet-linux-2.2.0.2/include/dahdi/kernel.h --- dahdi-linux-2.2.0.2/include/dahdi/kernel.h 2009-04-29 13:24:04.000000000 -0500 +++ dahdi-cnet-linux-2.2.0.2/include/dahdi/kernel.h 2009-07-24 11:35:11.000000000 -0500 @@ -582,6 +582,9 @@ #else unsigned char *lin2x; #endif + short ignoreflash; /* (CNET) Force FXO channel to ignore flash signals */ + short map_pulse; /* (CNET) Use Oslo/NZ or Swedish pulse dial mappings */ + short hearpulsing; /* (CNET) Make outpulsing audible to caller */ }; #ifdef CONFIG_DAHDI_NET diff -Nru dahdi-linux-2.2.0.2/include/dahdi/user.h dahdi-cnet-linux-2.2.0.2/include/dahdi/user.h --- dahdi-linux-2.2.0.2/include/dahdi/user.h 2009-04-30 15:59:20.000000000 -0500 +++ dahdi-cnet-linux-2.2.0.2/include/dahdi/user.h 2009-07-24 11:35:11.000000000 -0500 @@ -33,6 +33,10 @@ */ #ifndef _DAHDI_USER_H +/* (CNET) Activate CNET goodies in asterisk-cnet */ +#ifndef DAHDI_CNET +#define DAHDI_CNET +#endif #define _DAHDI_USER_H #include @@ -143,6 +147,7 @@ #define DAHDI_TONE_CUST1 8 #define DAHDI_TONE_CUST2 9 #define DAHDI_TONE_STUTTER 10 +#define DAHDI_TONE_SECONDDIALTONE 11 /* (CNET) Secondary dialtone */ #define DAHDI_TONE_MAX 16 #define DAHDI_TONE_DTMF_BASE 64 @@ -613,6 +618,10 @@ #define DAHDI_SPANCONFIG _IOW(DAHDI_CODE, 18, struct dahdi_lineconfig) +#define MAP_PULSE_NONE 0 +#define MAP_PULSE_NZ_OSLO 1 +#define MAP_PULSE_SWEDEN 2 + /* * Set Channel Configuration */ @@ -625,6 +634,9 @@ int idlebits; /* Idle bits (if this is a CAS channel) or channel to monitor (if this is DACS channel) */ char netdev_name[16];/* name for the hdlc network device*/ + int ignoreflash; /* (CNET) Force FXO to ignore flash signals */ + int map_pulse; /* (CNET) Use Oslo/NZ or Swedish pulse dial mappings */ + int hearpulsing; /* (CNET) Make outpulsing audible to caller */ }; #define DAHDI_CHANCONFIG _IOW(DAHDI_CODE, 19, struct dahdi_chanconfig) diff -Nru dahdi-linux-2.2.0.2/Makefile dahdi-cnet-linux-2.2.0.2/Makefile --- dahdi-linux-2.2.0.2/Makefile 2009-05-12 16:57:44.000000000 -0500 +++ dahdi-cnet-linux-2.2.0.2/Makefile 2009-07-24 11:35:11.000000000 -0500 @@ -72,6 +72,9 @@ endif endif +# (CNET) Mark as a CNET version +DAHDIVERSION:=${DAHDIVERSION}-cnet + all: modules modules: prereq diff -Nru dahdi-linux-2.2.0.2/READMEFIRST.cnet dahdi-cnet-linux-2.2.0.2/READMEFIRST.cnet --- dahdi-linux-2.2.0.2/READMEFIRST.cnet 1969-12-31 18:00:00.000000000 -0600 +++ dahdi-cnet-linux-2.2.0.2/READMEFIRST.cnet 2009-07-24 11:35:57.000000000 -0500 @@ -0,0 +1,148 @@ +dahdi-cnet: DAHDI with adaptations for antique telephony, especially +intended for use with C*NET, the Telephone Collector's Network. + +This driver is intended as a companion to asterisk-cnet, a modified +version of Asterisk. It MAY be used with standard Asterisk, but +asterisk-cnet MUST be used with dahdi-cnet. + +DISCLAIMER: This is NOT an official Digium package! Do not expect +any support from Digium if you use this! + +Modifications done by Russ Price , with audible +pulsing code by Max Parke. + +Latest changes: +2009-07-24: Updated drivers to 2.2.0.2. +2009-07-02: 2.2.0 First DAHDI-cnet release. + +This is a modified version of Digium's DAHDI device drivers for +telephony interfaces; especially, the wctdm driver for the TDM400P +analog telephone interface card. It provides the following features: + + * OSLEC echo canceller is INCLUDED in this package and will be + automatically built and installed + * the ability to force the TDM400P FXS ports to provide ring current + at a specific frequency, intended for US phones with frequency- + selective party line ringers + * improved pulse dialing on TDM400P FXS ports + * four additional call progress tone sets, usable on any FXS port + * an ignoreflash keyword that may be used in your + /etc/dahdi/system.conf file to cause FXO ports to ignore a remote + hookflash + * dial pulse mappings for New Zealand (or Oslo, Norway), and Sweden + * FXO pulse dial pause fix - "W" in dial strings will pause even if + the FXO is set for pulse dialing + * audible pulsing option; requires the CNET-modified Asterisk in + addition to the modified DAHDI + +Quick Install: + + cd /usr/src/dahdi-cnet-linux-2.2.0-rc5 + + make + + make install + + cd /usr/src/dahdi-cnet-tools-2.2.0-rc3 + + ./configure + + make + + make install + + +Making use of the features in /etc/dahdi/system.conf: + + * Ignore hookflash: If your TDM400P has FXO ports on channels 1 and + 2, and FXS ports on 3 and 4, you could tell the FXOs to ignore a + remote hookflash as follows: + + fxsks=1-2 + ignoreflash=1-2 + fxoks=3-4 + # use my PBX tones instead of standard tones on the FXS ports + loadzone=xp + defaultzone=xp + + * Dial pulse mapping: If you have rotary-dial phones with NZ/Oslo or + Swedish dial arrangements, you can set FXS channels like so: + + pulse_nz_oslo=8 + pulse_sweden=17-18 + + Note that dial pulse mapping will work on T1/E1 channels as well + as on TDM400P cards. The ignoreflash keyword will also work. + +UPDATE 2-27-07: Dial pulse mapping now works for FXO ports as well as +FXS. Use the same syntax above. + +(3-31-07) WARNING: Pulse dialing on T1 channel bank FXO ports may or may +not work for you! I've noticed that the pulses from an FXO on an Adtran +750 channel bank are rather irregular-sounding, and also sound like they +have an improper break/make ratio. If you need to pulse dial into a step +switch, I suggest using either a TDM400P or X100P card! + +(6-28-09) AMENDED. I don't know if this is a DAHDI vs. Zaptel issue, +or if the fact that I'm running DAHDI on a much faster CPU, but my pulse +dial problems with the channel bank are GONE. + + * Pause fix for FXO pulse dialing: Just use a "w" in the dial + string, just like you would for DTMF. + + * Audible pulsing: If you want callers to hear your Asterisk box + pulsing into your step switch, you will need to use the modified + Asterisk as well as the modified DAHDI. You will then need to + activate audible pulsing on the channels where you want it in both + /etc/dahdi/system.conf and /etc/asterisk/chan_dahdi.conf + for this to work. + + /etc/dahdi/system.conf: + + hearpulsing=2-3 + + /etc/asterisk/chan_dahdi.conf: + + ; be sure to apply other settings to these channels + hearpulsing=yes + channel => 2 + channel => 3 + + * FXS port ring frequency: set when loading the wctdm module. The + command line passed to modprobe would take the form: + + modprobe wctdm opermode=FCC30 fxshonormode=1 boostringer=1 + + Modes available are: + + * FCC17 (16.67 Hz) + * FCC25 (25 Hz) + * FCC30 (30 Hz) + * FCC33 (33.33 Hz) + * FCC40 (40 Hz) + * FCC42 (42 Hz) + * FCC50 (50 Hz) + * FCC54 (54 Hz) + * FCC60 (60 Hz) + * FCC67 (66.66 Hz) + + The standard FCC mode is 20 Hz. + + NOTE: Changing the opermode parameter affects all FXS ports on the + card. This may change in a future release. + + * Extra call progress tone zones: these require changes in + /etc/asterisk/chan_dahdi.conf and /etc/dahdi/system.conf. + + /etc/dahdi/system.conf: + + loadzone=xm # Re-creation of the 1960 Bell Labs experimental + # ESS in Morris, Illinois + loadzone=xp # North America (PBX tones) + loadzone=xu # Old-style UK tones (purring dial tone, etc.) + loadzone=x1 # Standard US tones with distinctive second + # dial tone + + /etc/asterisk/chan_dahdi.conf + + # Use tonezone=100 for xm, 101 for xp, 102 for xu, 103 for x1