dahdi-linux/drivers/dahdi/xpp/card_bri.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

1870 lines
48 KiB
C

/*
* Written by Oron Peled <oron@actcom.co.il>
* Copyright (C) 2004-2006, Xorcom
*
* Parts derived from Cologne demo driver for the chip.
*
* 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/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/delay.h>
#include "xpd.h"
#include "xproto.h"
#include "xpp_dahdi.h"
#include "card_bri.h"
#include "dahdi_debug.h"
#include "xbus-core.h"
static const char rcsid[] = "$Id$";
static DEF_PARM(int, debug, 0, 0644, "Print DBG statements"); /* must be before dahdi_debug.h */
static DEF_PARM(uint, poll_interval, 500, 0644, "Poll channel state interval in milliseconds (0 - disable)");
static DEF_PARM_BOOL(nt_keepalive, 1, 0644, "Force BRI_NT to keep trying connection");
enum xhfc_states {
ST_RESET = 0, /* G/F0 */
/* TE */
ST_TE_SENSING = 2, /* F2 */
ST_TE_DEACTIVATED = 3, /* F3 */
ST_TE_SIGWAIT = 4, /* F4 */
ST_TE_IDENT = 5, /* F5 */
ST_TE_SYNCED = 6, /* F6 */
ST_TE_ACTIVATED = 7, /* F7 */
ST_TE_LOST_FRAMING = 8, /* F8 */
/* NT */
ST_NT_DEACTIVATED = 1, /* G1 */
ST_NT_ACTIVATING = 2, /* G2 */
ST_NT_ACTIVATED = 3, /* G3 */
ST_NT_DEACTIVTING = 4, /* G4 */
};
#ifdef CONFIG_PROC_FS
static const char *xhfc_state_name(bool is_nt, enum xhfc_states state)
{
const char *p;
#define _E(x) [ST_ ## x] = #x
static const char *te_names[] = {
_E(RESET),
_E(TE_SENSING),
_E(TE_DEACTIVATED),
_E(TE_SIGWAIT),
_E(TE_IDENT),
_E(TE_SYNCED),
_E(TE_ACTIVATED),
_E(TE_LOST_FRAMING),
};
static const char *nt_names[] = {
_E(RESET),
_E(NT_DEACTIVATED),
_E(NT_ACTIVATING),
_E(NT_ACTIVATED),
_E(NT_DEACTIVTING),
};
#undef _E
if(is_nt) {
if ((state < ST_RESET) || (state > ST_NT_DEACTIVTING))
p = "NT ???";
else
p = nt_names[state];
} else {
if ((state < ST_RESET) || (state > ST_TE_LOST_FRAMING))
p = "TE ???";
else
p = te_names[state];
}
return p;
}
#endif
/* xhfc Layer1 physical commands */
#define HFC_L1_ACTIVATE_TE 0x01
#define HFC_L1_FORCE_DEACTIVATE_TE 0x02
#define HFC_L1_ACTIVATE_NT 0x03
#define HFC_L1_DEACTIVATE_NT 0x04
#define HFC_L1_ACTIVATING 1
#define HFC_L1_ACTIVATED 2
#define HFC_TIMER_T1 2500
#define HFC_TIMER_T3 8000 /* 8s activation timer T3 */
#define HFC_TIMER_OFF -1 /* timer disabled */
#define A_SU_WR_STA 0x30 /* ST/Up state machine register */
#define V_SU_LD_STA 0x10
#define V_SU_ACT 0x60 /* start activation/deactivation */
#define STA_DEACTIVATE 0x40 /* start deactivation in A_SU_WR_STA */
#define STA_ACTIVATE 0x60 /* start activation in A_SU_WR_STA */
#define V_SU_SET_G2_G3 0x80
#define A_SU_RD_STA 0x30
typedef union {
struct {
byte v_su_sta:4;
byte v_su_fr_sync:1;
byte v_su_t2_exp:1;
byte v_su_info0:1;
byte v_g2_g3:1;
} bits;
byte reg;
} su_rd_sta_t;
#define REG30_LOST 3 /* in polls */
#define DCHAN_LOST 15000 /* in ticks */
#ifdef CONFIG_DAHDI_BRI_DCHANS
#define BRI_DCHAN_SIGCAP ( \
DAHDI_SIG_EM | \
DAHDI_SIG_CLEAR | \
DAHDI_SIG_FXSLS | \
DAHDI_SIG_FXSGS | \
DAHDI_SIG_FXSKS | \
DAHDI_SIG_FXOLS | \
DAHDI_SIG_FXOGS | \
DAHDI_SIG_FXOKS | \
DAHDI_SIG_CAS | \
DAHDI_SIG_SF \
)
#else
#define BRI_DCHAN_SIGCAP DAHDI_SIG_HARDHDLC
#endif
#define BRI_BCHAN_SIGCAP (DAHDI_SIG_CLEAR | DAHDI_SIG_DACS)
#define IS_NT(xpd) ((xpd)->direction == TO_PHONE)
#define BRI_PORT(xpd) ((xpd)->addr.subunit)
/* shift in PCM highway */
#define SUBUNIT_PCM_SHIFT 4
#define PCM_SHIFT(mask, sunit) ((mask) << (SUBUNIT_PCM_SHIFT * (sunit)))
/*---------------- BRI Protocol Commands ----------------------------------*/
static int write_state_register(xpd_t *xpd, byte value);
static bool bri_packet_is_valid(xpacket_t *pack);
static void bri_packet_dump(const char *msg, xpacket_t *pack);
#ifdef CONFIG_PROC_FS
static int proc_bri_info_read(char *page, char **start, off_t off, int count, int *eof, void *data);
#endif
static int bri_spanconfig(struct dahdi_span *span, struct dahdi_lineconfig *lc);
static int bri_chanconfig(struct dahdi_chan *chan, int sigtype);
static int bri_startup(struct dahdi_span *span);
static int bri_shutdown(struct dahdi_span *span);
#define PROC_REGISTER_FNAME "slics"
#define PROC_BRI_INFO_FNAME "bri_info"
enum led_state {
BRI_LED_OFF = 0x0,
BRI_LED_ON = 0x1,
/*
* We blink by software from driver, so that
* if the driver malfunction that blink would stop.
*/
// BRI_LED_BLINK_SLOW = 0x2, /* 1/2 a second blink cycle */
// BRI_LED_BLINK_FAST = 0x3 /* 1/4 a second blink cycle */
};
enum bri_led_names {
GREEN_LED = 0,
RED_LED = 1
};
#define NUM_LEDS 2
#define LED_TICKS 100
struct bri_leds {
byte state:2;
byte led_sel:1; /* 0 - GREEN, 1 - RED */
byte reserved:5;
};
#ifndef MAX_DFRAME_LEN_L1
#define MAX_DFRAME_LEN_L1 300
#endif
#define DCHAN_BUFSIZE MAX_DFRAME_LEN_L1
struct BRI_priv_data {
struct proc_dir_entry *bri_info;
su_rd_sta_t state_register;
bool initialized;
int t1; /* timer 1 for NT deactivation */
int t3; /* timer 3 for TE activation */
ulong l1_flags;
bool reg30_good;
uint reg30_ticks;
bool layer1_up;
/*
* D-Chan: buffers + extra state info.
*/
#ifdef CONFIG_DAHDI_BRI_DCHANS
int dchan_r_idx;
byte dchan_rbuf[DCHAN_BUFSIZE];
#else
atomic_t hdlc_pending;
#endif
byte dchan_tbuf[DCHAN_BUFSIZE];
bool txframe_begin;
uint tick_counter;
uint poll_counter;
uint dchan_tx_counter;
uint dchan_rx_counter;
uint dchan_rx_drops;
bool dchan_alive;
uint dchan_alive_ticks;
uint dchan_notx_ticks;
uint dchan_norx_ticks;
enum led_state ledstate[NUM_LEDS];
};
static xproto_table_t PROTO_TABLE(BRI);
DEF_RPACKET_DATA(BRI, SET_LED, /* Set one of the LED's */
struct bri_leds bri_leds;
);
static /* 0x33 */ DECLARE_CMD(BRI, SET_LED, enum bri_led_names which_led, enum led_state to_led_state);
#define DO_LED(xpd, which, tostate) \
CALL_PROTO(BRI, SET_LED, (xpd)->xbus, (xpd), (which), (tostate))
#define DEBUG_BUF_SIZE (100)
static void dump_hex_buf(xpd_t *xpd, char *msg, byte *buf, size_t len)
{
char debug_buf[DEBUG_BUF_SIZE + 1];
int i;
int n = 0;
debug_buf[0] = '\0';
for(i = 0; i < len && n < DEBUG_BUF_SIZE; i++)
n += snprintf(&debug_buf[n], DEBUG_BUF_SIZE - n, "%02X ", buf[i]);
XPD_NOTICE(xpd, "%s[0..%zd]: %s%s\n", msg, len-1, debug_buf,
(n >= DEBUG_BUF_SIZE)?"...":"");
}
static void dump_dchan_packet(xpd_t *xpd, bool transmit, byte *buf, int len)
{
struct BRI_priv_data *priv;
char msgbuf[MAX_PROC_WRITE];
char ftype = '?';
char *direction;
int frame_begin;
priv = xpd->priv;
BUG_ON(!priv);
if(transmit) {
direction = "TX";
frame_begin = priv->txframe_begin;
} else {
direction = "RX";
frame_begin = 1;
}
if(frame_begin) { /* Packet start */
if(!IS_SET(buf[0], 7))
ftype = 'I'; /* Information */
else if(IS_SET(buf[0], 7) && !IS_SET(buf[0], 6))
ftype = 'S'; /* Supervisory */
else if(IS_SET(buf[0], 7) && IS_SET(buf[0], 6))
ftype = 'U'; /* Unnumbered */
else
XPD_NOTICE(xpd, "Unknown frame type 0x%X\n", buf[0]);
snprintf(msgbuf, MAX_PROC_WRITE, "D-Chan %s = (%c) ", direction, ftype);
} else {
snprintf(msgbuf, MAX_PROC_WRITE, "D-Chan %s = ", direction);
}
dump_hex_buf(xpd, msgbuf, buf, len);
}
static void set_bri_timer(xpd_t *xpd, const char *name, int *bri_timer, int value)
{
if(value == HFC_TIMER_OFF)
XPD_DBG(SIGNAL, xpd, "Timer %s DISABLE\n", name);
else
XPD_DBG(SIGNAL, xpd, "Timer %s: set to %d\n", name, value);
*bri_timer = value;
}
static void dchan_state(xpd_t *xpd, bool up)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
if(priv->dchan_alive == up)
return;
if(up) {
XPD_DBG(SIGNAL, xpd, "STATE CHANGE: D-Channel RUNNING\n");
priv->dchan_alive = 1;
} else {
XPD_DBG(SIGNAL, xpd, "STATE CHANGE: D-Channel STOPPED\n");
priv->dchan_rx_counter = priv->dchan_tx_counter = priv->dchan_rx_drops = 0;
priv->dchan_alive = 0;
priv->dchan_alive_ticks = 0;
}
}
static void layer1_state(xpd_t *xpd, bool up)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
if(priv->layer1_up == up)
return;
priv->layer1_up = up;
XPD_DBG(SIGNAL, xpd, "STATE CHANGE: Layer1 %s\n", (up)?"UP":"DOWN");
if(!up)
dchan_state(xpd, 0);
}
static void te_activation(xpd_t *xpd, bool on)
{
struct BRI_priv_data *priv;
xbus_t *xbus;
byte curr_state;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
curr_state = priv->state_register.bits.v_su_sta;
xbus = xpd->xbus;
XPD_DBG(SIGNAL, xpd, "%s\n", (on)?"ON":"OFF");
if(on) {
if(curr_state == ST_TE_DEACTIVATED) {
XPD_DBG(SIGNAL, xpd, "HFC_L1_ACTIVATE_TE\n");
set_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
write_state_register(xpd, STA_ACTIVATE);
set_bri_timer(xpd, "T3", &priv->t3, HFC_TIMER_T3);
} else {
XPD_DBG(SIGNAL, xpd,
"HFC_L1_ACTIVATE_TE (state %d, ignored)\n",
curr_state);
}
} else { /* happen only because of T3 expiry */
switch (curr_state) {
case ST_TE_DEACTIVATED: /* F3 */
case ST_TE_SYNCED: /* F6 */
case ST_TE_ACTIVATED: /* F7 */
XPD_DBG(SIGNAL, xpd,
"HFC_L1_FORCE_DEACTIVATE_TE (state %d, ignored)\n",
curr_state);
break;
case ST_TE_SIGWAIT: /* F4 */
case ST_TE_IDENT: /* F5 */
case ST_TE_LOST_FRAMING: /* F8 */
XPD_DBG(SIGNAL, xpd, "HFC_L1_FORCE_DEACTIVATE_TE\n");
write_state_register(xpd, STA_DEACTIVATE);
break;
default:
XPD_NOTICE(xpd, "Bad TE state: %d\n", curr_state);
break;
}
}
}
static void nt_activation(xpd_t *xpd, bool on)
{
struct BRI_priv_data *priv;
xbus_t *xbus;
byte curr_state;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
curr_state = priv->state_register.bits.v_su_sta;
xbus = xpd->xbus;
XPD_DBG(SIGNAL, xpd, "%s\n", (on)?"ON":"OFF");
if(on) {
switch(curr_state) {
case ST_RESET: /* F/G 0 */
case ST_NT_DEACTIVATED: /* G1 */
case ST_NT_DEACTIVTING: /* G4 */
XPD_DBG(SIGNAL, xpd, "HFC_L1_ACTIVATE_NT\n");
set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_T1);
set_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
write_state_register(xpd, STA_ACTIVATE);
break;
case ST_NT_ACTIVATING: /* G2 */
case ST_NT_ACTIVATED: /* G3 */
XPD_DBG(SIGNAL, xpd,
"HFC_L1_ACTIVATE_NT (in state %d, ignored)\n",
curr_state);
break;
}
} else {
switch(curr_state) {
case ST_RESET: /* F/G 0 */
case ST_NT_DEACTIVATED: /* G1 */
case ST_NT_DEACTIVTING: /* G4 */
XPD_DBG(SIGNAL, xpd,
"HFC_L1_DEACTIVATE_NT (in state %d, ignored)\n",
curr_state);
break;
case ST_NT_ACTIVATING: /* G2 */
case ST_NT_ACTIVATED: /* G3 */
XPD_DBG(SIGNAL, xpd, "HFC_L1_DEACTIVATE_NT\n");
write_state_register(xpd, STA_DEACTIVATE);
break;
default:
XPD_NOTICE(xpd, "Bad NT state: %d\n", curr_state);
break;
}
}
}
/*
* D-Chan receive
*/
static void bri_hdlc_abort(xpd_t *xpd, struct dahdi_chan *dchan, int event)
{
struct BRI_priv_data *priv;
priv = xpd->priv;
BUG_ON(!priv);
#ifdef CONFIG_DAHDI_BRI_DCHANS
if(debug & DBG_COMMANDS)
dump_hex_buf(xpd, "D-Chan(abort) RX: dchan_rbuf",
priv->dchan_rbuf, priv->dchan_r_idx);
priv->dchan_r_idx = 0;
#else
dahdi_hdlc_abort(dchan, event);
#endif
}
static int bri_check_stat(xpd_t *xpd, struct dahdi_chan *dchan, byte *buf, int len)
{
struct BRI_priv_data *priv;
byte status;
priv = xpd->priv;
BUG_ON(!priv);
#ifdef CONFIG_DAHDI_BRI_DCHANS
if(priv->dchan_r_idx < 4) {
XPD_NOTICE(xpd, "D-Chan RX short frame (dchan_r_idx=%d)\n",
priv->dchan_r_idx);
dump_hex_buf(xpd, "D-Chan RX: current packet", buf, len);
bri_hdlc_abort(xpd, dchan, DAHDI_EVENT_ABORT);
return -EPROTO;
}
#else
if(len <= 0) {
XPD_NOTICE(xpd, "D-Chan RX DROP: short frame (len=%d)\n", len);
bri_hdlc_abort(xpd, dchan, DAHDI_EVENT_ABORT);
return -EPROTO;
}
#endif
status = buf[len-1];
if(status) {
int event = DAHDI_EVENT_ABORT;
if(status == 0xFF) {
XPD_NOTICE(xpd, "D-Chan RX DROP: ABORT: %d\n", status);
} else {
XPD_NOTICE(xpd, "D-Chan RX DROP: BADFCS: %d\n", status);
event = DAHDI_EVENT_BADFCS;
}
dump_hex_buf(xpd, "D-Chan RX: current packet", buf, len);
bri_hdlc_abort(xpd, dchan, event);
return -EPROTO;
}
return 0;
}
static int bri_hdlc_putbuf(xpd_t *xpd, struct dahdi_chan *dchan,
unsigned char *buf, int len)
{
#ifdef CONFIG_DAHDI_BRI_DCHANS
struct BRI_priv_data *priv;
byte *dchan_buf;
byte *dst;
int idx;
priv = xpd->priv;
BUG_ON(!priv);
dchan_buf = dchan->readchunk;
idx = priv->dchan_r_idx;
if(idx + len >= DCHAN_BUFSIZE) {
XPD_ERR(xpd, "D-Chan RX overflow: %d\n", idx);
dump_hex_buf(xpd, " current packet", buf, len);
dump_hex_buf(xpd, " dchan_buf", dchan_buf, idx);
return -ENOSPC;
}
dst = dchan_buf + idx;
idx += len;
priv->dchan_r_idx = idx;
memcpy(dst, buf, len);
#else
dahdi_hdlc_putbuf(dchan, buf, len);
#endif
return 0;
}
static void bri_hdlc_finish(xpd_t *xpd, struct dahdi_chan *dchan)
{
struct BRI_priv_data *priv;
priv = xpd->priv;
BUG_ON(!priv);
#ifdef CONFIG_DAHDI_BRI_DCHANS
dchan->bytes2receive = priv->dchan_r_idx - 1;
dchan->eofrx = 1;
#else
dahdi_hdlc_finish(dchan);
#endif
}
#ifdef CONFIG_DAHDI_BRI_DCHANS
static int rx_dchan(xpd_t *xpd, reg_cmd_t *regcmd)
{
xbus_t *xbus;
struct BRI_priv_data *priv;
byte *src;
byte *dst;
byte *dchan_buf;
struct dahdi_chan *dchan;
uint len;
bool eoframe;
int idx;
int ret = 0;
src = REG_XDATA(regcmd);
len = regcmd->bytes;
eoframe = regcmd->eoframe;
if(len <= 0)
return 0;
if(!SPAN_REGISTERED(xpd)) /* Nowhere to copy data */
return 0;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
xbus = xpd->xbus;
#ifdef XPP_DEBUGFS
xbus_log(xbus, xpd, 0, regcmd, sizeof(reg_cmd_t)); /* 0 = RX */
#endif
dchan = XPD_CHAN(xpd, 2);
if(!IS_OFFHOOK(xpd, 2)) { /* D-chan is used? */
static int rate_limit;
if((rate_limit++ % 1000) == 0)
XPD_DBG(SIGNAL, xpd, "D-Chan unused\n");
dchan->bytes2receive = 0;
dchan->bytes2transmit = 0;
goto out;
}
dchan_buf = dchan->readchunk;
idx = priv->dchan_r_idx;
if(idx + len >= DCHAN_BUFSIZE) {
XPD_ERR(xpd, "D-Chan RX overflow: %d\n", idx);
dump_hex_buf(xpd, " current packet", src, len);
dump_hex_buf(xpd, " dchan_buf", dchan_buf, idx);
ret = -ENOSPC;
if(eoframe)
goto drop;
goto out;
}
dst = dchan_buf + idx;
idx += len;
priv->dchan_r_idx = idx;
memcpy(dst, src, len);
if(!eoframe)
goto out;
if(idx < 4) {
XPD_NOTICE(xpd, "D-Chan RX short frame (idx=%d)\n", idx);
dump_hex_buf(xpd, "D-Chan RX: current packet", src, len);
dump_hex_buf(xpd, "D-Chan RX: chan_buf", dchan_buf, idx);
ret = -EPROTO;
goto drop;
}
if((ret = bri_check_stat(xpd, dchan, dchan_buf, idx)) < 0)
goto drop;
if(debug)
dump_dchan_packet(xpd, 0, dchan_buf, idx /* - 3 */); /* Print checksum? */
/*
* Tell Dahdi that we received idx-1 bytes. They include the data and a 2-byte checksum.
* The last byte (that we don't pass on) is 0 if the checksum is correct. If it were wrong,
* we would drop the packet in the "if(dchan_buf[idx-1])" above.
*/
dchan->bytes2receive = idx - 1;
dchan->eofrx = 1;
priv->dchan_rx_counter++;
priv->dchan_norx_ticks = 0;
drop:
priv->dchan_r_idx = 0;
out:
return ret;
}
#else
static int rx_dchan(xpd_t *xpd, reg_cmd_t *regcmd)
{
xbus_t *xbus;
struct BRI_priv_data *priv;
byte *src;
struct dahdi_chan *dchan;
uint len;
bool eoframe;
int ret = 0;
src = REG_XDATA(regcmd);
len = regcmd->bytes;
eoframe = regcmd->eoframe;
if(len <= 0)
return 0;
if(!SPAN_REGISTERED(xpd)) /* Nowhere to copy data */
return 0;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
xbus = xpd->xbus;
#ifdef XPP_DEBUGFS
xbus_log(xbus, xpd, 0, regcmd, sizeof(reg_cmd_t)); /* 0 = RX */
#endif
dchan = XPD_CHAN(xpd, 2);
if(!IS_OFFHOOK(xpd, 2)) { /* D-chan is used? */
static int rate_limit;
if((rate_limit++ % 1000) == 0)
XPD_DBG(SIGNAL, xpd, "D-Chan unused\n");
#ifdef CONFIG_DAHDI_BRI_DCHANS
dchan->bytes2receive = 0;
dchan->bytes2transmit = 0;
#endif
goto out;
}
XPD_DBG(GENERAL, xpd, "D-Chan RX: eoframe=%d len=%d\n", eoframe, len);
ret = bri_hdlc_putbuf(xpd, dchan, src, (eoframe) ? len - 1 : len);
if(ret < 0)
goto out;
if(!eoframe)
goto out;
if((ret = bri_check_stat(xpd, dchan, src, len)) < 0)
goto out;
/*
* Tell Dahdi that we received len-1 bytes. They include the data and a 2-byte checksum.
* The last byte (that we don't pass on) is 0 if the checksum is correct. If it were wrong,
* we would drop the packet in the "if(src[len-1])" above.
*/
bri_hdlc_finish(xpd, dchan);
priv->dchan_rx_counter++;
priv->dchan_norx_ticks = 0;
out:
return ret;
}
#endif
/*
* D-Chan transmit
*/
#ifndef CONFIG_DAHDI_BRI_DCHANS
/* DAHDI calls this when it has data it wants to send to the HDLC controller */
static void bri_hdlc_hard_xmit(struct dahdi_chan *chan)
{
xpd_t *xpd = chan->pvt;
struct dahdi_chan *dchan;
struct BRI_priv_data *priv;
priv = xpd->priv;
BUG_ON(!priv);
dchan = XPD_CHAN(xpd, 2);
if (dchan == chan) {
atomic_inc(&priv->hdlc_pending);
}
}
#endif
static int bri_hdlc_getbuf(struct dahdi_chan *dchan, unsigned char *buf,
unsigned int *size)
{
int len = *size;
int eoframe;
#ifdef CONFIG_DAHDI_BRI_DCHANS
len = dchan->bytes2transmit; /* dchan's hdlc package len */
if(len > *size)
len = *size; /* Silent truncation */
eoframe = dchan->eoftx; /* dchan's end of frame */
dchan->bytes2transmit = 0;
dchan->eoftx = 0;
dchan->bytes2receive = 0;
dchan->eofrx = 0;
#else
eoframe = dahdi_hdlc_getbuf(dchan, buf, &len);
#endif
*size = len;
return eoframe;
}
static int tx_dchan(xpd_t *xpd)
{
struct BRI_priv_data *priv;
struct dahdi_chan *dchan;
int len;
int eoframe;
int ret;
priv = xpd->priv;
BUG_ON(!priv);
#ifndef CONFIG_DAHDI_BRI_DCHANS
if(atomic_read(&priv->hdlc_pending) == 0)
return 0;
#endif
if(!SPAN_REGISTERED(xpd) || !(xpd->span.flags & DAHDI_FLAG_RUNNING))
return 0;
dchan = XPD_CHAN(xpd, 2);
len = ARRAY_SIZE(priv->dchan_tbuf);
if(len > MULTIBYTE_MAX_LEN)
len = MULTIBYTE_MAX_LEN;
eoframe = bri_hdlc_getbuf(dchan, priv->dchan_tbuf, &len);
if(len <= 0)
return 0; /* Nothing to transmit on D channel */
if(len > MULTIBYTE_MAX_LEN) {
XPD_ERR(xpd, "%s: len=%d. need to split. Unimplemented.\n", __FUNCTION__, len);
dump_hex_buf(xpd, "D-Chan TX:", priv->dchan_tbuf, len);
return -EINVAL;
}
if(!test_bit(HFC_L1_ACTIVATED, &priv->l1_flags) && !test_bit(HFC_L1_ACTIVATING, &priv->l1_flags)) {
XPD_DBG(SIGNAL, xpd, "Want to transmit: Kick D-Channel transmiter\n");
if(xpd->direction == TO_PSTN)
te_activation(xpd, 1);
else
nt_activation(xpd, 1);
}
if(debug)
dump_dchan_packet(xpd, 1, priv->dchan_tbuf, len);
if(eoframe)
priv->txframe_begin = 1;
else
priv->txframe_begin = 0;
XPD_DBG(COMMANDS, xpd, "eoframe=%d len=%d\n", eoframe, len);
ret = send_multibyte_request(xpd->xbus, xpd->addr.unit, xpd->addr.subunit,
eoframe, priv->dchan_tbuf, len);
if(ret < 0)
XPD_NOTICE(xpd, "%s: failed sending xframe\n", __FUNCTION__);
if(eoframe) {
#ifndef CONFIG_DAHDI_BRI_DCHANS
atomic_dec(&priv->hdlc_pending);
#endif
priv->dchan_tx_counter++;
}
priv->dchan_notx_ticks = 0;
return ret;
}
/*---------------- BRI: Methods -------------------------------------------*/
static void bri_proc_remove(xbus_t *xbus, xpd_t *xpd)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
XPD_DBG(PROC, xpd, "\n");
#ifdef CONFIG_PROC_FS
if(priv->bri_info) {
XPD_DBG(PROC, xpd, "Removing '%s'\n", PROC_BRI_INFO_FNAME);
remove_proc_entry(PROC_BRI_INFO_FNAME, xpd->proc_xpd_dir);
}
#endif
}
static int bri_proc_create(xbus_t *xbus, xpd_t *xpd)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
XPD_DBG(PROC, xpd, "\n");
#ifdef CONFIG_PROC_FS
XPD_DBG(PROC, xpd, "Creating '%s'\n", PROC_BRI_INFO_FNAME);
priv->bri_info = create_proc_read_entry(PROC_BRI_INFO_FNAME, 0444, xpd->proc_xpd_dir, proc_bri_info_read, xpd);
if(!priv->bri_info) {
XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_BRI_INFO_FNAME);
bri_proc_remove(xbus, xpd);
return -EINVAL;
}
SET_PROC_DIRENTRY_OWNER(priv->bri_info);
#endif
return 0;
}
static xpd_t *BRI_card_new(xbus_t *xbus, int unit, int subunit, const xproto_table_t *proto_table,
byte subtype, int subunits, int subunit_ports, bool to_phone)
{
xpd_t *xpd = NULL;
int channels = min(3, CHANNELS_PERXPD);
if(subunit_ports != 1) {
XBUS_ERR(xbus, "Bad subunit_ports=%d\n", subunit_ports);
return NULL;
}
XBUS_DBG(GENERAL, xbus, "\n");
xpd = xpd_alloc(xbus, unit, subunit, subtype, subunits, sizeof(struct BRI_priv_data), proto_table, channels);
if(!xpd)
return NULL;
xpd->direction = (to_phone) ? TO_PHONE : TO_PSTN;
xpd->type_name = (to_phone) ? "BRI_NT" : "BRI_TE";
if(bri_proc_create(xbus, xpd) < 0)
goto err;
return xpd;
err:
xpd_free(xpd);
return NULL;
}
static int BRI_card_init(xbus_t *xbus, xpd_t *xpd)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
XPD_DBG(GENERAL, xpd, "\n");
priv = xpd->priv;
DO_LED(xpd, GREEN_LED, BRI_LED_OFF);
DO_LED(xpd, RED_LED, BRI_LED_OFF);
set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
write_state_register(xpd, 0); /* Enable L1 state machine */
priv->initialized = 1;
return 0;
}
static int BRI_card_remove(xbus_t *xbus, xpd_t *xpd)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
XPD_DBG(GENERAL, xpd, "\n");
bri_proc_remove(xbus, xpd);
return 0;
}
static const struct dahdi_span_ops BRI_span_ops = {
.owner = THIS_MODULE,
.spanconfig = bri_spanconfig,
.chanconfig = bri_chanconfig,
.startup = bri_startup,
.shutdown = bri_shutdown,
#ifndef CONFIG_DAHDI_BRI_DCHANS
.hdlc_hard_xmit = bri_hdlc_hard_xmit,
#endif
.open = xpp_open,
.close = xpp_close,
.hooksig = xpp_hooksig, /* Only with RBS bits */
.ioctl = xpp_ioctl,
.maint = xpp_maint,
#ifdef DAHDI_SYNC_TICK
.sync_tick = dahdi_sync_tick,
#endif
#ifdef CONFIG_DAHDI_WATCHDOG
.watchdog = xpp_watchdog,
#endif
};
static int BRI_card_dahdi_preregistration(xpd_t *xpd, bool on)
{
xbus_t *xbus;
struct BRI_priv_data *priv;
int i;
BUG_ON(!xpd);
xbus = xpd->xbus;
priv = xpd->priv;
BUG_ON(!xbus);
XPD_DBG(GENERAL, xpd, "%s\n", (on)?"on":"off");
if(!on) {
/* Nothing to do yet */
return 0;
}
xpd->span.spantype = "BRI";
xpd->span.linecompat = DAHDI_CONFIG_AMI | DAHDI_CONFIG_CCS;
xpd->span.deflaw = DAHDI_LAW_ALAW;
BIT_SET(xpd->digital_signalling, 2); /* D-Channel */
for_each_line(xpd, i) {
struct dahdi_chan *cur_chan = XPD_CHAN(xpd, i);
XPD_DBG(GENERAL, xpd, "setting BRI channel %d\n", i);
snprintf(cur_chan->name, MAX_CHANNAME, "XPP_%s/%02d/%1d%1d/%d",
xpd->type_name, xbus->num,
xpd->addr.unit, xpd->addr.subunit, i);
cur_chan->chanpos = i + 1;
cur_chan->pvt = xpd;
if(i == 2) { /* D-CHAN */
cur_chan->sigcap = BRI_DCHAN_SIGCAP;
clear_bit(DAHDI_FLAGBIT_HDLC, &cur_chan->flags);
priv->txframe_begin = 1;
#ifdef CONFIG_DAHDI_BRI_DCHANS
priv->dchan_r_idx = 0;
set_bit(DAHDI_FLAGBIT_BRIDCHAN, &cur_chan->flags);
/* Setup big buffers for D-Channel rx/tx */
cur_chan->readchunk = priv->dchan_rbuf;
cur_chan->writechunk = priv->dchan_tbuf;
cur_chan->maxbytes2transmit = MULTIBYTE_MAX_LEN;
cur_chan->bytes2transmit = 0;
cur_chan->bytes2receive = 0;
#else
atomic_set(&priv->hdlc_pending, 0);
#endif
} else {
cur_chan->sigcap = BRI_BCHAN_SIGCAP;
}
}
CALL_XMETHOD(card_pcm_recompute, xbus, xpd, 0);
xpd->span.ops = &BRI_span_ops;
return 0;
}
static int BRI_card_dahdi_postregistration(xpd_t *xpd, bool on)
{
xbus_t *xbus;
struct BRI_priv_data *priv;
BUG_ON(!xpd);
xbus = xpd->xbus;
priv = xpd->priv;
BUG_ON(!xbus);
XPD_DBG(GENERAL, xpd, "%s\n", (on)?"on":"off");
return(0);
}
static int BRI_card_hooksig(xbus_t *xbus, xpd_t *xpd, int pos, enum dahdi_txsig txsig)
{
LINE_DBG(SIGNAL, xpd, pos, "%s\n", txsig2str(txsig));
return 0;
}
/*
* LED managment is done by the driver now:
* - Turn constant ON RED/GREEN led to indicate NT/TE port
* - Very fast "Double Blink" to indicate Layer1 alive (without D-Channel)
* - Constant blink (1/2 sec cycle) to indicate D-Channel alive.
*/
static void handle_leds(xbus_t *xbus, xpd_t *xpd)
{
struct BRI_priv_data *priv;
unsigned int timer_count;
int which_led;
int other_led;
int mod;
BUG_ON(!xpd);
if(IS_NT(xpd)) {
which_led = RED_LED;
other_led = GREEN_LED;
} else {
which_led = GREEN_LED;
other_led = RED_LED;
}
priv = xpd->priv;
BUG_ON(!priv);
timer_count = xpd->timer_count;
if(xpd->blink_mode) {
if((timer_count % DEFAULT_LED_PERIOD) == 0) {
// led state is toggled
if(priv->ledstate[which_led] == BRI_LED_OFF) {
DO_LED(xpd, which_led, BRI_LED_ON);
DO_LED(xpd, other_led, BRI_LED_ON);
} else {
DO_LED(xpd, which_led, BRI_LED_OFF);
DO_LED(xpd, other_led, BRI_LED_OFF);
}
}
return;
}
if(priv->ledstate[other_led] != BRI_LED_OFF)
DO_LED(xpd, other_led, BRI_LED_OFF);
if(priv->dchan_alive) {
mod = timer_count % 1000;
switch(mod) {
case 0:
DO_LED(xpd, which_led, BRI_LED_ON);
break;
case 500:
DO_LED(xpd, which_led, BRI_LED_OFF);
break;
}
} else if(priv->layer1_up) {
mod = timer_count % 1000;
switch(mod) {
case 0:
case 100:
DO_LED(xpd, which_led, BRI_LED_ON);
break;
case 50:
case 150:
DO_LED(xpd, which_led, BRI_LED_OFF);
break;
}
} else {
if(priv->ledstate[which_led] != BRI_LED_ON)
DO_LED(xpd, which_led, BRI_LED_ON);
}
}
static void handle_bri_timers(xpd_t *xpd)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
if(IS_NT(xpd)) {
if (priv->t1 > HFC_TIMER_OFF) {
if (--priv->t1 == 0) {
set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
if(!nt_keepalive) {
if(priv->state_register.bits.v_su_sta == ST_NT_ACTIVATING) { /* G2 */
XPD_DBG(SIGNAL, xpd, "T1 Expired. Deactivate NT\n");
clear_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
nt_activation(xpd, 0); /* Deactivate NT */
} else
XPD_DBG(SIGNAL, xpd,
"T1 Expired. (state %d, ignored)\n",
priv->state_register.bits.v_su_sta);
}
}
}
} else {
if (priv->t3 > HFC_TIMER_OFF) {
/* timer expired ? */
if (--priv->t3 == 0) {
XPD_DBG(SIGNAL, xpd, "T3 expired. Deactivate TE\n");
set_bri_timer(xpd, "T3", &priv->t3, HFC_TIMER_OFF);
clear_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
te_activation(xpd, 0); /* Deactivate TE */
}
}
}
}
/* Poll the register ST/Up-State-machine Register, to see if the cable
* if a cable is connected to the port.
*/
static int BRI_card_tick(xbus_t *xbus, xpd_t *xpd)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
if(!priv->initialized || !xbus->self_ticking)
return 0;
if(poll_interval != 0 && (priv->tick_counter % poll_interval) == 0) {
// XPD_DBG(GENERAL, xpd, "%d\n", priv->tick_counter);
priv->poll_counter++;
xpp_register_request(xbus, xpd,
BRI_PORT(xpd), /* portno */
0, /* writing */
A_SU_RD_STA, /* regnum */
0, /* do_subreg */
0, /* subreg */
0, /* data_low */
0, /* do_datah */
0, /* data_high */
0 /* should_reply */
);
if(IS_NT(xpd) && nt_keepalive &&
!test_bit(HFC_L1_ACTIVATED, &priv->l1_flags) &&
!test_bit(HFC_L1_ACTIVATING, &priv->l1_flags)) {
XPD_DBG(SIGNAL, xpd, "Kick NT D-Channel\n");
nt_activation(xpd, 1);
}
}
/* Detect D-Channel disconnect heuristic */
priv->dchan_notx_ticks++;
priv->dchan_norx_ticks++;
priv->dchan_alive_ticks++;
if(priv->dchan_alive && (priv->dchan_notx_ticks > DCHAN_LOST || priv->dchan_norx_ticks > DCHAN_LOST)) {
/*
* No tx_dchan() or rx_dchan() for many ticks
* This D-Channel is probabelly dead.
*/
dchan_state(xpd, 0);
} else if(priv->dchan_rx_counter > 1 && priv->dchan_tx_counter > 1) {
if(!priv->dchan_alive)
dchan_state(xpd, 1);
}
/* Detect Layer1 disconnect */
if(priv->reg30_good && priv->reg30_ticks > poll_interval * REG30_LOST) {
/* No reply for 1/2 a second */
XPD_ERR(xpd, "Lost state tracking for %d ticks\n", priv->reg30_ticks);
priv->reg30_good = 0;
layer1_state(xpd, 0);
}
handle_leds(xbus, xpd);
handle_bri_timers(xpd);
tx_dchan(xpd);
priv->tick_counter++;
priv->reg30_ticks++;
return 0;
}
static int BRI_card_ioctl(xpd_t *xpd, int pos, unsigned int cmd, unsigned long arg)
{
BUG_ON(!xpd);
if(!XBUS_IS(xpd->xbus, READY))
return -ENODEV;
switch (cmd) {
case DAHDI_TONEDETECT:
/*
* Asterisk call all span types with this (FXS specific)
* call. Silently ignore it.
*/
LINE_DBG(SIGNAL, xpd, pos, "BRI: Starting a call\n");
return -ENOTTY;
default:
report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
return -ENOTTY;
}
return 0;
}
static int BRI_card_open(xpd_t *xpd, lineno_t pos)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
if(pos == 2) {
LINE_DBG(SIGNAL, xpd, pos, "OFFHOOK the whole span\n");
BIT_SET(xpd->offhook_state, 0);
BIT_SET(xpd->offhook_state, 1);
BIT_SET(xpd->offhook_state, 2);
CALL_XMETHOD(card_pcm_recompute, xpd->xbus, xpd, 0);
}
return 0;
}
static int BRI_card_close(xpd_t *xpd, lineno_t pos)
{
/* Clear D-Channel pending data */
#ifdef CONFIG_DAHDI_BRI_DCHANS
struct dahdi_chan *chan = XPD_CHAN(xpd, pos);
/* Clear D-Channel pending data */
chan->bytes2receive = 0;
chan->eofrx = 0;
chan->bytes2transmit = 0;
chan->eoftx = 0;
#endif
if(pos == 2) {
LINE_DBG(SIGNAL, xpd, pos, "ONHOOK the whole span\n");
BIT_CLR(xpd->offhook_state, 0);
BIT_CLR(xpd->offhook_state, 1);
BIT_CLR(xpd->offhook_state, 2);
CALL_XMETHOD(card_pcm_recompute, xpd->xbus, xpd, 0);
}
return 0;
}
/*
* Called only for 'span' keyword in /etc/dahdi/system.conf
*/
static int bri_spanconfig(struct dahdi_span *span, struct dahdi_lineconfig *lc)
{
xpd_t *xpd = container_of(span, struct xpd, span);
const char *framingstr = "";
const char *codingstr = "";
const char *crcstr = "";
/* framing first */
if (lc->lineconfig & DAHDI_CONFIG_B8ZS)
framingstr = "B8ZS";
else if (lc->lineconfig & DAHDI_CONFIG_AMI)
framingstr = "AMI";
else if (lc->lineconfig & DAHDI_CONFIG_HDB3)
framingstr = "HDB3";
/* then coding */
if (lc->lineconfig & DAHDI_CONFIG_ESF)
codingstr = "ESF";
else if (lc->lineconfig & DAHDI_CONFIG_D4)
codingstr = "D4";
else if (lc->lineconfig & DAHDI_CONFIG_CCS)
codingstr = "CCS";
/* E1's can enable CRC checking */
if (lc->lineconfig & DAHDI_CONFIG_CRC4)
crcstr = "CRC4";
XPD_DBG(GENERAL, xpd, "[%s]: span=%d (%s) lbo=%d lineconfig=%s/%s/%s (0x%X) sync=%d\n",
IS_NT(xpd)?"NT":"TE",
lc->span,
lc->name,
lc->lbo,
framingstr, codingstr, crcstr,
lc->lineconfig,
lc->sync);
/*
* FIXME: validate
*/
span->lineconfig = lc->lineconfig;
return 0;
}
/*
* Set signalling type (if appropriate)
* Called from dahdi with spinlock held on chan. Must not call back
* dahdi functions.
*/
static int bri_chanconfig(struct dahdi_chan *chan, int sigtype)
{
DBG(GENERAL, "channel %d (%s) -> %s\n", chan->channo, chan->name, sig2str(sigtype));
// FIXME: sanity checks:
// - should be supported (within the sigcap)
// - should not replace fxs <->fxo ??? (covered by previous?)
return 0;
}
/*
* Called only for 'span' keyword in /etc/dahdi/system.conf
*/
static int bri_startup(struct dahdi_span *span)
{
xpd_t *xpd = container_of(span, struct xpd, span);
struct BRI_priv_data *priv;
struct dahdi_chan *dchan;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
if(!XBUS_IS(xpd->xbus, READY)) {
XPD_DBG(GENERAL, xpd, "Startup called by dahdi. No Hardware. Ignored\n");
return -ENODEV;
}
XPD_DBG(GENERAL, xpd, "STARTUP\n");
// Turn on all channels
CALL_XMETHOD(XPD_STATE, xpd->xbus, xpd, 1);
if(SPAN_REGISTERED(xpd)) {
dchan = XPD_CHAN(xpd, 2);
span->flags |= DAHDI_FLAG_RUNNING;
/*
* Dahdi (wrongly) assume that D-Channel need HDLC decoding
* and during dahdi registration override our flags.
*
* Don't Get Mad, Get Even: Now we override dahdi :-)
*/
#ifdef CONFIG_DAHDI_BRI_DCHANS
set_bit(DAHDI_FLAGBIT_BRIDCHAN, &dchan->flags);
#endif
clear_bit(DAHDI_FLAGBIT_HDLC, &dchan->flags);
}
return 0;
}
/*
* Called only for 'span' keyword in /etc/dahdi/system.conf
*/
static int bri_shutdown(struct dahdi_span *span)
{
xpd_t *xpd = container_of(span, struct xpd, span);
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
if(!XBUS_IS(xpd->xbus, READY)) {
XPD_DBG(GENERAL, xpd, "Shutdown called by dahdi. No Hardware. Ignored\n");
return -ENODEV;
}
XPD_DBG(GENERAL, xpd, "SHUTDOWN\n");
// Turn off all channels
CALL_XMETHOD(XPD_STATE, xpd->xbus, xpd, 0);
return 0;
}
static void BRI_card_pcm_recompute(xbus_t *xbus, xpd_t *xpd,
xpp_line_t dont_care)
{
int i;
int line_count;
xpp_line_t pcm_mask;
uint pcm_len;
xpd_t *main_xpd;
unsigned long flags;
BUG_ON(!xpd);
main_xpd = xpd_byaddr(xbus, xpd->addr.unit, 0);
if(!main_xpd) {
XPD_DBG(DEVICES, xpd, "Unit 0 is already gone. Ignore request\n");
return;
}
/*
* We calculate all subunits, so use the main lock
* as a mutex for the whole operation.
*/
spin_lock_irqsave(&main_xpd->lock_recompute_pcm, flags);
line_count = 0;
pcm_mask = 0;
for(i = 0; i < MAX_SUBUNIT; i++) {
xpd_t *sub_xpd = xpd_byaddr(xbus, main_xpd->addr.unit, i);
if(sub_xpd) {
xpp_line_t lines =
sub_xpd->offhook_state & ~sub_xpd->digital_signalling;
if(lines) {
pcm_mask |= PCM_SHIFT(lines, i);
line_count += 2;
}
/* subunits have fake pcm_len and wanted_pcm_mask */
if(i > 0) {
update_wanted_pcm_mask(sub_xpd, lines, 0);
}
}
}
/*
* FIXME: Workaround a bug in sync code of the Astribank.
* Send dummy PCM for sync.
*/
if(main_xpd->addr.unit == 0 && line_count == 0) {
pcm_mask = BIT(0);
line_count = 1;
}
/*
* The main unit account for all subunits (pcm_len and wanted_pcm_mask).
*/
pcm_len = (line_count)
? RPACKET_HEADERSIZE + sizeof(xpp_line_t) + line_count * DAHDI_CHUNKSIZE
: 0L;
update_wanted_pcm_mask(main_xpd, pcm_mask, pcm_len);
spin_unlock_irqrestore(&main_xpd->lock_recompute_pcm, flags);
}
static void BRI_card_pcm_fromspan(xbus_t *xbus, xpd_t *xpd, xpacket_t *pack)
{
byte *pcm;
unsigned long flags;
int i;
int subunit;
xpp_line_t pcm_mask = 0;
xpp_line_t wanted_lines;
BUG_ON(!xbus);
BUG_ON(!xpd);
BUG_ON(!pack);
pcm = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, pcm);
for(subunit = 0; subunit < MAX_SUBUNIT; subunit++) {
xpd_t *tmp_xpd;
tmp_xpd = xpd_byaddr(xbus, xpd->addr.unit, subunit);
if(!tmp_xpd || !tmp_xpd->card_present)
continue;
spin_lock_irqsave(&tmp_xpd->lock, flags);
wanted_lines = tmp_xpd->wanted_pcm_mask;
for_each_line(tmp_xpd, i) {
struct dahdi_chan *chan = XPD_CHAN(tmp_xpd, i);
if(IS_SET(wanted_lines, i)) {
if(SPAN_REGISTERED(tmp_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;
}
}
pcm_mask |= PCM_SHIFT(wanted_lines, subunit);
XPD_COUNTER(tmp_xpd, PCM_WRITE)++;
spin_unlock_irqrestore(&tmp_xpd->lock, flags);
}
RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines) = pcm_mask;
}
static void BRI_card_pcm_tospan(xbus_t *xbus, xpd_t *xpd, xpacket_t *pack)
{
byte *pcm;
xpp_line_t pcm_mask;
unsigned long flags;
int subunit;
int i;
/*
* Subunit 0 handle all other subunits
*/
if(xpd->addr.subunit != 0)
return;
if(!SPAN_REGISTERED(xpd))
return;
pcm = RPACKET_FIELD(pack, GLOBAL, PCM_READ, pcm);
pcm_mask = RPACKET_FIELD(pack, GLOBAL, PCM_WRITE, lines);
for(subunit = 0; subunit < MAX_SUBUNIT; subunit++, pcm_mask >>= SUBUNIT_PCM_SHIFT) {
xpd_t *tmp_xpd;
if(!pcm_mask)
break; /* optimize */
tmp_xpd = xpd_byaddr(xbus, xpd->addr.unit, subunit);
if(!tmp_xpd || !tmp_xpd->card_present || !SPAN_REGISTERED(tmp_xpd))
continue;
spin_lock_irqsave(&tmp_xpd->lock, flags);
for (i = 0; i < 2; i++) {
xpp_line_t tmp_mask = pcm_mask & (BIT(0) | BIT(1));
volatile u_char *r;
if(IS_SET(tmp_mask, i)) {
r = XPD_CHAN(tmp_xpd, i)->readchunk;
// memset((u_char *)r, 0x5A, DAHDI_CHUNKSIZE); // DEBUG
memcpy((u_char *)r, pcm, DAHDI_CHUNKSIZE);
pcm += DAHDI_CHUNKSIZE;
}
}
XPD_COUNTER(tmp_xpd, PCM_READ)++;
spin_unlock_irqrestore(&tmp_xpd->lock, flags);
}
}
/*---------------- BRI: HOST COMMANDS -------------------------------------*/
static /* 0x0F */ HOSTCMD(BRI, XPD_STATE, bool on)
{
struct BRI_priv_data *priv;
BUG_ON(!xpd);
priv = xpd->priv;
XPD_DBG(GENERAL, xpd, "%s\n", (on)?"ON":"OFF");
if(on) {
if(!test_bit(HFC_L1_ACTIVATED, &priv->l1_flags)) {
if(xpd->direction == TO_PSTN)
te_activation(xpd, 1);
else
nt_activation(xpd, 1);
}
} else if(IS_NT(xpd))
nt_activation(xpd, 0);
return 0;
}
static /* 0x33 */ HOSTCMD(BRI, SET_LED, enum bri_led_names which_led, enum led_state to_led_state)
{
int ret = 0;
xframe_t *xframe;
xpacket_t *pack;
struct bri_leds *bri_leds;
struct BRI_priv_data *priv;
BUG_ON(!xbus);
priv = xpd->priv;
BUG_ON(!priv);
XPD_DBG(LEDS, xpd, "%s -> %d\n",
(which_led)?"RED":"GREEN",
to_led_state);
XFRAME_NEW_CMD(xframe, pack, xbus, BRI, SET_LED, xpd->xbus_idx);
bri_leds = &RPACKET_FIELD(pack, BRI, SET_LED, bri_leds);
bri_leds->state = to_led_state;
bri_leds->led_sel = which_led;
XPACKET_LEN(pack) = RPACKET_SIZE(BRI, SET_LED);
ret = send_cmd_frame(xbus, xframe);
priv->ledstate[which_led] = to_led_state;
return ret;
}
static int write_state_register(xpd_t *xpd, byte value)
{
int ret;
XPD_DBG(REGS, xpd, "value = 0x%02X\n", value);
ret = xpp_register_request(xpd->xbus, xpd,
BRI_PORT(xpd), /* portno */
1, /* writing */
A_SU_WR_STA, /* regnum */
0, /* do_subreg */
0, /* subreg */
value, /* data_low */
0, /* do_datah */
0, /* data_high */
0 /* should_reply */
);
return ret;
}
/*---------------- BRI: Astribank Reply Handlers --------------------------*/
static void su_new_state(xpd_t *xpd, byte reg_x30)
{
xbus_t *xbus;
struct BRI_priv_data *priv;
su_rd_sta_t new_state;
BUG_ON(!xpd);
priv = xpd->priv;
BUG_ON(!priv);
xbus = xpd->xbus;
if(!priv->initialized) {
XPD_ERR(xpd, "%s called on uninitialized AB\n", __FUNCTION__);
return;
}
new_state.reg = reg_x30;
if(new_state.bits.v_su_t2_exp) {
XPD_NOTICE(xpd, "T2 Expired\n");
}
priv->reg30_ticks = 0;
priv->reg30_good = 1;
if (priv->state_register.bits.v_su_sta == new_state.bits.v_su_sta)
return; /* same same */
XPD_DBG(SIGNAL, xpd, "%02X ---> %02X (info0=%d) (%s%i)\n",
priv->state_register.reg,
reg_x30,
new_state.bits.v_su_info0,
IS_NT(xpd)?"G":"F",
new_state.bits.v_su_sta);
if(!IS_NT(xpd)) {
switch (new_state.bits.v_su_sta) {
case ST_TE_DEACTIVATED: /* F3 */
XPD_DBG(SIGNAL, xpd, "State ST_TE_DEACTIVATED (F3)\n");
clear_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
layer1_state(xpd, 0);
break;
case ST_TE_SIGWAIT: /* F4 */
XPD_DBG(SIGNAL, xpd, "State ST_TE_SIGWAIT (F4)\n");
layer1_state(xpd, 0);
break;
case ST_TE_IDENT: /* F5 */
XPD_DBG(SIGNAL, xpd, "State ST_TE_IDENT (F5)\n");
layer1_state(xpd, 0);
break;
case ST_TE_SYNCED: /* F6 */
XPD_DBG(SIGNAL, xpd, "State ST_TE_SYNCED (F6)\n");
layer1_state(xpd, 0);
break;
case ST_TE_ACTIVATED: /* F7 */
XPD_DBG(SIGNAL, xpd, "State ST_TE_ACTIVATED (F7)\n");
set_bri_timer(xpd, "T3", &priv->t3, HFC_TIMER_OFF);
clear_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
set_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
layer1_state(xpd, 1);
update_xpd_status(xpd, DAHDI_ALARM_NONE);
break;
case ST_TE_LOST_FRAMING: /* F8 */
XPD_DBG(SIGNAL, xpd, "State ST_TE_LOST_FRAMING (F8)\n");
layer1_state(xpd, 0);
break;
default:
XPD_NOTICE(xpd, "Bad TE state: %d\n", new_state.bits.v_su_sta);
break;
}
} else {
switch (new_state.bits.v_su_sta) {
case ST_NT_DEACTIVATED: /* G1 */
XPD_DBG(SIGNAL, xpd, "State ST_NT_DEACTIVATED (G1)\n");
clear_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
layer1_state(xpd, 0);
break;
case ST_NT_ACTIVATING: /* G2 */
XPD_DBG(SIGNAL, xpd, "State ST_NT_ACTIVATING (G2)\n");
layer1_state(xpd, 0);
if(!test_bit(HFC_L1_ACTIVATED, &priv->l1_flags))
nt_activation(xpd, 1);
break;
case ST_NT_ACTIVATED: /* G3 */
XPD_DBG(SIGNAL, xpd, "State ST_NT_ACTIVATED (G3)\n");
clear_bit(HFC_L1_ACTIVATING, &priv->l1_flags);
set_bit(HFC_L1_ACTIVATED, &priv->l1_flags);
set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
layer1_state(xpd, 1);
update_xpd_status(xpd, DAHDI_ALARM_NONE);
break;
case ST_NT_DEACTIVTING: /* G4 */
XPD_DBG(SIGNAL, xpd, "State ST_NT_DEACTIVTING (G4)\n");
set_bri_timer(xpd, "T1", &priv->t1, HFC_TIMER_OFF);
layer1_state(xpd, 0);
break;
default:
XPD_NOTICE(xpd, "Bad NT state: %d\n", new_state.bits.v_su_sta);
break;
}
}
priv->state_register.reg = new_state.reg;
}
static int BRI_card_register_reply(xbus_t *xbus, xpd_t *xpd, reg_cmd_t *info)
{
unsigned long flags;
struct BRI_priv_data *priv;
struct xpd_addr addr;
xpd_t *orig_xpd;
int ret;
/* Map UNIT + PORTNUM to XPD */
orig_xpd = xpd;
addr.unit = orig_xpd->addr.unit;
addr.subunit = info->portnum;
xpd = xpd_byaddr(xbus, addr.unit, addr.subunit);
if(!xpd) {
static int rate_limit;
if((rate_limit++ % 1003) < 5)
notify_bad_xpd(__FUNCTION__, xbus, addr , orig_xpd->xpdname);
return -EPROTO;
}
spin_lock_irqsave(&xpd->lock, flags);
priv = xpd->priv;
BUG_ON(!priv);
if(REG_FIELD(info, do_subreg)) {
XPD_DBG(REGS, xpd, "RI %02X %02X %02X\n",
REG_FIELD(info, regnum), REG_FIELD(info, subreg), REG_FIELD(info, data_low));
} else {
if (REG_FIELD(info, regnum) != A_SU_RD_STA)
XPD_DBG(REGS, xpd, "RD %02X %02X\n",
REG_FIELD(info, regnum), REG_FIELD(info, data_low));
else
XPD_DBG(REGS, xpd, "Got SU_RD_STA=%02X\n",
REG_FIELD(info, data_low));
}
if(info->is_multibyte) {
XPD_DBG(REGS, xpd, "Got Multibyte: %d bytes, eoframe: %d\n",
info->bytes, info->eoframe);
ret = rx_dchan(xpd, info);
if (ret < 0) {
priv->dchan_rx_drops++;
if(atomic_read(&xpd->open_counter) > 0)
XPD_NOTICE(xpd, "Multibyte Drop: errno=%d\n", ret);
}
goto end;
}
if(REG_FIELD(info, regnum) == A_SU_RD_STA) {
su_new_state(xpd, REG_FIELD(info, data_low));
}
/* Update /proc info only if reply relate to the last slic read request */
if(
REG_FIELD(&xpd->requested_reply, regnum) == REG_FIELD(info, regnum) &&
REG_FIELD(&xpd->requested_reply, do_subreg) == REG_FIELD(info, do_subreg) &&
REG_FIELD(&xpd->requested_reply, subreg) == REG_FIELD(info, subreg)) {
xpd->last_reply = *info;
}
end:
spin_unlock_irqrestore(&xpd->lock, flags);
return 0;
}
static xproto_table_t PROTO_TABLE(BRI) = {
.owner = THIS_MODULE,
.entries = {
/* Table Card Opcode */
},
.name = "BRI", /* protocol name */
.ports_per_subunit = 1,
.type = XPD_TYPE_BRI,
.xops = {
.card_new = BRI_card_new,
.card_init = BRI_card_init,
.card_remove = BRI_card_remove,
.card_dahdi_preregistration = BRI_card_dahdi_preregistration,
.card_dahdi_postregistration = BRI_card_dahdi_postregistration,
.card_hooksig = BRI_card_hooksig,
.card_tick = BRI_card_tick,
.card_pcm_recompute = BRI_card_pcm_recompute,
.card_pcm_fromspan = BRI_card_pcm_fromspan,
.card_pcm_tospan = BRI_card_pcm_tospan,
.card_timing_priority = generic_timing_priority,
.card_ioctl = BRI_card_ioctl,
.card_open = BRI_card_open,
.card_close = BRI_card_close,
.card_register_reply = BRI_card_register_reply,
.XPD_STATE = XPROTO_CALLER(BRI, XPD_STATE),
},
.packet_is_valid = bri_packet_is_valid,
.packet_dump = bri_packet_dump,
};
static bool bri_packet_is_valid(xpacket_t *pack)
{
const xproto_entry_t *xe = NULL;
// DBG(GENERAL, "\n");
xe = xproto_card_entry(&PROTO_TABLE(BRI), XPACKET_OP(pack));
return xe != NULL;
}
static void bri_packet_dump(const char *msg, xpacket_t *pack)
{
DBG(GENERAL, "%s\n", msg);
}
/*------------------------- REGISTER Handling --------------------------*/
#ifdef CONFIG_PROC_FS
static int proc_bri_info_read(char *page, char **start, off_t off, int count, int *eof, void *data)
{
int len = 0;
unsigned long flags;
xpd_t *xpd = data;
struct BRI_priv_data *priv;
DBG(PROC, "\n");
if(!xpd)
return -ENODEV;
spin_lock_irqsave(&xpd->lock, flags);
priv = xpd->priv;
BUG_ON(!priv);
len += sprintf(page + len, "%05d Layer 1: ", priv->poll_counter);
if(priv->reg30_good) {
len += sprintf(page + len, "%-5s ", (priv->layer1_up) ? "UP" : "DOWN");
len += sprintf(page + len, "%c%d %-15s -- fr_sync=%d t2_exp=%d info0=%d g2_g3=%d\n",
IS_NT(xpd)?'G':'F',
priv->state_register.bits.v_su_sta,
xhfc_state_name(IS_NT(xpd), priv->state_register.bits.v_su_sta),
priv->state_register.bits.v_su_fr_sync,
priv->state_register.bits.v_su_t2_exp,
priv->state_register.bits.v_su_info0,
priv->state_register.bits.v_g2_g3);
} else
len += sprintf(page + len, "Unknown\n");
if(IS_NT(xpd)) {
len += sprintf(page + len, "T1 Timer: %d\n", priv->t1);
} else {
len += sprintf(page + len, "T3 Timer: %d\n", priv->t3);
}
len += sprintf(page + len, "Tick Counter: %d\n", priv->tick_counter);
len += sprintf(page + len, "Last Poll Reply: %d ticks ago\n", priv->reg30_ticks);
len += sprintf(page + len, "reg30_good=%d\n", priv->reg30_good);
len += sprintf(page + len, "D-Channel: TX=[%5d] RX=[%5d] BAD=[%5d] ",
priv->dchan_tx_counter, priv->dchan_rx_counter, priv->dchan_rx_drops);
if(priv->dchan_alive) {
len += sprintf(page + len, "(alive %d K-ticks)\n",
priv->dchan_alive_ticks/1000);
} else {
len += sprintf(page + len, "(dead)\n");
}
#ifndef CONFIG_DAHDI_BRI_DCHANS
len += sprintf(page + len, "hdlc_pending=%d\n", atomic_read(&priv->hdlc_pending));
#endif
len += sprintf(page + len, "dchan_notx_ticks: %d\n", priv->dchan_notx_ticks);
len += sprintf(page + len, "dchan_norx_ticks: %d\n", priv->dchan_norx_ticks);
len += sprintf(page + len, "LED: %-10s = %d\n", "GREEN", priv->ledstate[GREEN_LED]);
len += sprintf(page + len, "LED: %-10s = %d\n", "RED", priv->ledstate[RED_LED]);
len += sprintf(page + len, "\nDCHAN:\n");
len += sprintf(page + len, "\n");
spin_unlock_irqrestore(&xpd->lock, flags);
if (len <= off+count)
*eof = 1;
*start = page + off;
len -= off;
if (len > count)
len = count;
if (len < 0)
len = 0;
return len;
}
#endif
static DRIVER_ATTR_READER(dchan_hardhdlc_show, drv,buf)
{
int len = 0;
#if defined(CONFIG_DAHDI_BRI_DCHANS)
len += sprintf(buf + len, "0\n");
#elif defined(DAHDI_SIG_HARDHDLC)
len += sprintf(buf + len, "1\n");
#else
#error Cannot build BRI without BRISTUFF or HARDHDLC supprt
#endif
return len;
}
static DRIVER_ATTR(dchan_hardhdlc,S_IRUGO,dchan_hardhdlc_show,NULL);
static int bri_xpd_probe(struct device *dev)
{
xpd_t *xpd;
xpd = dev_to_xpd(dev);
/* Is it our device? */
if(xpd->type != XPD_TYPE_BRI) {
XPD_ERR(xpd, "drop suggestion for %s (%d)\n",
dev_name(dev), xpd->type);
return -EINVAL;
}
XPD_DBG(DEVICES, xpd, "SYSFS\n");
return 0;
}
static int bri_xpd_remove(struct device *dev)
{
xpd_t *xpd;
xpd = dev_to_xpd(dev);
XPD_DBG(DEVICES, xpd, "SYSFS\n");
return 0;
}
static struct xpd_driver bri_driver = {
.type = XPD_TYPE_BRI,
.driver = {
.name = "bri",
#ifndef OLD_HOTPLUG_SUPPORT
.owner = THIS_MODULE,
#endif
.probe = bri_xpd_probe,
.remove = bri_xpd_remove
}
};
static int __init card_bri_startup(void)
{
int ret;
if((ret = xpd_driver_register(&bri_driver.driver)) < 0)
return ret;
ret = driver_create_file(&bri_driver.driver, &driver_attr_dchan_hardhdlc);
if(ret < 0)
return ret;
INFO("revision %s\n", XPP_VERSION);
#if defined(CONFIG_DAHDI_BRI_DCHANS)
INFO("FEATURE: WITH BRISTUFF\n");
#elif defined(DAHDI_SIG_HARDHDLC)
INFO("FEATURE: WITH HARDHDLC\n");
#else
#error Cannot build BRI without BRISTUFF or HARDHDLC supprt
#endif
xproto_register(&PROTO_TABLE(BRI));
return 0;
}
static void __exit card_bri_cleanup(void)
{
DBG(GENERAL, "\n");
xproto_unregister(&PROTO_TABLE(BRI));
driver_remove_file(&bri_driver.driver, &driver_attr_dchan_hardhdlc);
xpd_driver_unregister(&bri_driver.driver);
}
MODULE_DESCRIPTION("XPP BRI Card Driver");
MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
MODULE_LICENSE("GPL");
MODULE_VERSION(XPP_VERSION);
MODULE_ALIAS_XPD(XPD_TYPE_BRI);
module_init(card_bri_startup);
module_exit(card_bri_cleanup);