dahdi-linux/drivers/dahdi/xpp/card_bri.c

1870 lines
48 KiB
C
Raw Normal View History

/*
* 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);