dahdi-linux/drivers/dahdi/xpp/xbus-pcm.c
Shaun Ruffell bf3fe05dfb wct4xxp: Moving the transmit short detection behind debug module param.
This needs some more testing before it's on by default.  If the card is
otherwise functioning, these messages may be confusing to the user.  If
the card is not functioning, the driver can be reloaded with debug to
check for this condition.

Signed-off-by: Shaun Ruffell <sruffell@digium.com>

git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@9205 a0bf4364-ded3-4de4-8d8a-66a801d63aff
2010-08-27 21:59:27 +00:00

1350 lines
37 KiB
C

/*
* 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_dahdi.h"
#include "dahdi_debug.h"
#include "parport_debug.h"
static const char rcsid[] = "$Id$";
extern int debug;
#ifdef OPTIMIZE_CHANMUTE
static DEF_PARM_BOOL(optimize_chanmute, 1, 0644, "Optimize by muting inactive channels");
#endif
static 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
static DEF_PARM_BOOL(disable_pll_sync, 0, 0644, "Disable automatic adjustment of AB clocks");
static xbus_t *syncer; /* current syncer */
static atomic_t xpp_tick_counter = ATOMIC_INIT(0);
static struct xpp_ticker dahdi_ticker;
/*
* The ref_ticker points to the current referece tick source.
* I.e: one of our AB or dahdi_ticker
*/
static struct xpp_ticker *ref_ticker = NULL;
static spinlock_t ref_ticker_lock = SPIN_LOCK_UNLOCKED;
static spinlock_t elect_syncer_lock = SPIN_LOCK_UNLOCKED;
static bool force_dahdi_sync = 0; /* from /sys/bus/astribanks/drivers/xppdrv/sync */
static xbus_t *global_ticker;
static struct xpp_ticker global_ticks_series;
#define PROC_SYNC "sync"
#define SYNC_CYCLE 500 /* Sampling cycle in usec */
#define SYNC_CYCLE_SAMPLE 100 /* Samples from end of SYNC_CYCLE */
#define SYNC_CONVERGE 10 /* Number of SYNC_CYCLE's to converge speed */
#define SYNC_CENTER 500 /* Offset from ref_ticker to other AB's */
#define SYNC_DELTA 40 /* If within +/-SYNC_DELTA, try to stay there */
#define BIG_TICK_INTERVAL 1000
#define SYNC_ADJ_MAX 20 /* maximal firmware drift unit (hardware limit 63) */
/*
* The USB bulk endpoints have a large jitter in the timing of frames
* from the AB to the ehci-hcd. This is because we cannot predict
* in which USB micro-frame our data passes. Each micro-frame is
* A 125 usec.
*/
#define SYNC_ADJ_QUICK 1000
#define SYNC_ADJ_SLOW 10000
#ifdef DAHDI_SYNC_TICK
static unsigned int dahdi_tick_count = 0;
#endif
/*------------------------- SYNC Handling --------------------------*/
static void send_drift(xbus_t *xbus, int drift);
static void ticker_set_cycle(struct xpp_ticker *ticker, int cycle)
{
unsigned long flags;
spin_lock_irqsave(&ticker->lock, flags);
if(cycle < SYNC_ADJ_QUICK)
cycle = SYNC_ADJ_QUICK;
if(cycle > SYNC_ADJ_SLOW)
cycle = SYNC_ADJ_SLOW;
ticker->cycle = cycle;
spin_unlock_irqrestore(&ticker->lock, flags);
}
static void xpp_ticker_init(struct xpp_ticker *ticker)
{
memset(ticker, 0, sizeof(*ticker));
spin_lock_init(&ticker->lock);
do_gettimeofday(&ticker->last_sample.tv);
ticker->first_sample = ticker->last_sample;
ticker_set_cycle(ticker, SYNC_ADJ_QUICK);
}
static int xpp_ticker_step(struct xpp_ticker *ticker, const struct timeval *t)
{
unsigned long flags;
long usec;
bool cycled = 0;
spin_lock_irqsave(&ticker->lock, flags);
ticker->last_sample.tv = *t;
if((ticker->count % ticker->cycle) == ticker->cycle - 1) { /* rate adjust */
usec = (long)usec_diff(
&ticker->last_sample.tv,
&ticker->first_sample.tv);
ticker->first_sample = ticker->last_sample;
ticker->tick_period = usec / ticker->cycle;
cycled = 1;
}
ticker->count++;
spin_unlock_irqrestore(&ticker->lock, flags);
return cycled;
}
/*
* No locking. It is called only from:
* - update_sync_master() in a globall spinlock protected code.
* - initalization.
*/
static inline void xbus_drift_clear(xbus_t *xbus)
{
struct xpp_drift *di = &xbus->drift;
do_gettimeofday(&di->last_lost_tick.tv);
ticker_set_cycle(&xbus->ticker, SYNC_ADJ_QUICK);
di->max_speed = -SYNC_ADJ_MAX;
di->min_speed = SYNC_ADJ_MAX;
}
void xpp_drift_init(xbus_t *xbus)
{
memset(&xbus->drift, 0, sizeof(xbus->drift));
spin_lock_init(&xbus->drift.lock);
xpp_ticker_init(&xbus->ticker);
xbus_drift_clear(xbus);
}
#ifdef SAMPLE_TICKS
static void sample_tick(xbus_t *xbus, int sample)
{
if(!xbus->sample_running)
return;
if(xbus->sample_pos < SAMPLE_SIZE)
xbus->sample_ticks[xbus->sample_pos++] = sample;
else {
xbus->sample_running = 0;
xbus->sample_pos = 0;
}
}
#else
#define sample_tick(x,y)
#endif
/*
* The following function adjust the clock of an astribank according
* to our reference clock (another astribank or another DAHDI device).
*
* It is VERY hard to stabilise these corrections:
* - The measurments are affected by 125usec USB micro-frames.
* - However, if we are off by more than +/-500usec we are risking
* loosing frames.
*
* Every change must be tested rigorously with many device combinations
* including abrupt changes in sync -- to verify the stability of the
* algorithm.
*/
static void xpp_drift_step(xbus_t *xbus, const struct timeval *tv)
{
struct xpp_drift *di = &xbus->drift;
struct xpp_ticker *ticker = &xbus->ticker;
unsigned long flags;
spin_lock_irqsave(&di->lock, flags);
xpp_ticker_step(&xbus->ticker, tv);
/*
* Do we need to be synchronized and is there an established reference
* ticker (another Astribank or another DAHDI device) already?
*/
if(ref_ticker && ref_ticker != &xbus->ticker && syncer && xbus->sync_mode == SYNC_MODE_PLL) {
int new_delta_tick = ticker->count - ref_ticker->count;
int lost_ticks = new_delta_tick - di->delta_tick;
long usec_delta;
di->delta_tick = new_delta_tick;
if(lost_ticks) {
static int rate_limit;
/*
* We just lost some ticks. Just report it and don't
* try to adjust anything until we are stable again.
*/
di->lost_ticks++;
di->lost_tick_count += abs(lost_ticks);
if((rate_limit++ % 1003) == 0) {
/* FIXME: This should be a NOTICE.
* However we have several false ones at
* startup.
*/
XBUS_DBG(SYNC, xbus, "Lost %d tick%s\n",
lost_ticks,
(abs(lost_ticks) > 1) ? "s": "");
}
if(abs(lost_ticks) > 100) {
xbus_drift_clear(xbus);
ticker->count = ref_ticker->count;
}
} else {
/* Sample a delta */
usec_delta = (long)usec_diff(
&ticker->last_sample.tv,
&ref_ticker->last_sample.tv);
sample_tick(xbus, usec_delta);
if ((ticker->count % SYNC_CYCLE) > (SYNC_CYCLE - SYNC_CYCLE_SAMPLE))
di->delta_sum += usec_delta;
if((ticker->count % SYNC_CYCLE) == 0) {
/*
* Full sampling cycle passed. Let's calculate
*/
int offset = di->delta_sum / SYNC_CYCLE_SAMPLE - SYNC_CENTER;
int offset_prev = di->offset_prev;
int speed = xbus->sync_adjustment;
int fix = 0;
int best_speed = (di->max_speed + di->min_speed) >> 1;
if (offset > 0 && offset < SYNC_DELTA) {
speed = best_speed - 1;
} else if (offset < 0 && offset > -SYNC_DELTA) {
speed = best_speed + 1;
} else {
if (offset > 0) {
if (offset > offset_prev)
fix--;
} else {
if (offset < offset_prev)
fix++;
}
speed += fix;
}
if (speed < -SYNC_ADJ_MAX)
speed = -SYNC_ADJ_MAX;
if (speed > SYNC_ADJ_MAX)
speed = SYNC_ADJ_MAX;
if (speed < di->min_speed)
di->min_speed = speed;
if (speed > di->max_speed)
di->max_speed = speed;
if(offset > di->offset_max)
di->offset_max = offset;
if(offset < di->offset_min)
di->offset_min = offset;
XBUS_DBG(SYNC, xbus, "offset: %d, min_speed=%d, max_speed=%d, usec_delta(last)=%ld\n",
offset_prev, di->min_speed, di->max_speed, usec_delta);
XBUS_DBG(SYNC, xbus, "ADJ: speed=%d (best_speed=%d) fix=%d\n",
speed, best_speed, fix);
xbus->sync_adjustment_offset = speed;
if(xbus != syncer && xbus->sync_adjustment != speed)
send_drift(xbus, speed);
di->sync_inaccuracy = abs(offset) + abs(di->offset_range) / 2;
if(ticker->count >= SYNC_CYCLE * SYNC_CONVERGE) {
di->offset_range = di->offset_max - di->offset_min;
di->offset_min = INT_MAX;
di->offset_max = -INT_MAX;
if(di->max_speed > best_speed)
di->max_speed--;
if(di->min_speed < best_speed)
di->min_speed++;
}
di->offset_prev = offset;
di->delta_sum = 0;
}
}
}
spin_unlock_irqrestore(&di->lock, flags);
}
const char *sync_mode_name(enum sync_mode mode)
{
static const char *sync_mode_names[] = {
[SYNC_MODE_AB] = "AB",
[SYNC_MODE_NONE] = "NONE",
[SYNC_MODE_PLL] = "PLL",
[SYNC_MODE_QUERY] = "QUERY",
};
if(mode >= ARRAY_SIZE(sync_mode_names))
return NULL;
return sync_mode_names[mode];
}
/*
* Caller must aquire/release the 'ref_ticker_lock' spinlock
*/
static void xpp_set_syncer(xbus_t *xbus, bool on)
{
if(!xbus) { /* Special case, no more syncers */
DBG(SYNC, "No more syncers\n");
syncer = NULL;
if(ref_ticker != &dahdi_ticker)
ref_ticker = NULL;
return;
}
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;
if(ref_ticker != &dahdi_ticker)
ref_ticker = NULL;
} else
XBUS_DBG(SYNC, xbus, "ignore %s (current syncer: %s)\n",
(on)?"ON":"OFF",
(syncer) ? syncer->busname : "NO-SYNC");
}
static 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);
}
} else if(timer_pending(&xbus->command_timer)) {
XBUS_DBG(SYNC, xbus, "del_timer\n");
del_timer(&xbus->command_timer);
}
xbus->self_ticking = ! on;
}
/*
* 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;
unsigned long flags2;
spin_lock_irqsave(&xbus->lock, flags);
spin_lock_irqsave(&ref_ticker_lock, flags2);
xbus->sync_adjustment = (signed char)drift;
if(xbus->sync_mode == mode) {
XBUS_DBG(SYNC, xbus, "Already in mode '%s'. Ignored\n",
sync_mode_name(mode));
goto out;
}
XBUS_DBG(SYNC, xbus, "Mode %s (%d), drift=%d (pcm_rx_counter=%d)\n",
sync_mode_name(mode), mode, drift, atomic_read(&xbus->pcm_rx_counter));
switch(mode) {
case SYNC_MODE_AB:
xbus->sync_mode = mode;
xbus_set_command_timer(xbus, 0);
xpp_set_syncer(xbus, 1);
global_ticker = xbus;
break;
case SYNC_MODE_PLL:
xbus->sync_mode = mode;
xbus_set_command_timer(xbus, 0);
xpp_set_syncer(xbus, 0);
global_ticker = xbus;
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(&ref_ticker_lock, flags2);
spin_unlock_irqrestore(&xbus->lock, flags);
}
void xbus_request_sync(xbus_t *xbus, enum sync_mode mode)
{
unsigned long flags;
BUG_ON(!xbus);
XBUS_DBG(SYNC, xbus, "sent request (mode=%d)\n", mode);
CALL_PROTO(GLOBAL, SYNC_SOURCE, xbus, NULL, mode, 0);
if(mode == SYNC_MODE_NONE) {
/*
* We must deselect the syncer *now* and not wait for the
* reply from the AB. Otherwise, a disconnect of the syncing
* AB would result in a corrupted 'syncer'.
*/
spin_lock_irqsave(&ref_ticker_lock, flags);
xpp_set_syncer(xbus, 0);
spin_unlock_irqrestore(&ref_ticker_lock, flags);
/*
* We must activate timer now since the commands go to
* the command queue, and we should maintain something to
* "tick" meanwhile until the AB get these commands and
* start being "self_ticking".
*/
xbus_set_command_timer(xbus, 1);
}
}
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(__func__, i);
if (xbus == NULL)
continue;
/*
* Don't send to non self_ticking Astribanks:
* - Maybe they didn't finish initialization
* - Or maybe they didn't answer us in the first place
(e.g: wrong firmware version, etc).
*/
if(xbus->self_ticking) {
if(!XBUS_FLAGS(xbus, CONNECTED)) {
XBUS_DBG(GENERAL, xbus,
"Dropped packet. Is shutting down.\n");
} else {
/* Reset sync LEDs once in a while */
CALL_PROTO(GLOBAL, RESET_SYNC_COUNTERS, xbus, NULL);
}
}
put_xbus(__func__, xbus);
}
}
static void send_drift(xbus_t *xbus, int drift)
{
struct timeval now;
const char *msg;
BUG_ON(abs(drift) > SYNC_ADJ_MAX);
do_gettimeofday(&now);
if(drift > xbus->sync_adjustment)
msg = "up";
else
msg = "down";
XBUS_DBG(SYNC, xbus, "%sDRIFT adjust %s (%d) (last update %ld seconds ago)\n",
(disable_pll_sync) ? "Fake " : "",
msg, drift, now.tv_sec - xbus->pll_updated_at);
if(!disable_pll_sync)
CALL_PROTO(GLOBAL, SYNC_SOURCE, xbus, NULL, SYNC_MODE_PLL, drift);
xbus->pll_updated_at = now.tv_sec;
}
static void global_tick(void)
{
struct timeval now;
do_gettimeofday(&now);
atomic_inc(&xpp_tick_counter);
if((atomic_read(&xpp_tick_counter) % BIG_TICK_INTERVAL) == 0)
reset_sync_counters();
xpp_ticker_step(&global_ticks_series, &now);
}
#ifdef DAHDI_SYNC_TICK
int dahdi_sync_tick(struct dahdi_span *span, int is_master)
{
xpd_t *xpd = container_of(span, struct xpd, span);
static int redundant_ticks; /* for extra spans */
struct timeval now;
if(!force_dahdi_sync)
goto noop;
do_gettimeofday(&now);
BUG_ON(!xpd);
/*
* Detect if any of our spans is dahdi sync master
*/
if(is_master) {
static int rate_limit;
if((rate_limit++ % 10003) == 0)
XPD_NOTICE(xpd, "Is a DAHDI sync master: ignore sync from DAHDI\n");
goto noop;
}
/* Now we know for sure someone else is dahdi sync master */
if(syncer) {
static int rate_limit;
if((rate_limit++ % 5003) == 0)
XBUS_DBG(SYNC, syncer,
"is a SYNCer: ignore sync from DAHDI\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", dahdi_tick_count);
#endif
goto noop;
}
xpp_ticker_step(&dahdi_ticker, &now);
dahdi_tick_count++;
//flip_parport_bit(1);
return 0;
noop:
return 0; /* No auto sync from dahdi */
}
#endif
/*
* called from elect_syncer()
* if new_syncer is NULL, than we move all to SYNC_MODE_PLL
* for DAHDI sync.
*/
static void update_sync_master(xbus_t *new_syncer, bool force_dahdi)
{
const char *msg;
int i;
unsigned long flags;
WARN_ON(new_syncer && force_dahdi); /* Ambigous */
force_dahdi_sync = force_dahdi;
msg = (force_dahdi_sync) ? "DAHDI" : "NO-SYNC";
DBG(SYNC, "%s => %s\n",
(syncer) ? syncer->busname : msg,
(new_syncer) ? new_syncer->busname : msg);
/*
* This global locking protects:
* - The ref_ticker so it won't be used while we change it.
* - The xbus_drift_clear() from corrupting driftinfo data.
* It's important to set ref_ticker now:
* - We cannot make the new xbus a syncer yet (until we get
* a reply from AB). Maybe it's still not self_ticking, so
* we must keep the timer for the command_queue to function.
* - However, we must not send drift commands to it, because
* they'll revert it to PLL instead of AB.
*/
spin_lock_irqsave(&ref_ticker_lock, flags);
if(syncer)
xbus_drift_clear(syncer); /* Clean old data */
if(new_syncer) {
XBUS_DBG(SYNC, new_syncer, "pcm_rx_counter=%d\n",
atomic_read(&new_syncer->pcm_rx_counter));
force_dahdi_sync = 0;
ref_ticker = &new_syncer->ticker;
xbus_drift_clear(new_syncer); /* Clean new data */
xpp_set_syncer(new_syncer, 1);
xbus_request_sync(new_syncer, SYNC_MODE_AB);
} else if(force_dahdi_sync) {
ref_ticker = &dahdi_ticker;
} else {
ref_ticker = NULL;
}
spin_unlock_irqrestore(&ref_ticker_lock, flags);
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(__func__, i);
if (xbus == NULL)
continue;
if(XBUS_FLAGS(xbus, CONNECTED) && xbus != new_syncer) {
if(xbus->self_ticking)
xbus_request_sync(xbus,
xbus->sync_mode_default);
else
XBUS_DBG(SYNC, xbus, "Not self_ticking yet. Ignore\n");
}
put_xbus(__func__, xbus);
}
}
void elect_syncer(const char *msg)
{
int i;
int j;
uint timing_priority = INT_MAX;
xpd_t *best_xpd = NULL;
xbus_t *the_xbus = NULL;
unsigned long flags;
spin_lock_irqsave(&elect_syncer_lock, flags);
DBG(SYNC, "%s: %s syncer=%s\n", __func__, msg,
(syncer) ? syncer->busname : "NULL");
for(i = 0; i < MAX_BUSES; i++) {
xbus_t *xbus = get_xbus(__func__, i);
if (xbus == NULL)
continue;
if(XBUS_IS(xbus, READY)) {
if(!the_xbus)
the_xbus = xbus; /* First candidate */
for(j = 0; j < MAX_XPDS; j++) {
xpd_t *xpd = xpd_of(xbus, j);
int prio;
if(!xpd || !xpd->card_present)
continue;
prio = CALL_XMETHOD(card_timing_priority,
xbus, xpd);
if (prio < 0) {
DBG(SYNC, "%s/%s: skip sync\n",
xbus->busname, xpd->xpdname);
continue;
}
if (prio > 0 && prio < timing_priority) {
timing_priority = prio;
best_xpd = xpd;
}
}
}
put_xbus(__func__, 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 {
unsigned long flags;
DBG(SYNC, "%s: No more syncers\n", msg);
spin_lock_irqsave(&ref_ticker_lock, flags);
xpp_set_syncer(NULL, 0);
spin_unlock_irqrestore(&ref_ticker_lock, flags);
the_xbus = NULL;
}
if(the_xbus != syncer)
update_sync_master(the_xbus, force_dahdi_sync);
spin_unlock_irqrestore(&elect_syncer_lock, flags);
}
/*
* This function should be called with the xpd already locked
*/
void update_wanted_pcm_mask(xpd_t *xpd, xpp_line_t new_mask, uint new_pcm_len)
{
xpd->pcm_len = new_pcm_len;
xpd->wanted_pcm_mask = new_mask;
XPD_DBG(SIGNAL, xpd, "pcm_len=%d wanted_pcm_mask=0x%X\n",
xpd->pcm_len, xpd->wanted_pcm_mask);
}
/*
* 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.
*/
void generic_card_pcm_recompute(xbus_t *xbus, xpd_t *xpd, xpp_line_t pcm_mask)
{
int i;
int line_count = 0;
unsigned long flags;
uint pcm_len;
spin_lock_irqsave(&xpd->lock_recompute_pcm, flags);
//XPD_DBG(SIGNAL, xpd, "pcm_mask=0x%X\n", pcm_mask);
/* Add/remove all the trivial cases */
pcm_mask |= xpd->offhook_state;
pcm_mask |= xpd->oht_pcm_pass;
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;
}
pcm_len = (line_count)
? RPACKET_HEADERSIZE + sizeof(xpp_line_t) + line_count * DAHDI_CHUNKSIZE
: 0L;
update_wanted_pcm_mask(xpd, pcm_mask, pcm_len);
spin_unlock_irqrestore(&xpd->lock_recompute_pcm, 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, DAHDI_CHUNKSIZE);
}
static void do_ec(xpd_t *xpd)
{
int i;
for (i = 0;i < xpd->span.channels; i++) {
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
if(unlikely(IS_SET(xpd->digital_signalling, i))) /* Don't echo cancel BRI D-chans */
continue;
if(!IS_SET(xpd->wanted_pcm_mask, i)) /* No ec for unwanted PCM */
continue;
dahdi_ec_chunk(chan, chan->readchunk, xpd->ec_chunk2[i]);
memcpy(xpd->ec_chunk2[i], xpd->ec_chunk1[i], DAHDI_CHUNKSIZE);
memcpy(xpd->ec_chunk1[i], chan->writechunk, DAHDI_CHUNKSIZE);
}
}
#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 DAHDI_FLAG_RBS in this case. */
/* Opt: If the span uses A/B bits, set them here */
int (*rbsbits)(struct dahdi_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 DAHDI_FLAG_RBS in this case as well */
int (*hooksig)(struct dahdi_chan *chan, enum dahdi_txsig hookstate);
/* Option 3: If you can't use sig bits, you can write a function
which handles the individual hook states */
int (*sethook)(struct dahdi_chan *chan, int hookstate);
#endif
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 || !XBUS_IS(xbus, READY)))
goto dropit;
if(XPACKET_ADDR_SYNC((xpacket_t *)xframe->packets)) {
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(debug & DBG_PCM )
dump_xframe("TX_XFRAME_PCM", xbus, xframe, debug);
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, xpacket_t *pack)
{
byte *pcm;
unsigned long flags;
xpp_line_t wanted_lines;
int i;
BUG_ON(!xbus);
BUG_ON(!xpd);
BUG_ON(!pack);
wanted_lines = xpd->wanted_pcm_mask;
RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = wanted_lines;
pcm = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, pcm);
spin_lock_irqsave(&xpd->lock, flags);
for (i = 0; i < xpd->channels; i++) {
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
if(IS_SET(wanted_lines, i)) {
if(SPAN_REGISTERED(xpd)) {
#ifdef DEBUG_PCMTX
int channo = chan->channo;
if(pcmtx >= 0 && pcmtx_chan == channo)
memset((u_char *)pcm, pcmtx, DAHDI_CHUNKSIZE);
else
#endif
memcpy((u_char *)pcm, chan->writechunk, DAHDI_CHUNKSIZE);
} else
memset((u_char *)pcm, 0x7F, DAHDI_CHUNKSIZE);
pcm += DAHDI_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;
xpp_line_t pcm_mute;
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);
/*
* Calculate the channels we want to mute
*/
pcm_mute = ~xpd->wanted_pcm_mask;
pcm_mute |= xpd->mute_dtmf | xpd->silence_pcm;
if(!SPAN_REGISTERED(xpd))
goto out;
for (i = 0; i < xpd->channels; i++) {
volatile u_char *r = XPD_CHAN(xpd, i)->readchunk;
bool got_data = IS_SET(pcm_mask, i);
if(got_data && !IS_SET(pcm_mute, i)) {
/* We have and want real data */
// memset((u_char *)r, 0x5A, DAHDI_CHUNKSIZE); // DEBUG
memcpy((u_char *)r, pcm, DAHDI_CHUNKSIZE);
} else if(IS_SET(xpd->wanted_pcm_mask | xpd->silence_pcm, i)) {
/* Inject SILENCE */
memset((u_char *)r, 0x7F, DAHDI_CHUNKSIZE);
if(IS_SET(xpd->silence_pcm, i)) {
/*
* This will clear the EC buffers until next tick
* So we don't have noise residues from the past.
*/
memset(xpd->ec_chunk2[i], 0x7F, DAHDI_CHUNKSIZE);
memset(xpd->ec_chunk1[i], 0x7F, DAHDI_CHUNKSIZE);
}
}
if(got_data)
pcm += DAHDI_CHUNKSIZE;
}
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(debug & DBG_PCM)
dump_xframe("RX_XFRAME_PCM", xbus, xframe, debug);
/* 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, debug);
}
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, debug);
}
goto out;
}
xpd = xpd_byaddr(xbus, XPACKET_ADDR_UNIT(pack), XPACKET_ADDR_SUBUNIT(pack));
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, debug);
}
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;
}
int generic_timing_priority(xbus_t *xbus, xpd_t *xpd)
{
return xpd->timing_priority;
}
static void xbus_tick(xbus_t *xbus)
{
int i;
xpd_t *xpd;
xframe_t *xframe = NULL;
xpacket_t *pack = NULL;
bool sent_sync_bit = 0;
/*
* Update dahdi
*/
for(i = 0; i < MAX_XPDS; i++) {
xpd = xpd_of(xbus, i);
if(xpd && SPAN_REGISTERED(xpd)) {
#ifdef OPTIMIZE_CHANMUTE
int j;
xpp_line_t xmit_mask = xpd->wanted_pcm_mask;
xmit_mask |= xpd->silence_pcm;
xmit_mask |= xpd->digital_signalling;
for_each_line(xpd, j) {
XPD_CHAN(xpd, j)->chanmute = (optimize_chanmute)
? !IS_SET(xmit_mask, j)
: 0;
}
#endif
/*
* calls to dahdi_transmit should be out of spinlocks, as it may call back
* our hook setting methods.
*/
dahdi_transmit(&xpd->span);
}
}
/*
* Fill xframes
*/
for(i = 0; i < MAX_XPDS; i++) {
size_t pcm_len;
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_SYNC(pack) = 1;
sent_sync_bit = 1;
}
CALL_XMETHOD(card_pcm_fromspan, xbus, xpd, pack);
XBUS_COUNTER(xbus, TX_PACK_PCM)++;
}
}
}
if(xframe) /* clean any leftovers */
pcm_frame_out(xbus, xframe);
/*
* Receive PCM
*/
while((xframe = xframe_dequeue(&xbus->pcm_tospan)) != NULL) {
copy_pcm_tospan(xbus, xframe);
if(XPACKET_ADDR_SYNC((xpacket_t *)xframe->packets)) {
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);
dahdi_receive(&xpd->span);
}
xpd->silence_pcm = 0; /* silence was injected */
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);
}
}
static void do_tick(xbus_t *xbus, const struct timeval *tv_received)
{
int counter = atomic_read(&xpp_tick_counter);
unsigned long flags;
xbus_command_queue_tick(xbus);
if(global_ticker == xbus)
global_tick(); /* called from here or dahdi_sync_tick() */
spin_lock_irqsave(&ref_ticker_lock, flags);
xpp_drift_step(xbus, tv_received);
spin_unlock_irqrestore(&ref_ticker_lock, flags);
if(likely(xbus->self_ticking))
xbus_tick(xbus);
xbus->global_counter = counter;
}
void xframe_receive_pcm(xbus_t *xbus, xframe_t *xframe)
{
if(!xframe_enqueue(&xbus->pcm_tospan, 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_SYNC((xpacket_t *)xframe->packets)) {
do_tick(xbus, &xframe->tv_received);
atomic_inc(&xbus->pcm_rx_counter);
} else
xbus->xbus_frag_count++;
}
int exec_sync_command(const char *buf, size_t count)
{
int ret = count;
int xbusno;
xbus_t *xbus;
if(strncmp("DAHDI", buf, 6) == 0) { /* Ignore the newline */
DBG(SYNC, "DAHDI");
update_sync_master(NULL, 1);
} else if(sscanf(buf, "SYNC=%d", &xbusno) == 1) {
DBG(SYNC, "SYNC=%d\n", xbusno);
xbus = get_xbus(__func__, xbusno);
if (xbus == NULL) {
ERR("No bus %d exists\n", xbusno);
return -ENXIO;
}
update_sync_master(xbus, 0);
put_xbus(__func__, xbus);
} else if(sscanf(buf, "QUERY=%d", &xbusno) == 1) {
DBG(SYNC, "QUERY=%d\n", xbusno);
xbus = get_xbus(__func__, xbusno);
if (xbus == NULL) {
ERR("No bus %d exists\n", xbusno);
return -ENXIO;
}
CALL_PROTO(GLOBAL, SYNC_SOURCE, xbus, NULL, SYNC_MODE_QUERY, 0);
put_xbus(__func__, xbus);
} else {
ERR("%s: cannot parse '%s'\n", __FUNCTION__, buf);
ret = -EINVAL;
}
return ret;
}
int fill_sync_string(char *buf, size_t count)
{
int len = 0;
if(!syncer) {
len += snprintf(buf, count, "%s\n",
(force_dahdi_sync) ? "DAHDI" : "NO-SYNC");
} else
len += snprintf(buf, count, "SYNC=%02d\n", syncer->num);
return len;
}
#ifdef OLD_PROC
#ifdef CONFIG_PROC_FS
static 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);
NOTICE("%s: DEPRECATED: %s[%d] read from /proc interface instead of /sys\n",
__FUNCTION__, current->comm, current->tgid);
len += sprintf(page + len, "# To modify sync source write into this file:\n");
len += sprintf(page + len, "# DAHDI - Another dahdi 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");
len += fill_sync_string(page + len, PAGE_SIZE - len);
#ifdef DAHDI_SYNC_TICK
if(force_dahdi_sync) {
len += sprintf(page + len,
"Dahdi Reference Sync (%d registered spans):\n",
total_registered_spans());
len += sprintf(page + len, "\tdahdi_tick: #%d\n", dahdi_tick_count);
len += sprintf(page + len, "\ttick - dahdi_tick = %d\n",
counter - dahdi_tick_count);
} else {
len += sprintf(page + len,
"Dahdi Reference Sync Not activated\n");
}
#endif
usec = usec_diff(&now, &global_ticks_series.last_sample.tv);
len += sprintf(page + len, "\ntick: #%d\n", counter);
len += sprintf(page + len,
"tick duration: %d usec (measured %ld.%ld msec ago)\n",
global_ticks_series.tick_period,
usec / 1000, usec % 1000);
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];
// DBG(SYNC, "%s: count=%ld\n", __FUNCTION__, count);
NOTICE("%s: DEPRECATED: %s[%d] write to /proc interface instead of /sys\n",
__FUNCTION__, current->comm, current->tgid);
if(count >= MAX_PROC_WRITE)
return -EINVAL;
if(copy_from_user(buf, buffer, count))
return -EFAULT;
buf[count] = '\0';
return exec_sync_command(buf, count);
}
static struct proc_dir_entry *top;
#endif
#endif /* OLD_PROC */
int xbus_pcm_init(void *toplevel)
{
int ret = 0;
#ifdef OPTIMIZE_CHANMUTE
INFO("FEATURE: with CHANMUTE optimization (%sactivated)\n",
(optimize_chanmute)?"":"de");
#endif
#ifdef DAHDI_SYNC_TICK
INFO("FEATURE: with sync_tick() from DAHDI\n");
#else
INFO("FEATURE: without sync_tick() from DAHDI\n");
#endif
xpp_ticker_init(&global_ticks_series);
xpp_ticker_init(&dahdi_ticker);
#ifdef OLD_PROC
#ifdef CONFIG_PROC_FS
{
struct proc_dir_entry *ent;
top = toplevel;
ent = create_proc_entry(PROC_SYNC, 0644, top);
if(ent) {
ent->read_proc = proc_sync_read;
ent->write_proc = proc_sync_write;
ent->data = NULL;
} else {
ret = -EFAULT;
}
}
#endif
#endif /* OLD_PROC */
return ret;
}
void xbus_pcm_shutdown(void)
{
#ifdef OLD_PROC
#ifdef CONFIG_PROC_FS
DBG(GENERAL, "Removing '%s' from proc\n", PROC_SYNC);
remove_proc_entry(PROC_SYNC, top);
#endif
#endif /* OLD_PROC */
}
EXPORT_SYMBOL(xbus_request_sync);
EXPORT_SYMBOL(got_new_syncer);
EXPORT_SYMBOL(elect_syncer);
#ifdef DAHDI_SYNC_TICK
EXPORT_SYMBOL(dahdi_sync_tick);
#endif
EXPORT_SYMBOL(update_wanted_pcm_mask);
EXPORT_SYMBOL(generic_card_pcm_recompute);
EXPORT_SYMBOL(generic_card_pcm_tospan);
EXPORT_SYMBOL(generic_card_pcm_fromspan);
EXPORT_SYMBOL(generic_timing_priority);
#ifdef DEBUG_PCMTX
EXPORT_SYMBOL(pcmtx);
EXPORT_SYMBOL(pcmtx_chan);
#endif