diff options
Diffstat (limited to 'xpp/xbus-pcm.c')
-rw-r--r-- | xpp/xbus-pcm.c | 1156 |
1 files changed, 1156 insertions, 0 deletions
diff --git a/xpp/xbus-pcm.c b/xpp/xbus-pcm.c new file mode 100644 index 0000000..eb63b11 --- /dev/null +++ b/xpp/xbus-pcm.c @@ -0,0 +1,1156 @@ +/* + * Written by Oron Peled <oron@actcom.co.il> + * Copyright (C) 2004-2007, Xorcom + * + * 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 as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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. + * + */ +#include <linux/version.h> + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +# warning "This module is tested only with 2.6 kernels" +#endif + +#include <linux/kernel.h> +#include <linux/module.h> +#include "xbus-pcm.h" +#include "xbus-core.h" +#include "xpp_zap.h" +#include "zap_debug.h" +#include "parport_debug.h" + +static const char rcsid[] = "$Id$"; + +extern int print_dbg; +#ifdef XPP_EC_CHUNK +#include "supress/ec_xpp.h" +DEF_PARM_BOOL(xpp_ec, 0, 0444, "Do we use our own (1) or Zaptel's (0) echo canceller"); +#endif + +DEF_PARM(int, disable_pcm, 0, 0644, "Disable all PCM transmissions"); +#ifdef DEBUG_PCMTX +DEF_PARM(int, pcmtx, -1, 0644, "Forced PCM value to transmit (negative to disable)"); +DEF_PARM(int, pcmtx_chan, 0, 0644, "channel to force PCM value"); +#endif +DEF_PARM_BOOL(pcm_tasklet, 0, 0644, "Handle PCM in a tasklet (lower interrupt load)"); +#define PCM_TASKLET_DEPRECATION "\n" \ + "====================================================================\n" \ + "CONFIGURATION ERROR: 'pcm_tasklet' module parameter is deprecated!!!\n" \ + "====================================================================\n" + +static xbus_t *syncer; /* current syncer */ +static struct xpp_timing ref_sync; +static atomic_t xpp_tick_counter; +static const struct xpp_timing *global_ticker; /* increment xpp_tick_counter */ +static bool zaptel_syncer = 0; + +#define PROC_SYNC "sync" +#define BIG_TICK_INTERVAL 1000 +#define SYNC_ADJ_MIN (-30) /* minimal firmware drift unit */ +#define SYNC_ADJ_MAX 30 /* maximal firmware drift unit */ +#define SYNC_ADJ_FACTOR(x) ((x) / 30) /* average usec/drift_unit */ + +#ifdef ZAPTEL_SYNC_TICK +static unsigned int zaptel_tick_count = 0; +#endif + +/*------------------------- SYNC Handling --------------------------*/ + + +const char *sync_mode_name(enum sync_mode mode) +{ + static const char *sync_mode_names[] = { + [SYNC_MODE_AB] "SYNC_MODE_AB", + [SYNC_MODE_NONE] "SYNC_MODE_NONE", + [SYNC_MODE_PLL] "SYNC_MODE_PLL", + [SYNC_MODE_QUERY] "SYNC_MODE_QUERY", + }; + if(mode >= ARRAY_SIZE(sync_mode_names)) + return NULL; + return sync_mode_names[mode]; +} + +static void xpp_set_syncer(xbus_t *xbus, bool on) +{ + if(syncer != xbus && on) { + XBUS_DBG(SYNC, xbus, "New syncer\n"); + syncer = xbus; + } else if(syncer == xbus && !on) { + XBUS_DBG(SYNC, xbus, "Lost syncer\n"); + syncer = NULL; + } else + XBUS_DBG(SYNC, xbus, "ignore %s (current syncer: %s)\n", + (on)?"ON":"OFF", + (syncer) ? syncer->busname : "NO-SYNC"); +} + +void xpp_timing_init(struct xpp_timing *timing, const char *name) +{ + memset(timing, 0, sizeof(*timing)); + do_gettimeofday(&timing->timing_val); + spin_lock_init(&timing->lock); + timing->name = name; +} + +#define XPP_TIMING_SAMPLES 50 +#define XPP_TIMING_TICKS 100 +#define XPP_TIMING_MAX_STDDEV 500 + +static void xpp_timing_tick(struct xpp_timing *timing, const struct timeval *val) +{ + long usec; + int diff_sec; + int diff_usec; + unsigned long flags; + + spin_lock_irqsave(&timing->lock, flags); + if((timing->timing_count % XPP_TIMING_TICKS) != 0) + goto out; + diff_sec = val->tv_sec - timing->timing_val.tv_sec; + diff_usec = val->tv_usec - timing->timing_val.tv_usec; + timing->timing_val = *val; + /* ignore first batch of samples */ + if(timing->timing_count < (XPP_TIMING_TICKS * XPP_TIMING_SAMPLES)) + goto out; + if(abs(diff_sec) > 2) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) + NOTICE("TIMING(%s): bad timing: diff_sec=%d\n", + timing->name, diff_sec); + goto out; + } + usec = diff_sec * 1000000 + diff_usec; + if(usec) + timing->tick_rate = XPP_TIMING_TICKS * 1000000 / usec; + usec -= 1000 * XPP_TIMING_TICKS; /* normalize */ + + timing->accumulated_usec += usec; + timing->accumulated_usec_sqr += usec * usec; + if((timing->timing_count % (XPP_TIMING_TICKS * XPP_TIMING_SAMPLES)) == 0) { + int avg; + int stddev; + + avg = timing->accumulated_usec / XPP_TIMING_SAMPLES; + stddev = (timing->accumulated_usec_sqr / XPP_TIMING_SAMPLES); + stddev = int_sqrt(stddev); + timing->accumulated_usec = 0; + timing->accumulated_usec_sqr = 0; + if(stddev > XPP_TIMING_MAX_STDDEV) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) + NOTICE("TIMING(%s): bad timing: stddev=%d avg=%d\n", + timing->name, stddev, avg); + goto out; + } + timing->tick_avg = avg; + timing->tick_stddev = stddev; + } +out: + timing->timing_count++; + if(timing == global_ticker) + atomic_inc(&xpp_tick_counter); + spin_unlock_irqrestore(&timing->lock, flags); +} + +void xbus_command_timer(unsigned long param) +{ + xbus_t *xbus = (xbus_t *)param; + struct timeval now; + + BUG_ON(!xbus); + do_gettimeofday(&now); + xbus_command_queue_tick(xbus); + if(!xbus->self_ticking) + mod_timer(&xbus->command_timer, jiffies + 1); /* Must be 1KHz rate */ +} + +void xbus_set_command_timer(xbus_t *xbus, bool on) +{ + XBUS_DBG(SYNC, xbus, "%s\n", (on)?"ON":"OFF"); + if(on) { + if(!timer_pending(&xbus->command_timer)) { + XBUS_DBG(SYNC, xbus, "add_timer\n"); + xbus->command_timer.function = xbus_command_timer; + xbus->command_timer.data = (unsigned long)xbus; + xbus->command_timer.expires = jiffies + 1; + add_timer(&xbus->command_timer); + xbus->self_ticking = 0; + } + } else if(timer_pending(&xbus->command_timer)) { + xbus->self_ticking = 1; + XBUS_DBG(SYNC, xbus, "del_timer\n"); + del_timer(&xbus->command_timer); + } +} + +/* + * Called when the Astribank replies to a sync change request + */ +void got_new_syncer(xbus_t *xbus, enum sync_mode mode, int drift) +{ + unsigned long flags; + + XBUS_DBG(SYNC, xbus, "%s (%d), drift=%d (pcm_rx_counter=%d)\n", + sync_mode_name(mode), mode, drift, atomic_read(&xbus->pcm_rx_counter)); + spin_lock_irqsave(&xbus->lock, flags); + xbus->sync_adjustment = (signed char)drift; + if(xbus->sync_mode == mode) { + XBUS_DBG(SYNC, xbus, "Already in %s. Ignored\n", sync_mode_name(mode)); + goto out; + } + switch(mode) { + case SYNC_MODE_AB: + xbus->sync_mode = mode; + xbus_set_command_timer(xbus, 0); + xpp_set_syncer(xbus, 1); + break; + case SYNC_MODE_PLL: + xbus->sync_mode = mode; + xbus_set_command_timer(xbus, 0); + xpp_set_syncer(xbus, 0); + break; + case SYNC_MODE_NONE: /* lost sync source */ + xbus->sync_mode = mode; + xbus_set_command_timer(xbus, 1); + xpp_set_syncer(xbus, 0); + break; + case SYNC_MODE_QUERY: /* ignore */ + break; + default: + XBUS_ERR(xbus, "%s: unknown mode=0x%X\n", __FUNCTION__, mode); + } +out: + spin_unlock_irqrestore(&xbus->lock, flags); +} + +void xbus_request_sync(xbus_t *xbus, enum sync_mode mode) +{ + BUG_ON(!xbus); + XBUS_DBG(SYNC, xbus, "sent request (mode=%d)\n", mode); + CALL_PROTO(GLOBAL, SYNC_SOURCE, xbus, NULL, mode, 0); +} + +static void reset_sync_counters(void) +{ + int i; + + //DBG(SYNC, "%d\n", atomic_read(&xpp_tick_counter)); + for(i = 0; i < MAX_BUSES; i++) { + xbus_t *xbus = get_xbus(i); + + if(!xbus) + continue; + if (TRANSPORT_RUNNING(xbus)) { + if(XBUS_GET(xbus)) { + /* Reset sync LEDs once in a while */ + CALL_PROTO(GLOBAL, RESET_SYNC_COUNTERS, xbus, NULL); + XBUS_PUT(xbus); + } else { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) + XBUS_DBG(GENERAL, xbus, + "Dropped packet. Is shutting down. (%d)\n", rate_limit); + } + } + put_xbus(xbus); + } +} + +static void send_drift(xbus_t *xbus, int drift) +{ + struct timeval now; + const char *msg; + + BUG_ON(drift < SYNC_ADJ_MIN || drift > SYNC_ADJ_MAX); + do_gettimeofday(&now); + if(drift > xbus->sync_adjustment) + msg = "up"; + else + msg = "down"; + XBUS_DBG(SYNC, xbus, "DRIFT adjust %s (%d) (last update %ld seconds ago)\n", + msg, drift, now.tv_sec - xbus->pll_updated_at); + CALL_PROTO(GLOBAL, SYNC_SOURCE, xbus, NULL, SYNC_MODE_PLL, drift); + xbus->pll_updated_at = now.tv_sec; +} + +#ifdef ZAPTEL_SYNC_TICK +int zaptel_sync_tick(struct zt_span *span, int is_master) +{ + xpd_t *xpd = span->pvt; + struct timeval now; + static int redundant_ticks; /* for extra spans */ + + if(!zaptel_syncer) + goto noop; + BUG_ON(!xpd); + /* + * Detect if any of our spans is zaptel sync master + */ + if(is_master) { + static int rate_limit; + + if(xpd->xbus != syncer && ((rate_limit % 1003) == 0)) { + XPD_ERR(xpd, + "Zaptel master, but syncer=%s\n", + xpd->xbus->busname); + } + if((rate_limit % 5003) == 0) + XPD_NOTICE(xpd, "Zaptel master: ignore ZAPTEL sync\n"); + rate_limit++; + goto noop; + } + /* Now we know for sure someone else is zaptel sync master */ + if(syncer) { + static int rate_limit; + + if((rate_limit++ % 5003) == 0) + XBUS_DBG(SYNC, syncer, + "Already a syncer, ignore ZAPTEL sync\n"); + goto noop; + } + /* ignore duplicate calls from all our registered spans */ + if((redundant_ticks++ % total_registered_spans()) != 0) { +#if 0 + static int rate_limit; + + if((rate_limit++ % 1003) < 16) + XPD_NOTICE(xpd, "boop (%d)\n", zaptel_tick_count); +#endif + goto noop; + } + do_gettimeofday(&now); + xpp_timing_tick(&ref_sync, &now); + zaptel_tick_count++; + flip_parport_bit(1); + return 0; +noop: + return 0; /* No auto sync from zaptel */ +} +#endif + + +static void sync_rate_adjust(xbus_t *xbus) +{ + int offset; + + xbus->sync_offset_usec = xbus->timing.tick_avg - ref_sync.tick_avg; + /* Calculate required PLL fix */ + offset = SYNC_ADJ_FACTOR(xbus->sync_offset_usec); + if(offset < SYNC_ADJ_MIN) + offset = SYNC_ADJ_MIN; + if(offset > SYNC_ADJ_MAX) + offset = SYNC_ADJ_MAX; + xbus->sync_adjustment_offset = offset; + if(xbus != syncer && xbus->sync_adjustment != offset) + send_drift(xbus, offset); +} + +/* + * called from elect_syncer() + * if new_syncer is NULL, than we move all to SYNC_MODE_PLL + * for ZAPTEL sync. + */ +static void update_sync_master(xbus_t *new_syncer) +{ + const char *msg = (zaptel_syncer) ? "ZAPTEL" : "NO-SYNC"; + int i; + + DBG(SYNC, "%s => %s\n", + (syncer) ? syncer->busname : msg, + (new_syncer) ? new_syncer->busname : msg); + if(new_syncer) { + XBUS_DBG(SYNC, new_syncer, "pcm_rx_counter=%d\n", + atomic_read(&new_syncer->pcm_rx_counter)); + zaptel_syncer = 0; + global_ticker = &new_syncer->timing; + xbus_request_sync(new_syncer, SYNC_MODE_AB); + } else + global_ticker = &ref_sync; + DBG(SYNC, "stop unwanted syncers\n"); + /* Shut all down except the wanted sync master */ + for(i = 0; i < MAX_BUSES; i++) { + xbus_t *xbus = get_xbus(i); + if(!xbus) + continue; + if(TRANSPORT_RUNNING(xbus) && xbus != new_syncer) { + if(xbus->self_ticking) + xbus_request_sync(xbus, SYNC_MODE_PLL); + else + XBUS_DBG(SYNC, xbus, "Not self_ticking yet. Ignore\n"); + } + put_xbus(xbus); + } +} + +void elect_syncer(const char *msg) +{ + int i; + int j; + uint timing_priority = 0; + xpd_t *best_xpd = NULL; + xbus_t *the_xbus = NULL; + + for(i = 0; i < MAX_BUSES; i++) { + xbus_t *xbus = get_xbus(i); + if(!xbus) + continue; + if(!the_xbus) + the_xbus = xbus; + if (TRANSPORT_RUNNING(xbus)) { + for(j = 0; j < MAX_XPDS; j++) { + xpd_t *xpd = xpd_of(xbus, j); + + if(!xpd) + continue; + if(xpd->timing_priority > timing_priority) { + timing_priority = xpd->timing_priority; + best_xpd = xpd; + } + } + } + put_xbus(xbus); + } + if(best_xpd) { + the_xbus = best_xpd->xbus; + XPD_DBG(SYNC, best_xpd, "%s: elected with priority %d\n", msg, timing_priority); + } else if(the_xbus) { + XBUS_DBG(SYNC, the_xbus, "%s: elected\n", msg); + } else + DBG(SYNC, "%s: No more syncers\n", msg); + if(the_xbus != syncer) + update_sync_master(the_xbus); +} + +/* + * This function is used by FXS/FXO. The pcm_mask argument signifies + * channels which should be *added* to the automatic calculation. + * Normally, this argument is 0. + * + * The caller should spinlock the XPD before calling it. + */ +void __pcm_recompute(xpd_t *xpd, xpp_line_t pcm_mask) +{ + int i; + int line_count = 0; + + /* Add/remove all the trivial cases */ + pcm_mask |= xpd->offhook; + pcm_mask |= xpd->cid_on; + pcm_mask &= ~xpd->digital_signalling; /* No PCM in D-Channels */ + pcm_mask &= ~xpd->digital_inputs; + pcm_mask &= ~xpd->digital_outputs; + for_each_line(xpd, i) + if(IS_SET(pcm_mask, i)) + line_count++; + /* + * FIXME: Workaround a bug in sync code of the Astribank. + * Send dummy PCM for sync. + */ + if(xpd->addr.unit == 0 && pcm_mask == 0) { + pcm_mask = BIT(0); + line_count = 1; + } + xpd->pcm_len = (line_count) + ? RPACKET_HEADERSIZE + sizeof(xpp_line_t) + line_count * ZT_CHUNKSIZE + : 0L; + xpd->wanted_pcm_mask = pcm_mask; +} + +/* + * A spinlocked version of __pcm_recompute() + */ +void pcm_recompute(xpd_t *xpd, xpp_line_t pcm_mask) +{ + unsigned long flags; + + spin_lock_irqsave(&xpd->lock, flags); + __pcm_recompute(xpd, pcm_mask); + spin_unlock_irqrestore(&xpd->lock, flags); +} + +void fill_beep(u_char *buf, int num, int duration) +{ + bool alternate = (duration) ? (jiffies/(duration*1000)) & 0x1 : 0; + int which; + u_char *snd; + + /* + * debug tones + */ + static u_char beep[] = { + 0x7F, 0xBE, 0xD8, 0xBE, 0x80, 0x41, 0x24, 0x41, /* Dima */ + 0x67, 0x90, 0x89, 0x90, 0xFF, 0x10, 0x09, 0x10, /* Izzy */ + }; + static u_char beep_alt[] = { + 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, /* silence */ + }; + if(alternate) { + which = num % ARRAY_SIZE(beep_alt); + snd = &beep_alt[which]; + } else { + which = num % ARRAY_SIZE(beep); + snd = &beep[which]; + } + memcpy(buf, snd, ZT_CHUNKSIZE); +} + +#ifdef XPP_EC_CHUNK +/* + * Taken from zaptel.c + */ +static inline void xpp_ec_chunk(struct zt_chan *chan, unsigned char *rxchunk, const unsigned char *txchunk) +{ + int16_t rxlin; + int x; + unsigned long flags; + + /* Perform echo cancellation on a chunk if necessary */ + if (!chan->ec) + return; + spin_lock_irqsave(&chan->lock, flags); + for (x=0;x<ZT_CHUNKSIZE;x++) { + rxlin = ZT_XLAW(rxchunk[x], chan); + rxlin = xpp_echo_can_update(chan->ec, ZT_XLAW(txchunk[x], chan), rxlin); + rxchunk[x] = ZT_LIN2X((int)rxlin, chan); + } + spin_unlock_irqrestore(&chan->lock, flags); +} +#endif + +static void do_ec(xpd_t *xpd) +{ + struct zt_chan *chans = xpd->span.chans; + int i; + +#ifdef WITH_ECHO_SUPPRESSION + /* FIXME: need to Echo cancel double buffered data */ + for (i = 0;i < xpd->span.channels; i++) { + if(unlikely(IS_SET(xpd->digital_signalling, i))) /* Don't echo cancel PRI/BRI D-chans */ + continue; +#ifdef XPP_EC_CHUNK + /* even if defined, parameterr xpp_ec can override at run-time */ + if (xpp_ec) + xpp_ec_chunk(&chans[i], chans[i].readchunk, xpd->ec_chunk2[i]); + else +#endif + zt_ec_chunk(&chans[i], chans[i].readchunk, xpd->ec_chunk2[i]); + memcpy(xpd->ec_chunk2[i], xpd->ec_chunk1[i], ZT_CHUNKSIZE); + memcpy(xpd->ec_chunk1[i], chans[i].writechunk, ZT_CHUNKSIZE); + } +#endif +} + +#if 0 +/* Okay, now we get to the signalling. You have several options: */ + +/* Option 1: If you're a T1 like interface, you can just provide a + rbsbits function and we'll assert robbed bits for you. Be sure to + set the ZT_FLAG_RBS in this case. */ + +/* Opt: If the span uses A/B bits, set them here */ +int (*rbsbits)(struct zt_chan *chan, int bits); + +/* Option 2: If you don't know about sig bits, but do have their + equivalents (i.e. you can disconnect battery, detect off hook, + generate ring, etc directly) then you can just specify a + sethook function, and we'll call you with appropriate hook states + to set. Still set the ZT_FLAG_RBS in this case as well */ +int (*hooksig)(struct zt_chan *chan, zt_txsig_t hookstate); + +/* Option 3: If you can't use sig bits, you can write a function + which handles the individual hook states */ +int (*sethook)(struct zt_chan *chan, int hookstate); +#endif + +int xpp_echocan(struct zt_chan *chan, int len) +{ +#ifdef XPP_EC_CHUNK + if(len == 0) { /* shut down */ + /* zaptel calls this also during channel initialization */ + if(chan->ec) { + xpp_echo_can_free(chan->ec); + } + return 0; + } + if(chan->ec) { + ERR("%s: Trying to override an existing EC (%p)\n", __FUNCTION__, chan->ec); + return -EINVAL; + } + chan->ec = xpp_echo_can_create(len, 0); + if(!chan->ec) { + ERR("%s: Failed creating xpp EC (len=%d)\n", __FUNCTION__, len); + return -EINVAL; + } +#endif + return 0; +} + +static bool pcm_valid(xpd_t *xpd, xpacket_t *pack) +{ + xpp_line_t lines = RPACKET_FIELD(pack, GLOBAL, PCM_READ, lines); + int i; + int count = 0; + uint16_t good_len; + + BUG_ON(!pack); + BUG_ON(XPACKET_OP(pack) != XPROTO_NAME(GLOBAL, PCM_READ)); + /* + * Don't use for_each_line(xpd, i) here because for BRI it will + * ignore the channels of the other xpd's in the same unit. + */ + for (i = 0; i < CHANNELS_PERXPD; i++) + if(IS_SET(lines, i)) + count++; + /* FRAMES: include opcode in calculation */ + good_len = RPACKET_HEADERSIZE + sizeof(xpp_line_t) + count * 8; + if(XPACKET_LEN(pack) != good_len) { + static int rate_limit = 0; + + XPD_COUNTER(xpd, RECV_ERRORS)++; + if((rate_limit++ % 1000) <= 10) { + XPD_ERR(xpd, "BAD PCM REPLY: packet_len=%d (should be %d), count=%d\n", + XPACKET_LEN(pack), good_len, count); + dump_packet("BAD PCM REPLY", pack, 1); + } + return 0; + } + return 1; +} + + + +static inline void pcm_frame_out(xbus_t *xbus, xframe_t *xframe) +{ + unsigned long flags; + struct timeval now; + unsigned long usec; + + spin_lock_irqsave(&xbus->lock, flags); + do_gettimeofday(&now); + if(unlikely(disable_pcm || !TRANSPORT_RUNNING(xbus))) + goto dropit; + if(XPACKET_ADDR((xpacket_t *)xframe->packets).sync_master) { + usec = usec_diff(&now, &xbus->last_tx_sync); + xbus->last_tx_sync = now; + /* ignore startup statistics */ + if(likely(atomic_read(&xbus->pcm_rx_counter) > BIG_TICK_INTERVAL)) { + if(abs(usec - 1000) > TICK_TOLERANCE) { + static int rate_limit; + + if((rate_limit++ % 5003) == 0) + XBUS_DBG(SYNC, xbus, "Bad PCM TX timing(%d): usec=%ld.\n", + rate_limit, usec); + } + if(usec > xbus->max_tx_sync) + xbus->max_tx_sync = usec; + if(usec < xbus->min_tx_sync) + xbus->min_tx_sync = usec; + } + } + spin_unlock_irqrestore(&xbus->lock, flags); + /* OK, really send it */ + if(print_dbg & DBG_PCM ) + dump_xframe("TX_XFRAME_PCM", xbus, xframe); + send_pcm_frame(xbus, xframe); + XBUS_COUNTER(xbus, TX_XFRAME_PCM)++; + return; +dropit: + spin_unlock_irqrestore(&xbus->lock, flags); + FREE_SEND_XFRAME(xbus, xframe); +} + +/* + * Generic implementations of card_pcmfromspan()/card_pcmtospan() + * For FXS/FXO + */ +void generic_card_pcm_fromspan(xbus_t *xbus, xpd_t *xpd, xpp_line_t lines, xpacket_t *pack) +{ + byte *pcm; + struct zt_chan *chans; + unsigned long flags; + int i; + + BUG_ON(!xbus); + BUG_ON(!xpd); + BUG_ON(!pack); + RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = lines; + pcm = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, pcm); + spin_lock_irqsave(&xpd->lock, flags); + chans = xpd->span.chans; + for (i = 0; i < xpd->channels; i++) { + if(IS_SET(lines, i)) { + if(SPAN_REGISTERED(xpd)) { +#ifdef DEBUG_PCMTX + if(pcmtx >= 0 && pcmtx_chan == i) + memset((u_char *)pcm, pcmtx, ZT_CHUNKSIZE); + else +#endif + memcpy((u_char *)pcm, chans[i].writechunk, ZT_CHUNKSIZE); + // fill_beep((u_char *)pcm, xpd->addr.subunit, 2); + } else + memset((u_char *)pcm, 0x7F, ZT_CHUNKSIZE); + pcm += ZT_CHUNKSIZE; + } + } + XPD_COUNTER(xpd, PCM_WRITE)++; + spin_unlock_irqrestore(&xpd->lock, flags); +} + +void generic_card_pcm_tospan(xbus_t *xbus, xpd_t *xpd, xpacket_t *pack) +{ + byte *pcm; + xpp_line_t pcm_mask; + unsigned long flags; + int i; + + pcm = RPACKET_FIELD(pack, GLOBAL, PCM_READ, pcm); + pcm_mask = RPACKET_FIELD(pack, GLOBAL, PCM_READ, lines); + spin_lock_irqsave(&xpd->lock, flags); + if(!SPAN_REGISTERED(xpd)) + goto out; + for (i = 0; i < xpd->channels; i++) { + volatile u_char *r = xpd->span.chans[i].readchunk; + + if(!IS_SET(xpd->wanted_pcm_mask, i)) + continue; + pcm_mask &= ~xpd->mute_dtmf; + if(IS_SET(pcm_mask, i)) { + // memset((u_char *)r, 0x5A, ZT_CHUNKSIZE); // DEBUG + // fill_beep((u_char *)r, 1, 1); // DEBUG: BEEP + memcpy((u_char *)r, pcm, ZT_CHUNKSIZE); + pcm += ZT_CHUNKSIZE; + } else { + memset((u_char *)r, 0x7F, ZT_CHUNKSIZE); // SILENCE + } + } +out: + XPD_COUNTER(xpd, PCM_READ)++; + spin_unlock_irqrestore(&xpd->lock, flags); +} + +static int copy_pcm_tospan(xbus_t *xbus, xframe_t *xframe) +{ + byte *xframe_end; + xpacket_t *pack; + byte *p; + int ret = -EPROTO; /* Assume error */ + + if(print_dbg & DBG_PCM) + dump_xframe("RX_XFRAME_PCM", xbus, xframe); + /* handle content */ + + p = xframe->packets; + xframe_end = p + XFRAME_LEN(xframe); + do { + int len; + xpd_t *xpd; + + pack = (xpacket_t *)p; + len = XPACKET_LEN(pack); + /* Sanity checks */ + if(unlikely(XPACKET_OP(pack) != XPROTO_NAME(GLOBAL,PCM_READ))) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) { + XBUS_NOTICE(xbus, + "%s: Non-PCM packet within a PCM xframe. (%d)\n", + __FUNCTION__, rate_limit); + dump_xframe("In PCM xframe", xbus, xframe); + } + goto out; + } + p += len; + if(p > xframe_end || len < RPACKET_HEADERSIZE) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) { + XBUS_NOTICE(xbus, + "%s: Invalid packet length %d. (%d)\n", + __FUNCTION__, len, rate_limit); + dump_xframe("BAD LENGTH", xbus, xframe); + } + goto out; + } + xpd = xpd_byaddr(xbus, XPACKET_ADDR(pack).unit, XPACKET_ADDR(pack).subunit); + if(unlikely(!xpd)) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) { + notify_bad_xpd(__FUNCTION__, xbus, XPACKET_ADDR(pack), "RECEIVE PCM"); + dump_xframe("Unknown XPD addr", xbus, xframe); + } + goto out; + } + if(!pcm_valid(xpd, pack)) + goto out; + if(SPAN_REGISTERED(xpd)) { + XBUS_COUNTER(xbus, RX_PACK_PCM)++; + CALL_XMETHOD(card_pcm_tospan, xbus, xpd, pack); + } + } while(p < xframe_end); + ret = 0; /* all good */ + XBUS_COUNTER(xbus, RX_XFRAME_PCM)++; +out: + FREE_RECV_XFRAME(xbus, xframe); + return ret; +} + +static void xbus_tick(xbus_t *xbus) +{ + int i; + xpd_t *xpd; + xframe_t *xframe = NULL; + xpacket_t *pack = NULL; + size_t pcm_len; + bool sent_sync_bit = 0; + + /* + * Update zaptel + */ + for(i = 0; i < MAX_XPDS; i++) { + xpd = xpd_of(xbus, i); + if(xpd && SPAN_REGISTERED(xpd)) { + /* + * calls to zt_transmit should be out of spinlocks, as it may call back + * our hook setting methods. + */ +#ifdef OPTIMIZE_CHANMUTE + int j; + + for_each_line(xpd, j) + xpd->chans[j].chanmute = !IS_SET(xpd->wanted_pcm_mask, j) && !IS_SET(xpd->digital_signalling, j); +#endif + zt_transmit(&xpd->span); + } + } + /* + * Fill xframes + */ + for(i = 0; i < MAX_XPDS; i++) { + if((xpd = xpd_of(xbus, i)) == NULL) + continue; + pcm_len = xpd->pcm_len; + if(SPAN_REGISTERED(xpd)) { + if(pcm_len && xpd->card_present) { + do { + // pack = NULL; /* FORCE single packet frames */ + if(xframe && !pack) { /* FULL frame */ + pcm_frame_out(xbus, xframe); + xframe = NULL; + XBUS_COUNTER(xbus, TX_PCM_FRAG)++; + } + if(!xframe) { /* Alloc frame */ + xframe = ALLOC_SEND_XFRAME(xbus); + if (!xframe) { + static int rate_limit; + + if((rate_limit++ % 3001) == 0) + XBUS_ERR(xbus, + "%s: failed to allocate new xframe\n", + __FUNCTION__); + return; + } + } + pack = xframe_next_packet(xframe, pcm_len); + } while(!pack); + XPACKET_INIT(pack, GLOBAL, PCM_WRITE, xpd->xbus_idx, 1, 0); + XPACKET_LEN(pack) = pcm_len; + if(!sent_sync_bit) { + XPACKET_ADDR(pack).sync_master = 1; + sent_sync_bit = 1; + } + CALL_XMETHOD(card_pcm_fromspan, xbus, xpd, xpd->wanted_pcm_mask, pack); + XBUS_COUNTER(xbus, TX_PACK_PCM)++; + } + } + } + if(xframe) /* clean any leftovers */ + pcm_frame_out(xbus, xframe); + /* + * Receive PCM + */ + i = atomic_read(&xbus->pcm_rx_counter) & 1; + while((xframe = xframe_dequeue(&xbus->pcm_tospan[i])) != NULL) { + copy_pcm_tospan(xbus, xframe); + if(XPACKET_ADDR((xpacket_t *)xframe->packets).sync_master) { + struct timeval now; + unsigned long usec; + + do_gettimeofday(&now); + usec = usec_diff(&now, &xbus->last_rx_sync); + xbus->last_rx_sync = now; + /* ignore startup statistics */ + if(likely(atomic_read(&xbus->pcm_rx_counter) > BIG_TICK_INTERVAL)) { + if(abs(usec - 1000) > TICK_TOLERANCE) { + static int rate_limit; + + if((rate_limit++ % 5003) == 0) + XBUS_DBG(SYNC, xbus, "Bad PCM RX timing(%d): usec=%ld.\n", + rate_limit, usec); + } + if(usec > xbus->max_rx_sync) + xbus->max_rx_sync = usec; + if(usec < xbus->min_rx_sync) + xbus->min_rx_sync = usec; + } + } + } + for(i = 0; i < MAX_XPDS; i++) { + xpd = xpd_of(xbus, i); + if(!xpd || !xpd->card_present) + continue; + if(SPAN_REGISTERED(xpd)) { + do_ec(xpd); + zt_receive(&xpd->span); + } + xpd->timer_count = xbus->global_counter; + /* + * Must be called *after* tx/rx so + * D-Chan counters may be cleared + */ + CALL_XMETHOD(card_tick, xbus, xpd); + } +} + +void do_tick(xbus_t *xbus, struct timeval tv_received) +{ + int counter = atomic_read(&xpp_tick_counter); + + xbus_command_queue_tick(xbus); + xpp_timing_tick(&xbus->timing, &tv_received); + if(syncer == xbus) { + xpp_timing_tick(&ref_sync, &tv_received); + if((counter % BIG_TICK_INTERVAL) == 0) + reset_sync_counters(); + } + if((atomic_read(&xbus->pcm_rx_counter) % BIG_TICK_INTERVAL) == 0) { + if(xbus->sync_mode == SYNC_MODE_PLL) + sync_rate_adjust(xbus); + } + if(likely(xbus->self_ticking)) + xbus_tick(xbus); + xbus->global_counter = counter; +} + +void xframe_receive_pcm(xbus_t *xbus, xframe_t *xframe) +{ + int which = atomic_read(&xbus->pcm_rx_counter) & 1; + + if(!xframe_enqueue(&xbus->pcm_tospan[which], xframe)) { + static int rate_limit; + + if((rate_limit++ % 1003) == 0) + XBUS_DBG(SYNC, xbus, + "Failed to enqueue received pcm frame. (%d)\n", + rate_limit); + FREE_RECV_XFRAME(xbus, xframe); + } + /* + * The sync_master bit is marked at the first packet + * of the frame, regardless of the XPD that is sync master. + * FIXME: what about PRI split? + */ + if(XPACKET_ADDR((xpacket_t *)xframe->packets).sync_master) { + do_tick(xbus, xframe->tv_received); + atomic_inc(&xbus->pcm_rx_counter); + } else + xbus->xbus_frag_count++; +} + +#ifdef CONFIG_PROC_FS +int proc_sync_read(char *page, char **start, off_t off, int count, int *eof, void *data) +{ + int len = 0; + struct timeval now; + unsigned int counter = atomic_read(&xpp_tick_counter); + unsigned long usec; + + do_gettimeofday(&now); + len += sprintf(page + len, "# To modify sync source write into this file:\n"); + len += sprintf(page + len, "# ZAPTEL - Another zaptel device provide sync\n"); + len += sprintf(page + len, "# SYNC=nn - XBUS-nn provide sync\n"); + len += sprintf(page + len, "# QUERY=nn - Query XBUS-nn for sync information (DEBUG)\n"); + if(!syncer) { + if(zaptel_syncer) + len += sprintf(page + len, "ZAPTEL\n"); + else + len += sprintf(page + len, "NO-SYNC\n"); + } else + len += sprintf(page + len, "SYNC=%02d\n", syncer->num); +#ifdef ZAPTEL_SYNC_TICK + if(zaptel_syncer) { + len += sprintf(page + len, + "Zaptel Reference Sync (%d registered spans):\n", + total_registered_spans()); + len += sprintf(page + len, "\tzaptel_tick: #%d\n", zaptel_tick_count); + len += sprintf(page + len, "\ttick - zaptel_tick = %d\n", + counter - zaptel_tick_count); + } else { + len += sprintf(page + len, + "Zaptel Reference Sync Not activated\n"); + } +#endif + usec = usec_diff(&now, &ref_sync.timing_val); + len += sprintf(page + len, "\ntick: #%d\n", counter); + len += sprintf(page + len, + "tick rate: %4d/second (measured %ld.%ld msec ago)\n", + ref_sync.tick_rate, + usec / 1000, usec % 1000); + if(pcm_tasklet) + len += sprintf(page + len, PCM_TASKLET_DEPRECATION); + if (len <= off+count) + *eof = 1; + *start = page + off; + len -= off; + if (len > count) + len = count; + if (len < 0) + len = 0; + return len; +} + +static int proc_sync_write(struct file *file, const char __user *buffer, unsigned long count, void *data) +{ + char buf[MAX_PROC_WRITE]; + int xbus_num; + int xpd_num; + xbus_t *xbus; + xpd_t *xpd; + + // DBG(SYNC, "%s: count=%ld\n", __FUNCTION__, count); + if(count >= MAX_PROC_WRITE) + return -EINVAL; + if(copy_from_user(buf, buffer, count)) + return -EFAULT; + buf[count] = '\0'; + if(strncmp("ZAPTEL", buf, 6) == 0) { + DBG(SYNC, "ZAPTEL\n"); + zaptel_syncer=1; + update_sync_master(NULL); + } else if(sscanf(buf, "SYNC=%d", &xbus_num) == 1) { + DBG(SYNC, "SYNC=%d\n", xbus_num); + if((xbus = get_xbus(xbus_num)) == NULL) { + ERR("No bus %d exists\n", xbus_num); + return -ENXIO; + } + if((xpd = xpd_of(xbus, 0)) == NULL) { + XBUS_ERR(xbus, "No xpd 0 exists\n"); + put_xbus(xbus); + return -ENXIO; + } + update_sync_master(xbus); + put_xbus(xbus); + } else if(sscanf(buf, "QUERY=%d", &xbus_num) == 1) { + DBG(SYNC, "QUERY=%d\n", xbus_num); + if((xbus = get_xbus(xbus_num)) == NULL) { + ERR("No bus %d exists\n", xbus_num); + return -ENXIO; + } + CALL_PROTO(GLOBAL, SYNC_SOURCE, xbus, NULL, SYNC_MODE_QUERY, 0); + put_xbus(xbus); + } else if(sscanf(buf, "%d %d", &xbus_num, &xpd_num) == 2) { + NOTICE("Using deprecated syntax to update %s file\n", + PROC_SYNC); + if(xpd_num != 0) { + ERR("Currently can only set sync for XPD #0\n"); + return -EINVAL; + } + if((xbus = get_xbus(xbus_num)) == NULL) { + ERR("No bus %d exists\n", xbus_num); + return -ENXIO; + } + if((xpd = xpd_of(xbus, xpd_num)) == NULL) { + XBUS_ERR(xbus, "No xpd %d exists\n", xpd_num); + put_xbus(xbus); + return -ENXIO; + } + update_sync_master(xbus); + put_xbus(xbus); + } else { + ERR("%s: cannot parse '%s'\n", __FUNCTION__, buf); + count = -EINVAL; + } + return count; +} + +static struct proc_dir_entry *top; + +#endif + +int xbus_pcm_init(struct proc_dir_entry *toplevel) +{ + int ret = 0; +#ifdef CONFIG_PROC_FS + struct proc_dir_entry *ent; +#endif + +#ifdef WITH_ECHO_SUPPRESSION + INFO("FEATURE: with ECHO_SUPPRESSION\n"); +#else + INFO("FEATURE: without ECHO_SUPPRESSION\n"); +#endif + if(xpp_ec) + INFO("FEATURE: with XPP_EC_CHUNK\n"); + else + INFO("FEATURE: without XPP_EC_CHUNK\n"); +#ifdef ZAPTEL_SYNC_TICK + INFO("FEATURE: with sync_tick() from ZAPTEL\n"); +#else + INFO("FEATURE: without sync_tick() from ZAPTEL\n"); +#endif +#ifdef CONFIG_PROC_FS + top = toplevel; + ent = create_proc_entry(PROC_SYNC, 0644, top); + if(!ent) { + ret = -EFAULT; + goto err; + } + ent->read_proc = proc_sync_read; + ent->write_proc = proc_sync_write; + ent->data = NULL; +#endif + if(pcm_tasklet) + ERR(PCM_TASKLET_DEPRECATION); + xpp_timing_init(&ref_sync, "REF-SYNC"); +err: + return ret; +} + +void xbus_pcm_shutdown(void) +{ +#ifdef CONFIG_PROC_FS + DBG(GENERAL, "Removing '%s' from proc\n", PROC_SYNC); + remove_proc_entry(PROC_SYNC, top); +#endif +} + + +EXPORT_SYMBOL(xbus_request_sync); +EXPORT_SYMBOL(got_new_syncer); +EXPORT_SYMBOL(elect_syncer); +EXPORT_SYMBOL(xpp_echocan); +#ifdef ZAPTEL_SYNC_TICK +EXPORT_SYMBOL(zaptel_sync_tick); +#endif +EXPORT_SYMBOL(__pcm_recompute); +EXPORT_SYMBOL(pcm_recompute); +EXPORT_SYMBOL(generic_card_pcm_tospan); +EXPORT_SYMBOL(generic_card_pcm_fromspan); + |