dahdi-linux/drivers/dahdi/xpp/xbus-pcm.c

1287 lines
35 KiB
C
Raw Normal View History

/*
* 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 DEFINE_SPINLOCK(ref_ticker_lock);
static DEFINE_SPINLOCK(elect_syncer_lock);
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
void dahdi_sync_tick(struct dahdi_span *span, int is_master)
{
struct phonedev *phonedev = container_of(span, struct phonedev, span);
xpd_t *xpd = container_of(phonedev, struct xpd, phonedev);
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);
noop:
return;
}
#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 || !IS_PHONEDEV(xpd))
continue;
prio = CALL_PHONE_METHOD(card_timing_priority, 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)
{
PHONEDEV(xpd).pcm_len = new_pcm_len;
PHONEDEV(xpd).wanted_pcm_mask = new_mask;
XPD_DBG(SIGNAL, xpd, "pcm_len=%d wanted_pcm_mask=0x%X\n",
PHONEDEV(xpd).pcm_len, PHONEDEV(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(xpd_t *xpd, xpp_line_t pcm_mask)
{
int i;
int line_count = 0;
unsigned long flags;
uint pcm_len;
spin_lock_irqsave(&PHONEDEV(xpd).lock_recompute_pcm, flags);
//XPD_DBG(SIGNAL, xpd, "pcm_mask=0x%X\n", pcm_mask);
/* Add/remove all the trivial cases */
pcm_mask |= PHONEDEV(xpd).offhook_state;
pcm_mask |= PHONEDEV(xpd).oht_pcm_pass;
pcm_mask &= ~(PHONEDEV(xpd).digital_inputs);
pcm_mask &= ~(PHONEDEV(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(&PHONEDEV(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 < PHONEDEV(xpd).span.channels; i++) {
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
if(unlikely(IS_SET(PHONEDEV(xpd).digital_signalling, i))) /* Don't echo cancel BRI D-chans */
continue;
if(!IS_SET(PHONEDEV(xpd).wanted_pcm_mask, i)) /* No ec for unwanted PCM */
continue;
dahdi_ec_chunk(chan, chan->readchunk, PHONEDEV(xpd).ec_chunk2[i]);
memcpy(PHONEDEV(xpd).ec_chunk2[i], PHONEDEV(xpd).ec_chunk1[i], DAHDI_CHUNKSIZE);
memcpy(PHONEDEV(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(xpd_t *xpd, xpacket_t *pack)
{
byte *pcm;
unsigned long flags;
xpp_line_t wanted_lines;
int i;
BUG_ON(!xpd);
BUG_ON(!pack);
wanted_lines = PHONEDEV(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 < PHONEDEV(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(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 = ~(PHONEDEV(xpd).wanted_pcm_mask);
pcm_mute |= PHONEDEV(xpd).mute_dtmf | PHONEDEV(xpd).silence_pcm;
if(!SPAN_REGISTERED(xpd))
goto out;
for (i = 0; i < PHONEDEV(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(PHONEDEV(xpd).wanted_pcm_mask | PHONEDEV(xpd).silence_pcm, i)) {
/* Inject SILENCE */
memset((u_char *)r, 0x7F, DAHDI_CHUNKSIZE);
if(IS_SET(PHONEDEV(xpd).silence_pcm, i)) {
/*
* This will clear the EC buffers until next tick
* So we don't have noise residues from the past.
*/
memset(PHONEDEV(xpd).ec_chunk2[i], 0x7F, DAHDI_CHUNKSIZE);
memset(PHONEDEV(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);
}
int generic_echocancel_timeslot(xpd_t *xpd, int pos)
{
return xpd->addr.unit * 32 + pos;
}
EXPORT_SYMBOL(generic_echocancel_timeslot);
int generic_echocancel_setmask(xpd_t *xpd, xpp_line_t ec_mask)
{
int i;
BUG_ON(!xpd);
XPD_DBG(GENERAL, xpd, "0x%8X\n", ec_mask);
if (!ECHOOPS(xpd->xbus)) {
XPD_DBG(GENERAL, xpd,
"No echo canceller in XBUS: Doing nothing.\n");
return -EINVAL;
}
for (i = 0; i < PHONEDEV(xpd).channels; i++) {
int on = BIT(i) & ec_mask;
CALL_EC_METHOD(ec_set, xpd->xbus, xpd, i, on);
}
CALL_EC_METHOD(ec_update, xpd->xbus, xpd->xbus);
return 0;
}
EXPORT_SYMBOL(generic_echocancel_setmask);
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_PHONE_METHOD(card_pcm_tospan, 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(xpd_t *xpd)
{
return PHONEDEV(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 = PHONEDEV(xpd).wanted_pcm_mask;
xmit_mask |= PHONEDEV(xpd).silence_pcm;
xmit_mask |= PHONEDEV(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(&PHONEDEV(xpd).span);
}
}
/*
* Fill xframes
*/
for(i = 0; i < MAX_XPDS; i++) {
if((xpd = xpd_of(xbus, i)) == NULL)
continue;
if (!IS_PHONEDEV(xpd))
continue;
if(SPAN_REGISTERED(xpd)) {
size_t pcm_len = PHONEDEV(xpd).pcm_len;
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_PHONE_METHOD(card_pcm_fromspan, 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 (IS_PHONEDEV(xpd)) {
if(SPAN_REGISTERED(xpd)) {
do_ec(xpd);
dahdi_receive(&PHONEDEV(xpd).span);
}
PHONEDEV(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, 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;
}
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);
return ret;
}
void xbus_pcm_shutdown(void)
{
}
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