- move branch mqueue to HEAD
This commit is contained in:
parent
56f18961c0
commit
b267c6033f
|
@ -0,0 +1,77 @@
|
||||||
|
BASEDIR=$(shell pwd)
|
||||||
|
|
||||||
|
|
||||||
|
INSTALL_PREFIX := /
|
||||||
|
export INSTALL_PREFIX
|
||||||
|
|
||||||
|
#PATH to linux source/headers
|
||||||
|
#LINUX=/usr/src/linux
|
||||||
|
MODS=/lib/modules/$(shell uname -r)
|
||||||
|
LINUX=$(MODS)/build
|
||||||
|
LINUX_SOURCE=$(MODS)/source
|
||||||
|
|
||||||
|
|
||||||
|
MISDNDIR=$(BASEDIR)
|
||||||
|
MISDN_SRC=$(MISDNDIR)/drivers/isdn/hardware/mISDN
|
||||||
|
|
||||||
|
########################################
|
||||||
|
# USER CONFIGS END
|
||||||
|
########################################
|
||||||
|
|
||||||
|
CONFIGS+=CONFIG_MISDN_DRV=m CONFIG_MISDN_DSP=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_HFCMULTI=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_HFCPCI=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_HFCUSB=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_XHFC=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_HFCMINI=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_W6692=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_SPEEDFAX=m
|
||||||
|
CONFIGS+=CONFIG_MISDN_AVM_FRITZ=m
|
||||||
|
|
||||||
|
|
||||||
|
MINCLUDES+=-I$(MISDNDIR)/include
|
||||||
|
|
||||||
|
all: test_old_misdn
|
||||||
|
@echo
|
||||||
|
@echo "Makeing mISDN"
|
||||||
|
@echo "============="
|
||||||
|
@echo
|
||||||
|
cp $(MISDNDIR)/drivers/isdn/hardware/mISDN/Makefile.v2.6 $(MISDNDIR)/drivers/isdn/hardware/mISDN/Makefile
|
||||||
|
|
||||||
|
export MINCLUDES=$(MISDNDIR)/include ; make -C $(LINUX) SUBDIRS=$(MISDN_SRC) modules $(CONFIGS)
|
||||||
|
|
||||||
|
|
||||||
|
install: all
|
||||||
|
cd $(LINUX) ; make SUBDIRS=$(MISDN_SRC) modules_install
|
||||||
|
cp $(MISDNDIR)/include/linux/*.h $(INSTALL_PREFIX)/usr/include/linux/
|
||||||
|
install -m755 misdn-init /etc/init.d/
|
||||||
|
depmod
|
||||||
|
|
||||||
|
|
||||||
|
test_old_misdn:
|
||||||
|
@if echo -ne "#include <linux/mISDNif.h>" | gcc -C -E - 2>/dev/null 1>/dev/null ; then \
|
||||||
|
if ! echo -ne "#include <linux/mISDNif.h>\n#ifndef FLG_MSG_DOWN\n#error old mISDNif.h\n#endif\n" | gcc -C -E - 2>/dev/null 1>/dev/null ; then \
|
||||||
|
echo -ne "\n!!You should remove the following files:\n\n$(LINUX)/include/linux/mISDNif.h\n$(LINUX)/include/linux/isdn_compat.h\n/usr/include/linux/mISDNif.h\n/usr/include/linux/isdn_compat.h\n\nIn order to upgrade to the mqueue branch\n\n"; \
|
||||||
|
echo -ne "I can do that for you, just type: make force\n\n" ; \
|
||||||
|
exit 1; \
|
||||||
|
fi ;\
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
.PHONY: install all clean
|
||||||
|
|
||||||
|
force:
|
||||||
|
rm -f $(LINUX)/include/linux/mISDNif.h
|
||||||
|
rm -f $(LINUX)/include/linux/isdn_compat.h
|
||||||
|
rm -f /usr/include/linux/mISDNif.h
|
||||||
|
rm -f /usr/include/linux/isdn_compat.h
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf drivers/isdn/hardware/mISDN/*.o
|
||||||
|
rm -rf drivers/isdn/hardware/mISDN/*.ko
|
||||||
|
rm -rf *~
|
||||||
|
find . -iname ".*.cmd" -exec rm -rf {} \;
|
||||||
|
find . -iname ".*.d" -exec rm -rf {} \;
|
||||||
|
find . -iname "*.mod.c" -exec rm -rf {} \;
|
||||||
|
find . -iname "*.mod" -exec rm -rf {} \;
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
misdn-init: init-script to auto-configure and load the mISDN kernel drivers
|
||||||
|
===========================================================================
|
||||||
|
|
||||||
|
This script makes it easy to configure and activate mISDN compatible
|
||||||
|
adapter cards. It scans an eyecandy config file named misdn-init.conf
|
||||||
|
for your card and port settings, then it loads the driver modules properly.
|
||||||
|
The misdn-init.conf can also be autogenerated by the misdn-init script.
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
Usage: /etc/init.d/misdn-init start|stop|restart|config|scan|help
|
||||||
|
|
||||||
|
--start scan /etc/misdn-init.conf and load the mISDN drivers
|
||||||
|
--stop unload the mISDN drivers
|
||||||
|
--restart see stop, then start
|
||||||
|
--config scan your PCI bus for mISDN compatible hardware and generate
|
||||||
|
a /etc/misdn-init.conf
|
||||||
|
--scan scan your PCI bus for mISDN compatible hardware and print
|
||||||
|
the results to the console
|
||||||
|
--help print the usage info
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
* Here is a quick overview on how to use misdn-init:
|
||||||
|
|
||||||
|
1) Get and install misdn-init:
|
||||||
|
$ wget http://www.beronet.com/downloads/chan_misdn/stable/chan_misdn.tar.gz
|
||||||
|
$ tar zxf chan_misdn.tar.gz
|
||||||
|
$ (as root) cp chan_misdn/misdn-init /etc/init.d/misdn-init
|
||||||
|
|
||||||
|
2) Let misdn-init scan your PCI bus for mISDN compatible hardware and write
|
||||||
|
the results into /etc/misdn-init.conf:
|
||||||
|
$ (as root) /etc/init.d/misdn-init config
|
||||||
|
|
||||||
|
3) (optional) Edit /etc/misdn-init.conf and set everything the way you want it.
|
||||||
|
This file is heavily commented, hence it should be self-explaining.
|
||||||
|
|
||||||
|
4) (optional, but recommended) Add misdn-init to your run level.
|
||||||
|
This is distribution dependend. Here an example for a debian system:
|
||||||
|
ATTENTION: If you have services in your runlevels that depend
|
||||||
|
on mISDN, make sure that misdn-init starts before, and
|
||||||
|
stops after them (this is done by changing the values
|
||||||
|
that are set to 60 in this example, more info: read the
|
||||||
|
manpage for update-rc.d).
|
||||||
|
$ (as root) update-rc.d misdn-init start 60 2 3 4 5 . stop 60 0 1 6 .
|
||||||
|
|
||||||
|
5) Run the following to start mISDN:
|
||||||
|
$ (as root) /etc/init.d/misdn-init start
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
---------------------------------------------------------------------------
|
||||||
|
* Report Bugs:
|
||||||
|
If you experience any bugs or have a feature request, please visit:
|
||||||
|
www.isdn4linux.de/mantis
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* Author (c) Karsten Keil <kkeil@suse.de>
|
||||||
|
*
|
||||||
|
* This file is released under the GPLv2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include "channel.h"
|
||||||
|
#include "layer1.h"
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_initchannel(channel_t *ch, ulong prop, int maxlen)
|
||||||
|
{
|
||||||
|
ch->log = kmalloc(MAX_LOG_SPACE, GFP_ATOMIC);
|
||||||
|
if (!ch->log) {
|
||||||
|
printk(KERN_WARNING
|
||||||
|
"mISDN: No memory for channel log\n");
|
||||||
|
return(-ENOMEM);
|
||||||
|
}
|
||||||
|
ch->Flags = prop;
|
||||||
|
ch->maxlen = maxlen;
|
||||||
|
ch->hw = NULL;
|
||||||
|
ch->rx_skb = NULL;
|
||||||
|
ch->tx_skb = NULL;
|
||||||
|
ch->tx_idx = 0;
|
||||||
|
ch->next_skb = NULL;
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_freechannel(channel_t *ch)
|
||||||
|
{
|
||||||
|
if (ch->tx_skb) {
|
||||||
|
dev_kfree_skb(ch->tx_skb);
|
||||||
|
ch->tx_skb = NULL;
|
||||||
|
}
|
||||||
|
if (ch->rx_skb) {
|
||||||
|
dev_kfree_skb(ch->rx_skb);
|
||||||
|
ch->rx_skb = NULL;
|
||||||
|
}
|
||||||
|
if (ch->next_skb) {
|
||||||
|
dev_kfree_skb(ch->next_skb);
|
||||||
|
ch->next_skb = NULL;
|
||||||
|
}
|
||||||
|
kfree(ch->log);
|
||||||
|
ch->log = NULL;
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* need called with HW lock */
|
||||||
|
int
|
||||||
|
mISDN_setpara(channel_t *ch, mISDN_stPara_t *stp)
|
||||||
|
{
|
||||||
|
if (!stp) { // clear parameters
|
||||||
|
ch->maxlen = 0;
|
||||||
|
ch->up_headerlen = 0;
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
if (stp->up_headerlen)
|
||||||
|
ch->up_headerlen = stp->up_headerlen;
|
||||||
|
if (stp->maxdatalen) {
|
||||||
|
if (ch->maxlen < stp->maxdatalen) {
|
||||||
|
if (ch->rx_skb) {
|
||||||
|
struct sk_buff *skb;
|
||||||
|
|
||||||
|
skb = alloc_skb(stp->maxdatalen +
|
||||||
|
ch->up_headerlen, GFP_ATOMIC);
|
||||||
|
if (!skb) {
|
||||||
|
int_errtxt("no skb for %d+%d", stp->maxdatalen, ch->up_headerlen);
|
||||||
|
return(-ENOMEM);
|
||||||
|
}
|
||||||
|
skb_reserve(skb, ch->up_headerlen);
|
||||||
|
memcpy(skb_put(skb, ch->rx_skb->len),
|
||||||
|
ch->rx_skb->data, ch->rx_skb->len);
|
||||||
|
dev_kfree_skb(ch->rx_skb);
|
||||||
|
ch->rx_skb = skb;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ch->maxlen = stp->maxdatalen;
|
||||||
|
}
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
EXPORT_SYMBOL(mISDN_initchannel);
|
||||||
|
EXPORT_SYMBOL(mISDN_freechannel);
|
||||||
|
EXPORT_SYMBOL(mISDN_setpara);
|
|
@ -0,0 +1,148 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* Basic declarations for a mISDN HW channel
|
||||||
|
*
|
||||||
|
* Author (c) Karsten Keil <kkeil@suse.de>
|
||||||
|
*
|
||||||
|
* This file is released under the GPLv2
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef MISDN_CHANNEL_H
|
||||||
|
#define MISDN_CHANNEL_H
|
||||||
|
#include <linux/mISDNif.h>
|
||||||
|
#include <linux/timer.h>
|
||||||
|
#include <linux/skbuff.h>
|
||||||
|
#include "helper.h"
|
||||||
|
#ifdef MISDN_MEMDEBUG
|
||||||
|
#include "memdbg.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define MAX_DFRAME_LEN_L1 300
|
||||||
|
#define MAX_MON_FRAME 32
|
||||||
|
#define MAX_LOG_SPACE 2048
|
||||||
|
#define MISDN_COPY_SIZE 32
|
||||||
|
|
||||||
|
/* channel->Flags bit field */
|
||||||
|
#define FLG_TX_BUSY 0 // tx_buf in use
|
||||||
|
#define FLG_TX_NEXT 1 // next_skb in use
|
||||||
|
#define FLG_L1_BUSY 2 // L1 is permanent busy
|
||||||
|
#define FLG_USED 5 // channel is in use
|
||||||
|
#define FLG_ACTIVE 6 // channel is activated
|
||||||
|
#define FLG_BUSY_TIMER 7
|
||||||
|
/* channel type */
|
||||||
|
#define FLG_DCHANNEL 8 // channel is D-channel
|
||||||
|
#define FLG_BCHANNEL 9 // channel is B-channel
|
||||||
|
#define FLG_ECHANNEL 10 // channel is E-channel
|
||||||
|
#define FLG_TRANSPARENT 12 // channel use transparent data
|
||||||
|
#define FLG_HDLC 13 // channel use hdlc data
|
||||||
|
#define FLG_L2DATA 14 // channel use L2 DATA primitivs
|
||||||
|
#define FLG_ORIGIN 15 // channel is on origin site
|
||||||
|
/* channel specific stuff */
|
||||||
|
/* arcofi specific */
|
||||||
|
#define FLG_ARCOFI_TIMER 16
|
||||||
|
#define FLG_ARCOFI_ERROR 17
|
||||||
|
/* isar specific */
|
||||||
|
#define FLG_INITIALIZED 16
|
||||||
|
#define FLG_DLEETX 17
|
||||||
|
#define FLG_LASTDLE 18
|
||||||
|
#define FLG_FIRST 19
|
||||||
|
#define FLG_LASTDATA 20
|
||||||
|
#define FLG_NMD_DATA 21
|
||||||
|
#define FLG_FTI_RUN 22
|
||||||
|
#define FLG_LL_OK 23
|
||||||
|
#define FLG_LL_CONN 24
|
||||||
|
#define FLG_DTMFSEND 25
|
||||||
|
|
||||||
|
|
||||||
|
#define MSK_INIT_DCHANNEL ((1<<FLG_DCHANNEL)|(1<<FLG_HDLC))
|
||||||
|
#define MSK_INIT_BCHANNEL (1<<FLG_BCHANNEL)
|
||||||
|
#define MSK_INIT_ECHANNEL (1<<FLG_ECHANNEL)
|
||||||
|
|
||||||
|
|
||||||
|
typedef struct _channel_t {
|
||||||
|
mISDNinstance_t inst;
|
||||||
|
int channel;
|
||||||
|
/* basic properties */
|
||||||
|
u_long Flags;
|
||||||
|
u_int type;
|
||||||
|
u_int state;
|
||||||
|
/* HW access */
|
||||||
|
u_char (*read_reg) (void *, u_char);
|
||||||
|
void (*write_reg) (void *, u_char, u_char);
|
||||||
|
void (*read_fifo) (void *, u_char *, int);
|
||||||
|
void (*write_fifo) (void *, u_char *, int);
|
||||||
|
void *hw;
|
||||||
|
struct timer_list timer;
|
||||||
|
/* receive data */
|
||||||
|
struct sk_buff *rx_skb;
|
||||||
|
int maxlen;
|
||||||
|
int up_headerlen;
|
||||||
|
/* send data */
|
||||||
|
struct sk_buff *next_skb;
|
||||||
|
struct sk_buff *tx_skb;
|
||||||
|
int tx_idx;
|
||||||
|
/* debug */
|
||||||
|
int debug;
|
||||||
|
char *log;
|
||||||
|
/* statistics */
|
||||||
|
int err_crc;
|
||||||
|
int err_tx;
|
||||||
|
int err_rx;
|
||||||
|
} channel_t;
|
||||||
|
|
||||||
|
extern int mISDN_initchannel(channel_t *, ulong, int);
|
||||||
|
extern int mISDN_freechannel(channel_t *);
|
||||||
|
extern int mISDN_setpara(channel_t *, mISDN_stPara_t *);
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
queue_ch_frame(channel_t *ch, u_int pr, int dinfo, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
pr |= test_bit(FLG_L2DATA, &ch->Flags) ? DL_DATA : PH_DATA;
|
||||||
|
if (!skb)
|
||||||
|
err = mISDN_queue_data(&ch->inst, FLG_MSG_UP, pr, dinfo, 0, NULL, ch->up_headerlen);
|
||||||
|
else
|
||||||
|
err = mISDN_queueup_newhead(&ch->inst, 0, pr, dinfo, skb);
|
||||||
|
if (unlikely(err)) {
|
||||||
|
int_errtxt("err=%d", err);
|
||||||
|
if (skb)
|
||||||
|
dev_kfree_skb(skb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int
|
||||||
|
channel_senddata(channel_t *ch, int di, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
/* HW lock must be obtained */
|
||||||
|
/* check oversize */
|
||||||
|
if (skb->len <= 0) {
|
||||||
|
printk(KERN_WARNING "%s: skb too small\n", __FUNCTION__);
|
||||||
|
return(-EINVAL);
|
||||||
|
}
|
||||||
|
if (skb->len > ch->maxlen) {
|
||||||
|
printk(KERN_WARNING "%s: skb too large(%d/%d)\n",
|
||||||
|
__FUNCTION__, skb->len, ch->maxlen);
|
||||||
|
return(-EINVAL);
|
||||||
|
}
|
||||||
|
/* check for pending next_skb */
|
||||||
|
if (ch->next_skb) {
|
||||||
|
printk(KERN_WARNING "%s: next_skb exist ERROR (skb->len=%d next_skb->len=%d)\n",
|
||||||
|
__FUNCTION__, skb->len, ch->next_skb->len);
|
||||||
|
return(-EBUSY);
|
||||||
|
}
|
||||||
|
if (test_and_set_bit(FLG_TX_BUSY, &ch->Flags)) {
|
||||||
|
test_and_set_bit(FLG_TX_NEXT, &ch->Flags);
|
||||||
|
ch->next_skb = skb;
|
||||||
|
return(0);
|
||||||
|
} else {
|
||||||
|
/* write to fifo */
|
||||||
|
ch->tx_skb = skb;
|
||||||
|
ch->tx_idx = 0;
|
||||||
|
queue_ch_frame(ch, CONFIRM, di, NULL);
|
||||||
|
return(skb->len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,347 @@
|
||||||
|
#ifndef _ZAPTEL_ARITH_H
|
||||||
|
#define _ZAPTEL_ARITH_H
|
||||||
|
/*
|
||||||
|
* Handy add/subtract functions to operate on chunks of shorts.
|
||||||
|
* Feel free to add customizations for additional architectures
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifdef CONFIG_ZAPTEL_MMX
|
||||||
|
#ifdef ZT_CHUNKSIZE
|
||||||
|
static inline void __ACSS(volatile short *dst, const short *src)
|
||||||
|
{
|
||||||
|
__asm__ __volatile__ (
|
||||||
|
"movq 0(%0), %%mm0;\n"
|
||||||
|
"movq 0(%1), %%mm1;\n"
|
||||||
|
"movq 8(%0), %%mm2;\n"
|
||||||
|
"movq 8(%1), %%mm3;\n"
|
||||||
|
"paddsw %%mm1, %%mm0;\n"
|
||||||
|
"paddsw %%mm3, %%mm2;\n"
|
||||||
|
"movq %%mm0, 0(%0);\n"
|
||||||
|
"movq %%mm2, 8(%0);\n"
|
||||||
|
: "=r" (dst)
|
||||||
|
: "r" (src), "0" (dst)
|
||||||
|
: "memory"
|
||||||
|
#if CLOBBERMMX
|
||||||
|
, "%mm0", "%mm1", "%mm2", "%mm3"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
static inline void __SCSS(volatile short *dst, const short *src)
|
||||||
|
{
|
||||||
|
__asm__ __volatile__ (
|
||||||
|
"movq 0(%0), %%mm0;\n"
|
||||||
|
"movq 0(%1), %%mm1;\n"
|
||||||
|
"movq 8(%0), %%mm2;\n"
|
||||||
|
"movq 8(%1), %%mm3;\n"
|
||||||
|
"psubsw %%mm1, %%mm0;\n"
|
||||||
|
"psubsw %%mm3, %%mm2;\n"
|
||||||
|
"movq %%mm0, 0(%0);\n"
|
||||||
|
"movq %%mm2, 8(%0);\n"
|
||||||
|
: "=r" (dst)
|
||||||
|
: "r" (src), "0" (dst)
|
||||||
|
: "memory"
|
||||||
|
#if CLOBBERMMX
|
||||||
|
, "%mm0", "%mm1", "%mm2", "%mm3"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#if (ZT_CHUNKSIZE == 8)
|
||||||
|
#define ACSS(a,b) __ACSS(a,b)
|
||||||
|
#define SCSS(a,b) __SCSS(a,b)
|
||||||
|
#elif (ZT_CHUNKSIZE > 8)
|
||||||
|
static inline void ACSS(volatile short *dst, const short *src)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x=0;x<ZT_CHUNKSIZE;x+=8)
|
||||||
|
__ACSS(dst + x, src + x);
|
||||||
|
}
|
||||||
|
static inline void SCSS(volatile short *dst, const short *src)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
for (x=0;x<ZT_CHUNKSIZE;x+=8)
|
||||||
|
__SCSS(dst + x, src + x);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
#error No MMX for ZT_CHUNKSIZE < 8
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
static inline int CONVOLVE(const int *coeffs, const short *hist, int len)
|
||||||
|
{
|
||||||
|
int sum;
|
||||||
|
/* Divide length by 16 */
|
||||||
|
len >>= 4;
|
||||||
|
|
||||||
|
/* Clear our accumulator, mm4 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
For every set of eight...
|
||||||
|
|
||||||
|
Load 16 coefficients into four registers...
|
||||||
|
Shift each word right 16 to make them shorts...
|
||||||
|
Pack the resulting shorts into two registers...
|
||||||
|
With the coefficients now in mm0 and mm2, load the
|
||||||
|
history into mm1 and mm3...
|
||||||
|
Multiply/add mm1 into mm0, and mm3 into mm2...
|
||||||
|
Add mm2 into mm0 (without saturation, alas). Now we have two half-results.
|
||||||
|
Accumulate in mm4 (again, without saturation, alas)
|
||||||
|
*/
|
||||||
|
__asm__ (
|
||||||
|
"pxor %%mm4, %%mm4;\n"
|
||||||
|
"mov %1, %%edi;\n"
|
||||||
|
"mov %2, %%esi;\n"
|
||||||
|
"mov %3, %%ecx;\n"
|
||||||
|
"1:"
|
||||||
|
"movq 0(%%edi), %%mm0;\n"
|
||||||
|
"movq 8(%%edi), %%mm1;\n"
|
||||||
|
"movq 16(%%edi), %%mm2;\n"
|
||||||
|
"movq 24(%%edi), %%mm3;\n"
|
||||||
|
/* can't use 4/5 since 4 is the accumulator for us */
|
||||||
|
"movq 32(%%edi), %%mm6;\n"
|
||||||
|
"movq 40(%%edi), %%mm7;\n"
|
||||||
|
"psrad $16, %%mm0;\n"
|
||||||
|
"psrad $16, %%mm1;\n"
|
||||||
|
"psrad $16, %%mm2;\n"
|
||||||
|
"psrad $16, %%mm3;\n"
|
||||||
|
"psrad $16, %%mm6;\n"
|
||||||
|
"psrad $16, %%mm7;\n"
|
||||||
|
"packssdw %%mm1, %%mm0;\n"
|
||||||
|
"packssdw %%mm3, %%mm2;\n"
|
||||||
|
"packssdw %%mm7, %%mm6;\n"
|
||||||
|
"movq 0(%%esi), %%mm1;\n"
|
||||||
|
"movq 8(%%esi), %%mm3;\n"
|
||||||
|
"movq 16(%%esi), %%mm7;\n"
|
||||||
|
"pmaddwd %%mm1, %%mm0;\n"
|
||||||
|
"pmaddwd %%mm3, %%mm2;\n"
|
||||||
|
"pmaddwd %%mm7, %%mm6;\n"
|
||||||
|
"paddd %%mm6, %%mm4;\n"
|
||||||
|
"paddd %%mm2, %%mm4;\n"
|
||||||
|
"paddd %%mm0, %%mm4;\n"
|
||||||
|
/* Come back and do for the last few bytes */
|
||||||
|
"movq 48(%%edi), %%mm6;\n"
|
||||||
|
"movq 56(%%edi), %%mm7;\n"
|
||||||
|
"psrad $16, %%mm6;\n"
|
||||||
|
"psrad $16, %%mm7;\n"
|
||||||
|
"packssdw %%mm7, %%mm6;\n"
|
||||||
|
"movq 24(%%esi), %%mm7;\n"
|
||||||
|
"pmaddwd %%mm7, %%mm6;\n"
|
||||||
|
"paddd %%mm6, %%mm4;\n"
|
||||||
|
"add $64, %%edi;\n"
|
||||||
|
"add $32, %%esi;\n"
|
||||||
|
"dec %%ecx;\n"
|
||||||
|
"jnz 1b;\n"
|
||||||
|
"movq %%mm4, %%mm0;\n"
|
||||||
|
"psrlq $32, %%mm0;\n"
|
||||||
|
"paddd %%mm0, %%mm4;\n"
|
||||||
|
"movd %%mm4, %0;\n"
|
||||||
|
: "=r" (sum)
|
||||||
|
: "r" (coeffs), "r" (hist), "r" (len)
|
||||||
|
: "%ecx", "%edi", "%esi"
|
||||||
|
);
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void UPDATE(volatile int *taps, const short *history, const int nsuppr, const int ntaps)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int correction;
|
||||||
|
for (i=0;i<ntaps;i++) {
|
||||||
|
correction = history[i] * nsuppr;
|
||||||
|
taps[i] += correction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void UPDATE2(volatile int *taps, volatile short *taps_short, const short *history, const int nsuppr, const int ntaps)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int correction;
|
||||||
|
#if 0
|
||||||
|
ntaps >>= 4;
|
||||||
|
/* First, load up taps, */
|
||||||
|
__asm__ (
|
||||||
|
"pxor %%mm4, %%mm4;\n"
|
||||||
|
"mov %0, %%edi;\n"
|
||||||
|
"mov %1, %%esi;\n"
|
||||||
|
"mov %3, %%ecx;\n"
|
||||||
|
"1:"
|
||||||
|
"jnz 1b;\n"
|
||||||
|
"movq %%mm4, %%mm0;\n"
|
||||||
|
"psrlq $32, %%mm0;\n"
|
||||||
|
"paddd %%mm0, %%mm4;\n"
|
||||||
|
"movd %%mm4, %0;\n"
|
||||||
|
: "=r" (taps), "=r" (taps_short)
|
||||||
|
: "r" (history), "r" (nsuppr), "r" (ntaps), "0" (taps)
|
||||||
|
: "%ecx", "%edi", "%esi"
|
||||||
|
);
|
||||||
|
#endif
|
||||||
|
#if 1
|
||||||
|
for (i=0;i<ntaps;i++) {
|
||||||
|
correction = history[i] * nsuppr;
|
||||||
|
taps[i] += correction;
|
||||||
|
taps_short[i] = taps[i] >> 16;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int CONVOLVE2(const short *coeffs, const short *hist, int len)
|
||||||
|
{
|
||||||
|
int sum;
|
||||||
|
/* Divide length by 16 */
|
||||||
|
len >>= 4;
|
||||||
|
|
||||||
|
/* Clear our accumulator, mm4 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
For every set of eight...
|
||||||
|
Load in eight coefficients and eight historic samples, multliply add and
|
||||||
|
accumulate the result
|
||||||
|
*/
|
||||||
|
__asm__ (
|
||||||
|
"pxor %%mm4, %%mm4;\n"
|
||||||
|
"mov %1, %%edi;\n"
|
||||||
|
"mov %2, %%esi;\n"
|
||||||
|
"mov %3, %%ecx;\n"
|
||||||
|
"1:"
|
||||||
|
"movq 0(%%edi), %%mm0;\n"
|
||||||
|
"movq 8(%%edi), %%mm2;\n"
|
||||||
|
"movq 0(%%esi), %%mm1;\n"
|
||||||
|
"movq 8(%%esi), %%mm3;\n"
|
||||||
|
"pmaddwd %%mm1, %%mm0;\n"
|
||||||
|
"pmaddwd %%mm3, %%mm2;\n"
|
||||||
|
"paddd %%mm2, %%mm4;\n"
|
||||||
|
"paddd %%mm0, %%mm4;\n"
|
||||||
|
"movq 16(%%edi), %%mm0;\n"
|
||||||
|
"movq 24(%%edi), %%mm2;\n"
|
||||||
|
"movq 16(%%esi), %%mm1;\n"
|
||||||
|
"movq 24(%%esi), %%mm3;\n"
|
||||||
|
"pmaddwd %%mm1, %%mm0;\n"
|
||||||
|
"pmaddwd %%mm3, %%mm2;\n"
|
||||||
|
"paddd %%mm2, %%mm4;\n"
|
||||||
|
"paddd %%mm0, %%mm4;\n"
|
||||||
|
"add $32, %%edi;\n"
|
||||||
|
"add $32, %%esi;\n"
|
||||||
|
"dec %%ecx;\n"
|
||||||
|
"jnz 1b;\n"
|
||||||
|
"movq %%mm4, %%mm0;\n"
|
||||||
|
"psrlq $32, %%mm0;\n"
|
||||||
|
"paddd %%mm0, %%mm4;\n"
|
||||||
|
"movd %%mm4, %0;\n"
|
||||||
|
: "=r" (sum)
|
||||||
|
: "r" (coeffs), "r" (hist), "r" (len)
|
||||||
|
: "%ecx", "%edi", "%esi"
|
||||||
|
);
|
||||||
|
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
static inline short MAX16(const short *y, int len, int *pos)
|
||||||
|
{
|
||||||
|
int k;
|
||||||
|
short max = 0;
|
||||||
|
int bestpos = 0;
|
||||||
|
for (k=0;k<len;k++) {
|
||||||
|
if (max < y[k]) {
|
||||||
|
bestpos = k;
|
||||||
|
max = y[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*pos = (len - 1 - bestpos);
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#ifdef ZT_CHUNKSIZE
|
||||||
|
static inline void ACSS(short *dst, short *src)
|
||||||
|
{
|
||||||
|
int x,sum;
|
||||||
|
/* Add src to dst with saturation, storing in dst */
|
||||||
|
for (x=0;x<ZT_CHUNKSIZE;x++) {
|
||||||
|
sum = dst[x]+src[x];
|
||||||
|
if (sum > 32767)
|
||||||
|
sum = 32767;
|
||||||
|
else if (sum < -32768)
|
||||||
|
sum = -32768;
|
||||||
|
dst[x] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void SCSS(short *dst, short *src)
|
||||||
|
{
|
||||||
|
int x,sum;
|
||||||
|
/* Add src to dst with saturation, storing in dst */
|
||||||
|
for (x=0;x<ZT_CHUNKSIZE;x++) {
|
||||||
|
sum = dst[x]-src[x];
|
||||||
|
if (sum > 32767)
|
||||||
|
sum = 32767;
|
||||||
|
else if (sum < -32768)
|
||||||
|
sum = -32768;
|
||||||
|
dst[x] = sum;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* ZT_CHUNKSIZE */
|
||||||
|
|
||||||
|
static inline int CONVOLVE(const int *coeffs, const short *hist, int len)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
int sum = 0;
|
||||||
|
for (x=0;x<len;x++)
|
||||||
|
sum += (coeffs[x] >> 16) * hist[x];
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int CONVOLVE2(const short *coeffs, const short *hist, int len)
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
int sum = 0;
|
||||||
|
for (x=0;x<len;x++)
|
||||||
|
sum += coeffs[x] * hist[x];
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void UPDATE(int *taps, const short *history, const int nsuppr, const int ntaps)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int correction;
|
||||||
|
for (i=0;i<ntaps;i++) {
|
||||||
|
correction = history[i] * nsuppr;
|
||||||
|
taps[i] += correction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void UPDATE2(int *taps, short *taps_short, const short *history, const int nsuppr, const int ntaps)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
int correction;
|
||||||
|
for (i=0;i<ntaps;i++) {
|
||||||
|
correction = history[i] * nsuppr;
|
||||||
|
taps[i] += correction;
|
||||||
|
taps_short[i] = taps[i] >> 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline short MAX16(const short *y, int len, int *pos)
|
||||||
|
{
|
||||||
|
int k;
|
||||||
|
short max = 0;
|
||||||
|
int bestpos = 0;
|
||||||
|
for (k=0;k<len;k++) {
|
||||||
|
if (max < y[k]) {
|
||||||
|
bestpos = k;
|
||||||
|
max = y[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*pos = (len - 1 - bestpos);
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* MMX */
|
||||||
|
#endif /* _ZAPTEL_ARITH_H */
|
|
@ -0,0 +1,73 @@
|
||||||
|
/*
|
||||||
|
* SpanDSP - a series of DSP components for telephony
|
||||||
|
*
|
||||||
|
* biquad.h - General telephony bi-quad section routines (currently this just
|
||||||
|
* handles canonic/type 2 form)
|
||||||
|
*
|
||||||
|
* Written by Steve Underwood <steveu@coppice.org>
|
||||||
|
*
|
||||||
|
* Copyright (C) 2001 Steve Underwood
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
int32_t gain;
|
||||||
|
int32_t a1;
|
||||||
|
int32_t a2;
|
||||||
|
int32_t b1;
|
||||||
|
int32_t b2;
|
||||||
|
|
||||||
|
int32_t z1;
|
||||||
|
int32_t z2;
|
||||||
|
} biquad2_state_t;
|
||||||
|
|
||||||
|
static inline void biquad2_init (biquad2_state_t *bq,
|
||||||
|
int32_t gain,
|
||||||
|
int32_t a1,
|
||||||
|
int32_t a2,
|
||||||
|
int32_t b1,
|
||||||
|
int32_t b2)
|
||||||
|
{
|
||||||
|
bq->gain = gain;
|
||||||
|
bq->a1 = a1;
|
||||||
|
bq->a2 = a2;
|
||||||
|
bq->b1 = b1;
|
||||||
|
bq->b2 = b2;
|
||||||
|
|
||||||
|
bq->z1 = 0;
|
||||||
|
bq->z2 = 0;
|
||||||
|
}
|
||||||
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
|
static inline int16_t biquad2 (biquad2_state_t *bq, int16_t sample)
|
||||||
|
{
|
||||||
|
int32_t y;
|
||||||
|
int32_t z0;
|
||||||
|
|
||||||
|
z0 = sample*bq->gain + bq->z1*bq->a1 + bq->z2*bq->a2;
|
||||||
|
y = z0 + bq->z1*bq->b1 + bq->z2*bq->b2;
|
||||||
|
|
||||||
|
bq->z2 = bq->z1;
|
||||||
|
bq->z1 = z0 >> 15;
|
||||||
|
y >>= 15;
|
||||||
|
return y;
|
||||||
|
}
|
||||||
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
/*- End of file ------------------------------------------------------------*/
|
|
@ -0,0 +1,317 @@
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
* Simple but fast Echo cancellation for mISDN_dsp.
|
||||||
|
*
|
||||||
|
* Copyright Chrisian Richter
|
||||||
|
*
|
||||||
|
* This software may be used and distributed according to the terms
|
||||||
|
* of the GNU General Public License, incorporated herein by reference.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "layer1.h"
|
||||||
|
#include "helper.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include "dsp.h"
|
||||||
|
#include <asm/i387.h>
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* how this works:
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void bchdev_echocancel_chunk(dsp_t* dev, uint8_t *rxchunk, uint8_t *txchunk, uint16_t size);
|
||||||
|
int bchdev_echocancel_activate(dsp_t* dev, int deftaps, int train);
|
||||||
|
void bchdev_echocancel_deactivate(dsp_t* dev);
|
||||||
|
|
||||||
|
|
||||||
|
void
|
||||||
|
dsp_cancel_tx(dsp_t *dsp, u8 *data, int len)
|
||||||
|
{
|
||||||
|
if (!dsp ) return ;
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
if (dsp->txbuflen + len < ECHOCAN_BUFLEN) {
|
||||||
|
memcpy(&dsp->txbuf[dsp->txbuflen],data,len);
|
||||||
|
dsp->txbuflen+=len;
|
||||||
|
} else {
|
||||||
|
static int i=0;
|
||||||
|
if(i==4000) {
|
||||||
|
printk("ECHOCAN: i:%d TXBUF Overflow txbuflen:%d txcancellen:%d\n", i, dsp->txbuflen,len);
|
||||||
|
i=0;
|
||||||
|
}
|
||||||
|
i+=len;
|
||||||
|
|
||||||
|
dsp->txbuflen=0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
dsp_cancel_rx(dsp_t *dsp, u8 *data, int len)
|
||||||
|
{
|
||||||
|
if (!dsp ) return ;
|
||||||
|
if (!data) return;
|
||||||
|
|
||||||
|
if (len <= dsp->txbuflen) {
|
||||||
|
char tmp[ECHOCAN_BUFLEN];
|
||||||
|
|
||||||
|
int delta=dsp->txbuflen-len;
|
||||||
|
|
||||||
|
memcpy(tmp,&dsp->txbuf[len],delta);
|
||||||
|
|
||||||
|
kernel_fpu_begin();
|
||||||
|
bchdev_echocancel_chunk(dsp, data, dsp->txbuf, len);
|
||||||
|
kernel_fpu_end();
|
||||||
|
|
||||||
|
memcpy(dsp->txbuf,tmp,delta);
|
||||||
|
dsp->txbuflen=delta;
|
||||||
|
} else {
|
||||||
|
static int i=0;
|
||||||
|
if(i==4000) {
|
||||||
|
printk("ECHOCAN: i:%d TXBUF Underrun txbuflen:%d rxcancellen:%d\n",i,dsp->txbuflen,len);
|
||||||
|
i=0;
|
||||||
|
}
|
||||||
|
i+=len;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
dsp_cancel_init(dsp_t *dsp, int deftaps, int training, int delay)
|
||||||
|
{
|
||||||
|
|
||||||
|
if (!dsp) return -1;
|
||||||
|
|
||||||
|
printk("DSP_CANCEL_INIT called\n");
|
||||||
|
|
||||||
|
if (delay < 0)
|
||||||
|
{
|
||||||
|
printk("Disabling EC\n");
|
||||||
|
dsp->cancel_enable = 0;
|
||||||
|
|
||||||
|
dsp->txbuflen=0;
|
||||||
|
|
||||||
|
bchdev_echocancel_deactivate(dsp);
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
dsp->txbuflen=0;
|
||||||
|
dsp->rxbuflen=0;
|
||||||
|
|
||||||
|
|
||||||
|
bchdev_echocancel_activate(dsp,deftaps, training);
|
||||||
|
|
||||||
|
printk("Enabling EC\n");
|
||||||
|
dsp->cancel_enable = 1;
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*****************************************************/
|
||||||
|
#define __ECHO_STATE_MUTE (1 << 8)
|
||||||
|
#define ECHO_STATE_IDLE (0)
|
||||||
|
#define ECHO_STATE_PRETRAINING (1 | (__ECHO_STATE_MUTE))
|
||||||
|
#define ECHO_STATE_STARTTRAINING (2 | (__ECHO_STATE_MUTE))
|
||||||
|
#define ECHO_STATE_AWAITINGECHO (3 | (__ECHO_STATE_MUTE))
|
||||||
|
#define ECHO_STATE_TRAINING (4 | (__ECHO_STATE_MUTE))
|
||||||
|
#define ECHO_STATE_ACTIVE (5)
|
||||||
|
|
||||||
|
#define AMI_MASK 0x55
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/** @return string of given echo cancellation state */
|
||||||
|
char* bchdev_echocancel_statestr(uint16_t state)
|
||||||
|
{
|
||||||
|
switch(state) {
|
||||||
|
case ECHO_STATE_IDLE:
|
||||||
|
return "idle";
|
||||||
|
break;
|
||||||
|
case ECHO_STATE_PRETRAINING:
|
||||||
|
return "pre-training";
|
||||||
|
break;
|
||||||
|
case ECHO_STATE_STARTTRAINING:
|
||||||
|
return "transmit impulse";
|
||||||
|
break;
|
||||||
|
case ECHO_STATE_AWAITINGECHO:
|
||||||
|
return "awaiting echo";
|
||||||
|
break;
|
||||||
|
case ECHO_STATE_TRAINING:
|
||||||
|
return "training start";
|
||||||
|
break;
|
||||||
|
case ECHO_STATE_ACTIVE:
|
||||||
|
return "training finished";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Changes state of echo cancellation to given state */
|
||||||
|
void bchdev_echocancel_setstate(dsp_t* dev, uint16_t state)
|
||||||
|
{
|
||||||
|
char* statestr = bchdev_echocancel_statestr(state);
|
||||||
|
|
||||||
|
printk("bchdev: echo cancel state %d (%s)\n", state & 0xff, statestr);
|
||||||
|
if (state == ECHO_STATE_ACTIVE)
|
||||||
|
printk("bchdev: %d taps trained\n", dev->echolastupdate);
|
||||||
|
dev->echostate = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int buf_size=0;
|
||||||
|
static int ec_timer=2000;
|
||||||
|
//static int ec_timer=1000;
|
||||||
|
|
||||||
|
|
||||||
|
/** Activates echo cancellation for the given bch_dev, device must have been locked before! */
|
||||||
|
int bchdev_echocancel_activate(dsp_t* dev, int deftaps, int training)
|
||||||
|
{
|
||||||
|
int taps;
|
||||||
|
|
||||||
|
if (! dev) return -EINVAL;
|
||||||
|
|
||||||
|
if (dev->ec && dev->ecdis_rd && dev->ecdis_wr) {
|
||||||
|
// already active
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deftaps>0) {
|
||||||
|
taps=deftaps;
|
||||||
|
} else {
|
||||||
|
taps=128;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
switch (buf_size) {
|
||||||
|
case 0: taps += 0; break;
|
||||||
|
case 1: taps += 256-128; break;
|
||||||
|
case 2: taps += 512-128; break;
|
||||||
|
default: taps += 1024-128;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dev->ec) dev->ec = echo_can_create(taps, 0);
|
||||||
|
if (!dev->ec) {
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev->echolastupdate = 0;
|
||||||
|
|
||||||
|
if (!training) {
|
||||||
|
dev->echotimer=0;
|
||||||
|
bchdev_echocancel_setstate(dev, ECHO_STATE_IDLE);
|
||||||
|
} else {
|
||||||
|
if (training<10)
|
||||||
|
training= ec_timer;
|
||||||
|
|
||||||
|
dev->echotimer = training;
|
||||||
|
bchdev_echocancel_setstate(dev, ECHO_STATE_PRETRAINING);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!dev->ecdis_rd) dev->ecdis_rd = kmalloc(sizeof(echo_can_disable_detector_state_t), GFP_KERNEL);
|
||||||
|
if (!dev->ecdis_rd) {
|
||||||
|
kfree(dev->ec); dev->ec = NULL;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
echo_can_disable_detector_init(dev->ecdis_rd);
|
||||||
|
|
||||||
|
if (!dev->ecdis_wr) dev->ecdis_wr = kmalloc(sizeof(echo_can_disable_detector_state_t), GFP_KERNEL);
|
||||||
|
if (!dev->ecdis_wr) {
|
||||||
|
kfree(dev->ec); dev->ec = NULL;
|
||||||
|
kfree(dev->ecdis_rd); dev->ecdis_rd = NULL;
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
echo_can_disable_detector_init(dev->ecdis_wr);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deactivates echo cancellation for the given bch_dev, device must have been locked before! */
|
||||||
|
void bchdev_echocancel_deactivate(dsp_t* dev)
|
||||||
|
{
|
||||||
|
if (! dev) return;
|
||||||
|
|
||||||
|
//chan_misdn_log("bchdev: deactivating echo cancellation on port=%04x, chan=%02x\n", dev->stack->port, dev->channel);
|
||||||
|
|
||||||
|
if (dev->ec) echo_can_free(dev->ec);
|
||||||
|
dev->ec = NULL;
|
||||||
|
|
||||||
|
dev->echolastupdate = 0;
|
||||||
|
dev->echotimer = 0;
|
||||||
|
bchdev_echocancel_setstate(dev, ECHO_STATE_IDLE);
|
||||||
|
|
||||||
|
if (dev->ecdis_rd) kfree(dev->ecdis_rd);
|
||||||
|
dev->ecdis_rd = NULL;
|
||||||
|
|
||||||
|
if (dev->ecdis_wr) kfree(dev->ecdis_wr);
|
||||||
|
dev->ecdis_wr = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Processes one TX- and one RX-packet with echocancellation */
|
||||||
|
void bchdev_echocancel_chunk(dsp_t* dev, uint8_t *rxchunk, uint8_t *txchunk, uint16_t size)
|
||||||
|
{
|
||||||
|
int16_t rxlin, txlin;
|
||||||
|
uint16_t pos;
|
||||||
|
|
||||||
|
/* Perform echo cancellation on a chunk if requested */
|
||||||
|
if (dev->ec) {
|
||||||
|
if (dev->echostate & __ECHO_STATE_MUTE) {
|
||||||
|
if (dev->echostate == ECHO_STATE_STARTTRAINING) {
|
||||||
|
// Transmit impulse now
|
||||||
|
txchunk[0] = dsp_audio_s16_to_law[16384 & 0xffff];
|
||||||
|
memset(txchunk+1, 0, size-1);
|
||||||
|
bchdev_echocancel_setstate(dev, ECHO_STATE_TRAINING); //AWAITINGECHO);
|
||||||
|
} else {
|
||||||
|
// train the echo cancellation
|
||||||
|
for (pos = 0; pos < size; pos++) {
|
||||||
|
rxlin = dsp_audio_law_to_s32[rxchunk[pos]];
|
||||||
|
txlin = dsp_audio_law_to_s32[txchunk[pos]];
|
||||||
|
if (dev->echostate == ECHO_STATE_PRETRAINING) {
|
||||||
|
if (dev->echotimer <= 0) {
|
||||||
|
dev->echotimer = 0;
|
||||||
|
bchdev_echocancel_setstate(dev, ECHO_STATE_STARTTRAINING);
|
||||||
|
} else {
|
||||||
|
dev->echotimer--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((dev->echostate == ECHO_STATE_AWAITINGECHO) && (txlin > 8000)) {
|
||||||
|
dev->echolastupdate = 0;
|
||||||
|
bchdev_echocancel_setstate(dev, ECHO_STATE_TRAINING);
|
||||||
|
}
|
||||||
|
if (dev->echostate == ECHO_STATE_TRAINING) {
|
||||||
|
if (echo_can_traintap(dev->ec, dev->echolastupdate++, rxlin)) {
|
||||||
|
bchdev_echocancel_setstate(dev, ECHO_STATE_ACTIVE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rxchunk[pos] = dsp_silence;
|
||||||
|
txchunk[pos] = dsp_silence;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (pos = 0; pos < size; pos++) {
|
||||||
|
rxlin = dsp_audio_law_to_s32[rxchunk[pos]];
|
||||||
|
txlin = dsp_audio_law_to_s32[txchunk[pos]];
|
||||||
|
|
||||||
|
if (echo_can_disable_detector_update(dev->ecdis_rd, rxlin) ||
|
||||||
|
echo_can_disable_detector_update(dev->ecdis_wr, txlin)) {
|
||||||
|
bchdev_echocancel_deactivate(dev);
|
||||||
|
printk("EC: Disable tone detected\n");
|
||||||
|
return ;
|
||||||
|
} else {
|
||||||
|
rxlin = echo_can_update(dev->ec, txlin, rxlin);
|
||||||
|
rxchunk[pos] = dsp_audio_s16_to_law[rxlin & 0xffff];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/******************************************************/
|
|
@ -0,0 +1,118 @@
|
||||||
|
/*
|
||||||
|
* SpanDSP - a series of DSP components for telephony
|
||||||
|
*
|
||||||
|
* ec_disable_detector.h - A detector which should eventually meet the
|
||||||
|
* G.164/G.165 requirements for detecting the
|
||||||
|
* 2100Hz echo cancellor disable tone.
|
||||||
|
*
|
||||||
|
* Written by Steve Underwood <steveu@coppice.org>
|
||||||
|
*
|
||||||
|
* Copyright (C) 2001 Steve Underwood
|
||||||
|
*
|
||||||
|
* 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 "dsp_biquad.h"
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
biquad2_state_t notch;
|
||||||
|
int notch_level;
|
||||||
|
int channel_level;
|
||||||
|
int tone_present;
|
||||||
|
int tone_cycle_duration;
|
||||||
|
int good_cycles;
|
||||||
|
int hit;
|
||||||
|
} echo_can_disable_detector_state_t;
|
||||||
|
|
||||||
|
|
||||||
|
#define FALSE 0
|
||||||
|
#define TRUE (!FALSE)
|
||||||
|
|
||||||
|
static inline void echo_can_disable_detector_init (echo_can_disable_detector_state_t *det)
|
||||||
|
{
|
||||||
|
/* Elliptic notch */
|
||||||
|
/* This is actually centred at 2095Hz, but gets the balance we want, due
|
||||||
|
to the asymmetric walls of the notch */
|
||||||
|
biquad2_init (&det->notch,
|
||||||
|
(int32_t) (-0.7600000*32768.0),
|
||||||
|
(int32_t) (-0.1183852*32768.0),
|
||||||
|
(int32_t) (-0.5104039*32768.0),
|
||||||
|
(int32_t) ( 0.1567596*32768.0),
|
||||||
|
(int32_t) ( 1.0000000*32768.0));
|
||||||
|
|
||||||
|
det->channel_level = 0;
|
||||||
|
det->notch_level = 0;
|
||||||
|
det->tone_present = FALSE;
|
||||||
|
det->tone_cycle_duration = 0;
|
||||||
|
det->good_cycles = 0;
|
||||||
|
det->hit = 0;
|
||||||
|
}
|
||||||
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
|
||||||
|
static inline int echo_can_disable_detector_update (echo_can_disable_detector_state_t *det,
|
||||||
|
int16_t amp)
|
||||||
|
{
|
||||||
|
int16_t notched;
|
||||||
|
|
||||||
|
notched = biquad2 (&det->notch, amp);
|
||||||
|
/* Estimate the overall energy in the channel, and the energy in
|
||||||
|
the notch (i.e. overall channel energy - tone energy => noise).
|
||||||
|
Use abs instead of multiply for speed (is it really faster?).
|
||||||
|
Damp the overall energy a little more for a stable result.
|
||||||
|
Damp the notch energy a little less, so we don't damp out the
|
||||||
|
blip every time the phase reverses */
|
||||||
|
det->channel_level += ((abs(amp) - det->channel_level) >> 5);
|
||||||
|
det->notch_level += ((abs(notched) - det->notch_level) >> 4);
|
||||||
|
if (det->channel_level > 280)
|
||||||
|
{
|
||||||
|
/* There is adequate energy in the channel. Is it mostly at 2100Hz? */
|
||||||
|
if (det->notch_level*6 < det->channel_level)
|
||||||
|
{
|
||||||
|
/* The notch says yes, so we have the tone. */
|
||||||
|
if (!det->tone_present)
|
||||||
|
{
|
||||||
|
/* Do we get a kick every 450+-25ms? */
|
||||||
|
if (det->tone_cycle_duration >= 425*8
|
||||||
|
&&
|
||||||
|
det->tone_cycle_duration <= 475*8)
|
||||||
|
{
|
||||||
|
det->good_cycles++;
|
||||||
|
if (det->good_cycles > 2)
|
||||||
|
det->hit = TRUE;
|
||||||
|
}
|
||||||
|
det->tone_cycle_duration = 0;
|
||||||
|
}
|
||||||
|
det->tone_present = TRUE;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
det->tone_present = FALSE;
|
||||||
|
}
|
||||||
|
det->tone_cycle_duration++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
det->tone_present = FALSE;
|
||||||
|
det->tone_cycle_duration = 0;
|
||||||
|
det->good_cycles = 0;
|
||||||
|
}
|
||||||
|
return det->hit;
|
||||||
|
}
|
||||||
|
/*- End of function --------------------------------------------------------*/
|
||||||
|
/*- End of file ------------------------------------------------------------*/
|
|
@ -0,0 +1,574 @@
|
||||||
|
/*
|
||||||
|
* ECHO_CAN_KB1
|
||||||
|
*
|
||||||
|
* by Kris Boutilier
|
||||||
|
*
|
||||||
|
* Based upon mech2.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2002, Digium, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software and may be used and
|
||||||
|
* distributed according to the terms of the GNU
|
||||||
|
* General Public License, incorporated herein by
|
||||||
|
* reference.
|
||||||
|
*
|
||||||
|
* Additional background on the techniques used in this code can be found in:
|
||||||
|
*
|
||||||
|
* Messerschmitt, David; Hedberg, David; Cole, Christopher; Haoui, Amine;
|
||||||
|
* Winship, Peter; "Digital Voice Echo Canceller with a TMS32020,"
|
||||||
|
* in Digital Signal Processing Applications with the TMS320 Family,
|
||||||
|
* pp. 415-437, Texas Instruments, Inc., 1986.
|
||||||
|
*
|
||||||
|
* A pdf of which is available by searching on the document title at http://www.ti.com/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MARK2_ECHO_H
|
||||||
|
#define _MARK2_ECHO_H
|
||||||
|
|
||||||
|
#define EC_TYPE "KB1"
|
||||||
|
|
||||||
|
#ifdef __KERNEL__
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#define MALLOC(a) kmalloc((a), GFP_KERNEL)
|
||||||
|
#define FREE(a) kfree(a)
|
||||||
|
#else
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#define MALLOC(a) malloc(a)
|
||||||
|
#define FREE(a) free(a)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Uncomment to provide summary statistics for overall echo can performance every 4000 samples */
|
||||||
|
/* #define MEC2_STATS 4000 */
|
||||||
|
|
||||||
|
/* Uncomment to generate per-sample statistics - this will severely degrade system performance and audio quality */
|
||||||
|
/* #define MEC2_STATS_DETAILED */
|
||||||
|
|
||||||
|
/* Get optimized routines for math */
|
||||||
|
#include "dsp_arith.h"
|
||||||
|
|
||||||
|
/* Bring in definitions for the various constants and thresholds */
|
||||||
|
#include "dsp_kb1ec_const.h"
|
||||||
|
|
||||||
|
#ifndef NULL
|
||||||
|
#define NULL 0
|
||||||
|
#endif
|
||||||
|
#ifndef FALSE
|
||||||
|
#define FALSE 0
|
||||||
|
#endif
|
||||||
|
#ifndef TRUE
|
||||||
|
#define TRUE (!FALSE)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Generic circular buffer definition */
|
||||||
|
typedef struct {
|
||||||
|
/* Pointer to the relative 'start' of the buffer */
|
||||||
|
int idx_d;
|
||||||
|
/* The absolute size of the buffer */
|
||||||
|
int size_d;
|
||||||
|
/* The actual sample - twice as large as we need, however we do store values at idx_d and idx_d+size_d */
|
||||||
|
short *buf_d;
|
||||||
|
} echo_can_cb_s;
|
||||||
|
|
||||||
|
/* Echo canceller definition */
|
||||||
|
struct echo_can_state {
|
||||||
|
/* an arbitrary ID for this echo can - this really should be settable from the calling channel... */
|
||||||
|
int id;
|
||||||
|
|
||||||
|
/* absolute time - aka. sample number index - essentially the number of samples since this can was init'ed */
|
||||||
|
int i_d;
|
||||||
|
|
||||||
|
/* Pre-computed constants */
|
||||||
|
/* ---------------------- */
|
||||||
|
/* Number of filter coefficents */
|
||||||
|
int N_d;
|
||||||
|
/* Rate of adaptation of filter */
|
||||||
|
int beta2_i;
|
||||||
|
|
||||||
|
/* Accumulators for power computations */
|
||||||
|
/* ----------------------------------- */
|
||||||
|
/* reference signal power estimate - aka. Average absolute value of y(k) */
|
||||||
|
int Ly_i;
|
||||||
|
/* ... */
|
||||||
|
int Lu_i;
|
||||||
|
|
||||||
|
/* Accumulators for signal detectors */
|
||||||
|
/* --------------------------------- */
|
||||||
|
/* Power estimate of the recent past of the near-end hybrid signal - aka. Short-time average of: 2 x |s(i)| */
|
||||||
|
int s_tilde_i;
|
||||||
|
/* Power estimate of the recent past of the far-end receive signal - aka. Short-time average of: |y(i)| */
|
||||||
|
int y_tilde_i;
|
||||||
|
|
||||||
|
/* Near end speech detection counter - stores Hangover counter time remaining, in samples */
|
||||||
|
int HCNTR_d;
|
||||||
|
|
||||||
|
/* Circular buffers and coefficients */
|
||||||
|
/* --------------------------------- */
|
||||||
|
/* ... */
|
||||||
|
int *a_i;
|
||||||
|
/* ... */
|
||||||
|
short *a_s;
|
||||||
|
/* Reference samples of far-end receive signal */
|
||||||
|
echo_can_cb_s y_s;
|
||||||
|
/* Reference samples of near-end signal */
|
||||||
|
echo_can_cb_s s_s;
|
||||||
|
/* Reference samples of near-end signal minus echo estimate */
|
||||||
|
echo_can_cb_s u_s;
|
||||||
|
/* Reference samples of far-end receive signal used to calculate short-time average */
|
||||||
|
echo_can_cb_s y_tilde_s;
|
||||||
|
|
||||||
|
/* Peak far-end receive signal */
|
||||||
|
/* --------------------------- */
|
||||||
|
/* Highest y_tilde value in the sample buffer */
|
||||||
|
short max_y_tilde;
|
||||||
|
/* Index of the sample containing the max_y_tilde value */
|
||||||
|
int max_y_tilde_pos;
|
||||||
|
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
/* Storage for performance statistics */
|
||||||
|
int cntr_nearend_speech_frames;
|
||||||
|
int cntr_residualcorrected_frames;
|
||||||
|
int cntr_residualcorrected_framesskipped;
|
||||||
|
int cntr_coeff_updates;
|
||||||
|
int cntr_coeff_missedupdates;
|
||||||
|
|
||||||
|
int avg_Lu_i_toolow;
|
||||||
|
int avg_Lu_i_ok;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void init_cb_s(echo_can_cb_s *cb, int len, void *where)
|
||||||
|
{
|
||||||
|
cb->buf_d = (short *)where;
|
||||||
|
cb->idx_d = 0;
|
||||||
|
cb->size_d = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void add_cc_s(echo_can_cb_s *cb, short newval)
|
||||||
|
{
|
||||||
|
/* Can't use modulus because N+M isn't a power of two (generally) */
|
||||||
|
cb->idx_d--;
|
||||||
|
if (cb->idx_d < (int)0)
|
||||||
|
/* Whoops - the pointer to the 'start' wrapped around so reset it to the top of the buffer */
|
||||||
|
cb->idx_d += cb->size_d;
|
||||||
|
|
||||||
|
/* Load two copies into memory */
|
||||||
|
cb->buf_d[cb->idx_d] = newval;
|
||||||
|
cb->buf_d[cb->idx_d + cb->size_d] = newval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline short get_cc_s(echo_can_cb_s *cb, int pos)
|
||||||
|
{
|
||||||
|
/* Load two copies into memory */
|
||||||
|
return cb->buf_d[cb->idx_d + pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void init_cc(struct echo_can_state *ec, int N, int maxy, int maxu)
|
||||||
|
{
|
||||||
|
|
||||||
|
void *ptr = ec;
|
||||||
|
unsigned long tmp;
|
||||||
|
/* Double-word align past end of state */
|
||||||
|
ptr += sizeof(struct echo_can_state);
|
||||||
|
tmp = (unsigned long)ptr;
|
||||||
|
tmp += 3;
|
||||||
|
tmp &= ~3L;
|
||||||
|
ptr = (void *)tmp;
|
||||||
|
|
||||||
|
/* Reset parameters */
|
||||||
|
ec->N_d = N;
|
||||||
|
ec->beta2_i = DEFAULT_BETA1_I;
|
||||||
|
|
||||||
|
/* Allocate coefficient memory */
|
||||||
|
ec->a_i = ptr;
|
||||||
|
ptr += (sizeof(int) * ec->N_d);
|
||||||
|
ec->a_s = ptr;
|
||||||
|
ptr += (sizeof(short) * ec->N_d);
|
||||||
|
|
||||||
|
/* Reset Y circular buffer (short version) */
|
||||||
|
init_cb_s(&ec->y_s, maxy, ptr);
|
||||||
|
ptr += (sizeof(short) * (maxy) * 2);
|
||||||
|
|
||||||
|
/* Reset Sigma circular buffer (short version for FIR filter) */
|
||||||
|
init_cb_s(&ec->s_s, (1 << DEFAULT_ALPHA_ST_I), ptr);
|
||||||
|
ptr += (sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) * 2);
|
||||||
|
|
||||||
|
init_cb_s(&ec->u_s, maxu, ptr);
|
||||||
|
ptr += (sizeof(short) * maxu * 2);
|
||||||
|
|
||||||
|
/* Allocate a buffer for the reference signal power computation */
|
||||||
|
init_cb_s(&ec->y_tilde_s, ec->N_d, ptr);
|
||||||
|
|
||||||
|
/* Reset the absolute time index */
|
||||||
|
ec->i_d = (int)0;
|
||||||
|
|
||||||
|
/* Reset the power computations (for y and u) */
|
||||||
|
ec->Ly_i = DEFAULT_CUTOFF_I;
|
||||||
|
ec->Lu_i = DEFAULT_CUTOFF_I;
|
||||||
|
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
/* set the identity */
|
||||||
|
ec->id = (int)&ptr;
|
||||||
|
|
||||||
|
/* Reset performance stats */
|
||||||
|
ec->cntr_nearend_speech_frames = (int)0;
|
||||||
|
ec->cntr_residualcorrected_frames = (int)0;
|
||||||
|
ec->cntr_residualcorrected_framesskipped = (int)0;
|
||||||
|
ec->cntr_coeff_updates = (int)0;
|
||||||
|
ec->cntr_coeff_missedupdates = (int)0;
|
||||||
|
|
||||||
|
ec->avg_Lu_i_toolow = (int)0;
|
||||||
|
ec->avg_Lu_i_ok = (int)0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Reset the near-end speech detector */
|
||||||
|
ec->s_tilde_i = (int)0;
|
||||||
|
ec->y_tilde_i = (int)0;
|
||||||
|
ec->HCNTR_d = (int)0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void echo_can_free(struct echo_can_state *ec)
|
||||||
|
{
|
||||||
|
FREE(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline short echo_can_update(struct echo_can_state *ec, short iref, short isig)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Declare local variables that are used more than once */
|
||||||
|
/* ... */
|
||||||
|
int k;
|
||||||
|
/* ... */
|
||||||
|
int rs;
|
||||||
|
/* ... */
|
||||||
|
short u;
|
||||||
|
/* ... */
|
||||||
|
int Py_i;
|
||||||
|
/* ... */
|
||||||
|
int two_beta_i;
|
||||||
|
|
||||||
|
/* flow A on pg. 428 */
|
||||||
|
/* eq. (16): high-pass filter the input to generate the next value;
|
||||||
|
* push the current value into the circular buffer
|
||||||
|
*
|
||||||
|
* sdc_im1_d = sdc_d;
|
||||||
|
* sdc_d = sig;
|
||||||
|
* s_i_d = sdc_d;
|
||||||
|
* s_d = s_i_d;
|
||||||
|
* s_i_d = (float)(1.0 - gamma_d) * s_i_d
|
||||||
|
* + (float)(0.5 * (1.0 - gamma_d)) * (sdc_d - sdc_im1_d);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Update the Far-end receive signal circular buffers and accumulators */
|
||||||
|
/* ------------------------------------------------------------------- */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->y_tilde_i -= abs(get_cc_s(&ec->y_s, (1 << DEFAULT_ALPHA_YT_I) - 1 )) >> DEFAULT_ALPHA_YT_I;
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->y_tilde_i += abs(iref) >> DEFAULT_ALPHA_ST_I;
|
||||||
|
/* Push a copy of the new sample into its circular buffer */
|
||||||
|
add_cc_s(&ec->y_s, iref);
|
||||||
|
|
||||||
|
|
||||||
|
/* eq. (2): compute r in fixed-point */
|
||||||
|
rs = CONVOLVE2(ec->a_s,
|
||||||
|
ec->y_s.buf_d + ec->y_s.idx_d,
|
||||||
|
ec->N_d);
|
||||||
|
rs >>= 15;
|
||||||
|
|
||||||
|
/* eq. (3): compute the output value (see figure 3) and the error
|
||||||
|
* note: the error is the same as the output signal when near-end
|
||||||
|
* speech is not present
|
||||||
|
*/
|
||||||
|
u = isig - rs;
|
||||||
|
|
||||||
|
/* Push a copy of the output value sample into its circular buffer */
|
||||||
|
add_cc_s(&ec->u_s, u);
|
||||||
|
|
||||||
|
|
||||||
|
/* Update the Near-end hybrid signal circular buffers and accumulators */
|
||||||
|
/* ------------------------------------------------------------------- */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->s_tilde_i -= abs(get_cc_s(&ec->s_s, (1 << DEFAULT_ALPHA_ST_I) - 1 ));
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->s_tilde_i += abs(isig);
|
||||||
|
/* Push a copy of the new sample into it's circular buffer */
|
||||||
|
add_cc_s(&ec->s_s, isig);
|
||||||
|
|
||||||
|
|
||||||
|
/* Push a copy of the current short-time average of the far-end receive signal into it's circular buffer */
|
||||||
|
add_cc_s(&ec->y_tilde_s, ec->y_tilde_i);
|
||||||
|
|
||||||
|
/* flow B on pg. 428 */
|
||||||
|
|
||||||
|
/* If the hangover timer isn't running then compute the new convergence factor, otherwise set Py_i to 32768 */
|
||||||
|
if (!ec->HCNTR_d) {
|
||||||
|
Py_i = (ec->Ly_i >> DEFAULT_SIGMA_LY_I) * (ec->Ly_i >> DEFAULT_SIGMA_LY_I);
|
||||||
|
Py_i >>= 15;
|
||||||
|
} else {
|
||||||
|
Py_i = (1 << 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* Vary rate of adaptation depending on position in the file
|
||||||
|
* Do not do this for the first (DEFAULT_UPDATE_TIME) secs after speech
|
||||||
|
* has begun of the file to allow the echo cancellor to estimate the
|
||||||
|
* channel accurately
|
||||||
|
* Still needs conversion!
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ec->start_speech_d != 0 ){
|
||||||
|
if ( ec->i_d > (DEFAULT_T0 + ec->start_speech_d)*(SAMPLE_FREQ) ){
|
||||||
|
ec->beta2_d = max_cc_float(MIN_BETA, DEFAULT_BETA1 * exp((-1/DEFAULT_TAU)*((ec->i_d/(float)SAMPLE_FREQ) - DEFAULT_T0 - ec->start_speech_d)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ec->beta2_d = DEFAULT_BETA1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Fixed point, inverted */
|
||||||
|
ec->beta2_i = DEFAULT_BETA1_I;
|
||||||
|
|
||||||
|
/* Fixed point version, inverted */
|
||||||
|
two_beta_i = (ec->beta2_i * Py_i) >> 15;
|
||||||
|
if (!two_beta_i)
|
||||||
|
two_beta_i++;
|
||||||
|
|
||||||
|
/* Update the Suppressed signal power estimate accumulator */
|
||||||
|
/* ------------------------------------------------------- */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->Lu_i -= abs(get_cc_s(&ec->u_s, (1 << DEFAULT_SIGMA_LU_I) - 1 )) ;
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->Lu_i += abs(u);
|
||||||
|
|
||||||
|
/* Update the Far-end reference signal power estimate accumulator */
|
||||||
|
/* -------------------------------------------------------------- */
|
||||||
|
/* eq. (10): update power estimate of the reference */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->Ly_i -= abs(get_cc_s(&ec->y_s, (1 << DEFAULT_SIGMA_LY_I) - 1)) ;
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->Ly_i += abs(iref);
|
||||||
|
|
||||||
|
if (ec->Ly_i < DEFAULT_CUTOFF_I)
|
||||||
|
ec->Ly_i = DEFAULT_CUTOFF_I;
|
||||||
|
|
||||||
|
|
||||||
|
/* Update the Peak far-end receive signal detected */
|
||||||
|
/* ----------------------------------------------- */
|
||||||
|
if (ec->y_tilde_i > ec->max_y_tilde) {
|
||||||
|
/* New highest y_tilde with full life */
|
||||||
|
ec->max_y_tilde = ec->y_tilde_i;
|
||||||
|
ec->max_y_tilde_pos = ec->N_d - 1;
|
||||||
|
} else if (--ec->max_y_tilde_pos < 0) {
|
||||||
|
/* Time to find new max y tilde... */
|
||||||
|
ec->max_y_tilde = MAX16(ec->y_tilde_s.buf_d + ec->y_tilde_s.idx_d, ec->N_d, &ec->max_y_tilde_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine if near end speech was detected in this sample */
|
||||||
|
/* -------------------------------------------------------- */
|
||||||
|
if (((ec->s_tilde_i >> (DEFAULT_ALPHA_ST_I - 1)) > ec->max_y_tilde)
|
||||||
|
&& (ec->max_y_tilde > 0)) {
|
||||||
|
/* Then start the Hangover counter */
|
||||||
|
ec->HCNTR_d = DEFAULT_HANGT;
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk(KERN_INFO "Reset near end speech timer with: s_tilde_i %d, stmnt %d, max_y_tilde %d\n", ec->s_tilde_i, (ec->s_tilde_i >> (DEFAULT_ALPHA_ST_I - 1)), ec->max_y_tilde);
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_nearend_speech_frames;
|
||||||
|
#endif
|
||||||
|
} else if (ec->HCNTR_d > (int)0) {
|
||||||
|
/* otherwise, if it's still non-zero, decrement the Hangover counter by one sample */
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_nearend_speech_frames;
|
||||||
|
#endif
|
||||||
|
ec->HCNTR_d--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update coefficients if no near-end speech in this sample (ie. HCNTR_d = 0)
|
||||||
|
* and we have enough signal to bother trying to update.
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
if (!ec->HCNTR_d && /* no near-end speech present */
|
||||||
|
!(ec->i_d % DEFAULT_M)) { /* we only update on every DEFAULM_M'th sample from the stream */
|
||||||
|
if (ec->Lu_i > MIN_UPDATE_THRESH_I) { /* there is sufficient energy above the noise floor to contain meaningful data */
|
||||||
|
/* so loop over all the filter coefficients */
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "updating coefficients with: ec->Lu_i %9d\n", ec->Lu_i);
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
ec->avg_Lu_i_ok = ec->avg_Lu_i_ok + ec->Lu_i;
|
||||||
|
++ec->cntr_coeff_updates;
|
||||||
|
#endif
|
||||||
|
for (k=0; k < ec->N_d; k++) {
|
||||||
|
/* eq. (7): compute an expectation over M_d samples */
|
||||||
|
int grad2;
|
||||||
|
grad2 = CONVOLVE2(ec->u_s.buf_d + ec->u_s.idx_d,
|
||||||
|
ec->y_s.buf_d + ec->y_s.idx_d + k,
|
||||||
|
DEFAULT_M);
|
||||||
|
/* eq. (7): update the coefficient */
|
||||||
|
ec->a_i[k] += grad2 / two_beta_i;
|
||||||
|
ec->a_s[k] = ec->a_i[k] >> 16;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "insufficient signal to update coefficients ec->Lu_i %5d < %5d\n", ec->Lu_i, MIN_UPDATE_THRESH_I);
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
ec->avg_Lu_i_toolow = ec->avg_Lu_i_toolow + ec->Lu_i;
|
||||||
|
++ec->cntr_coeff_missedupdates;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* paragraph below eq. (15): if no near-end speech in the sample and
|
||||||
|
* the reference signal power estimate > cutoff threshold
|
||||||
|
* then perform residual error suppression
|
||||||
|
*/
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
if (ec->HCNTR_d == 0)
|
||||||
|
printk( KERN_INFO "possibily correcting frame with ec->Ly_i %9d ec->Lu_i %9d and expression %d\n", ec->Ly_i, ec->Lu_i, (ec->Ly_i/(ec->Lu_i + 1)));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NO_ECHO_SUPPRESSOR
|
||||||
|
#ifdef AGGRESSIVE_SUPPRESSOR
|
||||||
|
if ((ec->HCNTR_d < AGGRESSIVE_HCNTR) && (ec->Ly_i > (ec->Lu_i << 1))) {
|
||||||
|
for (k=0; k < RESIDUAL_SUPRESSION_PASSES; k++) {
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I)) + 1);
|
||||||
|
}
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "aggresively correcting frame with ec->Ly_i %9d ec->Lu_i %9d expression %d\n", ec->Ly_i, ec->Lu_i, (ec->Ly_i/(ec->Lu_i + 1)));
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_residualcorrected_frames;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (ec->HCNTR_d == 0) {
|
||||||
|
if ((ec->Ly_i/(ec->Lu_i + 1)) > DEFAULT_SUPPR_I) {
|
||||||
|
for (k=0; k < RESIDUAL_SUPRESSION_PASSES; k++) {
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I + 2)) + 1);
|
||||||
|
}
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "correcting frame with ec->Ly_i %9d ec->Lu_i %9d expression %d\n", ec->Ly_i, ec->Lu_i, (ec->Ly_i/(ec->Lu_i + 1)));
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_residualcorrected_frames;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
else {
|
||||||
|
++ec->cntr_residualcorrected_framesskipped;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* This will generate a non-linear supression factor, once converted */
|
||||||
|
if ((ec->HCNTR_d == 0) &&
|
||||||
|
((ec->Lu_d/ec->Ly_d) < DEFAULT_SUPPR) &&
|
||||||
|
(ec->Lu_d/ec->Ly_d > EC_MIN_DB_VALUE)) {
|
||||||
|
suppr_factor = (10 / (float)(SUPPR_FLOOR - SUPPR_CEIL)) * log(ec->Lu_d/ec->Ly_d)
|
||||||
|
- SUPPR_CEIL / (float)(SUPPR_FLOOR - SUPPR_CEIL);
|
||||||
|
u_suppr = pow(10.0, (suppr_factor) * RES_SUPR_FACTOR / 10.0) * u_suppr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
/* Periodically dump performance stats */
|
||||||
|
if ((ec->i_d % MEC2_STATS) == 0) {
|
||||||
|
/* make sure to avoid div0's! */
|
||||||
|
if (ec->cntr_coeff_missedupdates > 0)
|
||||||
|
ec->avg_Lu_i_toolow = (int)(ec->avg_Lu_i_toolow / ec->cntr_coeff_missedupdates);
|
||||||
|
else
|
||||||
|
ec->avg_Lu_i_toolow = -1;
|
||||||
|
|
||||||
|
if (ec->cntr_coeff_updates > 0)
|
||||||
|
ec->avg_Lu_i_ok = (ec->avg_Lu_i_ok / ec->cntr_coeff_updates);
|
||||||
|
else
|
||||||
|
ec->avg_Lu_i_ok = -1;
|
||||||
|
|
||||||
|
printk( KERN_INFO "%d: Near end speech: %5d Residuals corrected/skipped: %5d/%5d Coefficients updated ok/low sig: %3d/%3d Lu_i avg ok/low sig %6d/%5d\n",
|
||||||
|
ec->id,
|
||||||
|
ec->cntr_nearend_speech_frames,
|
||||||
|
ec->cntr_residualcorrected_frames, ec->cntr_residualcorrected_framesskipped,
|
||||||
|
ec->cntr_coeff_updates, ec->cntr_coeff_missedupdates,
|
||||||
|
ec->avg_Lu_i_ok, ec->avg_Lu_i_toolow);
|
||||||
|
|
||||||
|
ec->cntr_nearend_speech_frames = 0;
|
||||||
|
ec->cntr_residualcorrected_frames = 0;
|
||||||
|
ec->cntr_residualcorrected_framesskipped = 0;
|
||||||
|
ec->cntr_coeff_updates = 0;
|
||||||
|
ec->cntr_coeff_missedupdates = 0;
|
||||||
|
ec->avg_Lu_i_ok = 0;
|
||||||
|
ec->avg_Lu_i_toolow = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Increment the sample index and return the corrected sample */
|
||||||
|
ec->i_d++;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct echo_can_state *echo_can_create(int len, int adaption_mode)
|
||||||
|
{
|
||||||
|
struct echo_can_state *ec;
|
||||||
|
int maxy;
|
||||||
|
int maxu;
|
||||||
|
maxy = len + DEFAULT_M;
|
||||||
|
maxu = DEFAULT_M;
|
||||||
|
if (maxy < (1 << DEFAULT_ALPHA_YT_I))
|
||||||
|
maxy = (1 << DEFAULT_ALPHA_YT_I);
|
||||||
|
if (maxy < (1 << DEFAULT_SIGMA_LY_I))
|
||||||
|
maxy = (1 << DEFAULT_SIGMA_LY_I);
|
||||||
|
if (maxu < (1 << DEFAULT_SIGMA_LU_I))
|
||||||
|
maxu = (1 << DEFAULT_SIGMA_LU_I);
|
||||||
|
ec = (struct echo_can_state *)MALLOC(sizeof(struct echo_can_state) +
|
||||||
|
4 + /* align */
|
||||||
|
sizeof(int) * len + /* a_i */
|
||||||
|
sizeof(short) * len + /* a_s */
|
||||||
|
2 * sizeof(short) * (maxy) + /* y_s */
|
||||||
|
2 * sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) + /* s_s */
|
||||||
|
2 * sizeof(short) * (maxu) + /* u_s */
|
||||||
|
2 * sizeof(short) * len); /* y_tilde_s */
|
||||||
|
if (ec) {
|
||||||
|
memset(ec, 0, sizeof(struct echo_can_state) +
|
||||||
|
4 + /* align */
|
||||||
|
sizeof(int) * len + /* a_i */
|
||||||
|
sizeof(short) * len + /* a_s */
|
||||||
|
2 * sizeof(short) * (maxy) + /* y_s */
|
||||||
|
2 * sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) + /* s_s */
|
||||||
|
2 * sizeof(short) * (maxu) + /* u_s */
|
||||||
|
2 * sizeof(short) * len); /* y_tilde_s */
|
||||||
|
init_cc(ec, len, maxy, maxu);
|
||||||
|
}
|
||||||
|
return ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int echo_can_traintap(struct echo_can_state *ec, int pos, short val)
|
||||||
|
{
|
||||||
|
/* Set the hangover counter to the length of the can to
|
||||||
|
* avoid adjustments occuring immediately after initial forced training
|
||||||
|
*/
|
||||||
|
ec->HCNTR_d = ec->N_d << 1;
|
||||||
|
|
||||||
|
if (pos >= ec->N_d)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
ec->a_i[pos] = val << 17;
|
||||||
|
ec->a_s[pos] = val << 1;
|
||||||
|
|
||||||
|
if (++pos >= ec->N_d)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
Important constants for tuning kb1 echo can
|
||||||
|
*/
|
||||||
|
#ifndef _MEC2_CONST_H
|
||||||
|
#define _MEC2_CONST_H
|
||||||
|
|
||||||
|
|
||||||
|
/* Convergence (aka. adaptation) speed -- higher means slower */
|
||||||
|
#define DEFAULT_BETA1_I 2048
|
||||||
|
|
||||||
|
/* Constants for various power computations */
|
||||||
|
#define DEFAULT_SIGMA_LY_I 7
|
||||||
|
#define DEFAULT_SIGMA_LU_I 7
|
||||||
|
#define DEFAULT_ALPHA_ST_I 5 /* near-end speech detection sensitivity factor */
|
||||||
|
#define DEFAULT_ALPHA_YT_I 5
|
||||||
|
|
||||||
|
#define DEFAULT_CUTOFF_I 128
|
||||||
|
|
||||||
|
/* Define the near-end speech hangover counter: if near-end speech
|
||||||
|
* is declared, hcntr is set equal to hangt (see pg. 432)
|
||||||
|
*/
|
||||||
|
#define DEFAULT_HANGT 600 /* in samples, so 600 samples = 75ms */
|
||||||
|
|
||||||
|
/* define the residual error suppression threshold */
|
||||||
|
#define DEFAULT_SUPPR_I 16 /* 16 = -24db */
|
||||||
|
|
||||||
|
/* This is the minimum reference signal power estimate level
|
||||||
|
* that will result in filter adaptation.
|
||||||
|
* If this is too low then background noise will cause the filter
|
||||||
|
* coefficients to constantly be updated.
|
||||||
|
*/
|
||||||
|
#define MIN_UPDATE_THRESH_I 4096
|
||||||
|
|
||||||
|
/* The number of samples used to update coefficients using the
|
||||||
|
* the block update method (M). It should be related back to the
|
||||||
|
* length of the echo can.
|
||||||
|
* ie. it only updates coefficients when (sample number MOD default_m) = 0
|
||||||
|
*
|
||||||
|
* Getting this wrong may cause an oops. Consider yourself warned!
|
||||||
|
*/
|
||||||
|
#define DEFAULT_M 16 /* every 16th sample */
|
||||||
|
|
||||||
|
/* If AGGRESSIVE supression is enabled, then we start cancelling residual
|
||||||
|
* echos again even while there is potentially the very end of a near-side
|
||||||
|
* signal present.
|
||||||
|
* This defines how many samples of DEFAULT_HANGT can remain before we
|
||||||
|
* kick back in
|
||||||
|
*/
|
||||||
|
#define AGGRESSIVE_HCNTR 160 /* in samples, so 160 samples = 20ms */
|
||||||
|
|
||||||
|
/* This knob controls the number of passes the residual echo supression
|
||||||
|
* algorithm makes.
|
||||||
|
*/
|
||||||
|
#ifdef AGGRESSIVE_SUPPRESSOR
|
||||||
|
#define RESIDUAL_SUPRESSION_PASSES 2
|
||||||
|
#else
|
||||||
|
#define RESIDUAL_SUPRESSION_PASSES 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/***************************************************************/
|
||||||
|
/* The following knobs are not implemented in the current code */
|
||||||
|
|
||||||
|
/* we need a dynamic level of suppression varying with the ratio of the
|
||||||
|
power of the echo to the power of the reference signal this is
|
||||||
|
done so that we have a smoother background.
|
||||||
|
we have a higher suppression when the power ratio is closer to
|
||||||
|
suppr_ceil and reduces logarithmically as we approach suppr_floor.
|
||||||
|
*/
|
||||||
|
#define SUPPR_FLOOR -64
|
||||||
|
#define SUPPR_CEIL -24
|
||||||
|
|
||||||
|
/* in a second departure, we calculate the residual error suppression
|
||||||
|
* as a percentage of the reference signal energy level. The threshold
|
||||||
|
* is defined in terms of dB below the reference signal.
|
||||||
|
*/
|
||||||
|
#define RES_SUPR_FACTOR -20
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* _MEC2_CONST_H */
|
||||||
|
|
|
@ -0,0 +1,424 @@
|
||||||
|
/*
|
||||||
|
* Mark's Second Echo Canceller
|
||||||
|
*
|
||||||
|
* Copyright (C) 2002, Digium, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software and may be used and
|
||||||
|
* distributed according to the terms of the GNU
|
||||||
|
* General Public License, incorporated herein by
|
||||||
|
* reference.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#ifndef _MARK2_ECHO_H
|
||||||
|
#define _MARK2_ECHO_H
|
||||||
|
|
||||||
|
#define EC_TYPE "MARK2"
|
||||||
|
|
||||||
|
#ifdef __KERNEL__
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#define MALLOC(a) kmalloc((a), GFP_KERNEL)
|
||||||
|
#define FREE(a) kfree(a)
|
||||||
|
#else
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#define MALLOC(a) malloc(a)
|
||||||
|
#define FREE(a) free(a)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Get optimized routines for math */
|
||||||
|
#include "dsp_arith.h"
|
||||||
|
|
||||||
|
#ifndef NULL
|
||||||
|
#define NULL 0
|
||||||
|
#endif
|
||||||
|
#ifndef FALSE
|
||||||
|
#define FALSE 0
|
||||||
|
#endif
|
||||||
|
#ifndef TRUE
|
||||||
|
#define TRUE (!FALSE)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "dsp_mec2_const.h"
|
||||||
|
|
||||||
|
/* Circular buffer definition */
|
||||||
|
typedef struct {
|
||||||
|
int idx_d;
|
||||||
|
int size_d;
|
||||||
|
short *buf_d; /* Twice as large as we need */
|
||||||
|
} echo_can_cb_s;
|
||||||
|
|
||||||
|
// class definition
|
||||||
|
//
|
||||||
|
struct echo_can_state {
|
||||||
|
/* Echo canceller definition */
|
||||||
|
|
||||||
|
/* absolute time */
|
||||||
|
int i_d;
|
||||||
|
|
||||||
|
/* pre-computed constants */
|
||||||
|
|
||||||
|
int N_d;
|
||||||
|
int beta2_i;
|
||||||
|
|
||||||
|
// declare accumulators for power computations
|
||||||
|
//
|
||||||
|
int Ly_i;
|
||||||
|
int Lu_i;
|
||||||
|
|
||||||
|
// declare an accumulator for the near-end signal detector
|
||||||
|
//
|
||||||
|
int s_tilde_i;
|
||||||
|
int HCNTR_d;
|
||||||
|
|
||||||
|
// circular buffers and coefficients
|
||||||
|
//
|
||||||
|
int *a_i;
|
||||||
|
short *a_s;
|
||||||
|
echo_can_cb_s y_s;
|
||||||
|
echo_can_cb_s s_s;
|
||||||
|
echo_can_cb_s u_s;
|
||||||
|
echo_can_cb_s y_tilde_s;
|
||||||
|
int y_tilde_i;
|
||||||
|
|
||||||
|
/* Max memory */
|
||||||
|
short max_y_tilde;
|
||||||
|
int max_y_tilde_pos;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void init_cb_s(echo_can_cb_s *cb, int len, void *where)
|
||||||
|
{
|
||||||
|
cb->buf_d = (short *)where;
|
||||||
|
cb->idx_d = 0;
|
||||||
|
cb->size_d = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void add_cc_s(echo_can_cb_s *cb, short newval)
|
||||||
|
{
|
||||||
|
/* Can't use modulus because N+M isn't a power of two (generally) */
|
||||||
|
cb->idx_d--;
|
||||||
|
if (cb->idx_d < (int)0)
|
||||||
|
{cb->idx_d += cb->size_d;}
|
||||||
|
/* Load two copies into memory */
|
||||||
|
cb->buf_d[cb->idx_d] = newval;
|
||||||
|
cb->buf_d[cb->idx_d + cb->size_d] = newval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline short get_cc_s(echo_can_cb_s *cb, int pos)
|
||||||
|
{
|
||||||
|
/* Load two copies into memory */
|
||||||
|
return cb->buf_d[cb->idx_d + pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void init_cc(struct echo_can_state *ec, int N, int maxy, int maxu) {
|
||||||
|
|
||||||
|
void *ptr = ec;
|
||||||
|
unsigned long tmp;
|
||||||
|
/* double-word align past end of state */
|
||||||
|
ptr += sizeof(struct echo_can_state);
|
||||||
|
tmp = (unsigned long)ptr;
|
||||||
|
tmp += 3;
|
||||||
|
tmp &= ~3L;
|
||||||
|
ptr = (void *)tmp;
|
||||||
|
|
||||||
|
// reset parameters
|
||||||
|
//
|
||||||
|
ec->N_d = N;
|
||||||
|
ec->beta2_i = DEFAULT_BETA1_I;
|
||||||
|
|
||||||
|
// allocate coefficient memory
|
||||||
|
//
|
||||||
|
ec->a_i = ptr;
|
||||||
|
ptr += (sizeof(int) * ec->N_d);
|
||||||
|
ec->a_s = ptr;
|
||||||
|
ptr += (sizeof(short) * ec->N_d);
|
||||||
|
|
||||||
|
/* Reset Y circular buffer (short version) */
|
||||||
|
init_cb_s(&ec->y_s, maxy, ptr);
|
||||||
|
ptr += (sizeof(short) * (maxy) * 2);
|
||||||
|
|
||||||
|
/* Reset Sig circular buffer (short version for FIR filter) */
|
||||||
|
init_cb_s(&ec->s_s, (1 << DEFAULT_ALPHA_ST_I), ptr);
|
||||||
|
ptr += (sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) * 2);
|
||||||
|
|
||||||
|
init_cb_s(&ec->u_s, maxu, ptr);
|
||||||
|
ptr += (sizeof(short) * maxu * 2);
|
||||||
|
|
||||||
|
// allocate a buffer for the reference signal power computation
|
||||||
|
//
|
||||||
|
init_cb_s(&ec->y_tilde_s, ec->N_d, ptr);
|
||||||
|
|
||||||
|
|
||||||
|
// reset absolute time
|
||||||
|
//
|
||||||
|
ec->i_d = (int)0;
|
||||||
|
|
||||||
|
// reset the power computations (for y and u)
|
||||||
|
//
|
||||||
|
ec->Ly_i = DEFAULT_CUTOFF_I;
|
||||||
|
ec->Lu_i = DEFAULT_CUTOFF_I;
|
||||||
|
|
||||||
|
// reset the near-end speech detector
|
||||||
|
//
|
||||||
|
ec->s_tilde_i = 0;
|
||||||
|
ec->y_tilde_i = 0;
|
||||||
|
ec->HCNTR_d = (int)0;
|
||||||
|
|
||||||
|
// exit gracefully
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void echo_can_free(struct echo_can_state *ec)
|
||||||
|
{
|
||||||
|
FREE(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline short echo_can_update(struct echo_can_state *ec, short iref, short isig) {
|
||||||
|
|
||||||
|
/* declare local variables that are used more than once
|
||||||
|
*/
|
||||||
|
int k;
|
||||||
|
int rs;
|
||||||
|
short u;
|
||||||
|
int Py_i;
|
||||||
|
int two_beta_i;
|
||||||
|
|
||||||
|
/***************************************************************************
|
||||||
|
//
|
||||||
|
// flow A on pg. 428
|
||||||
|
//
|
||||||
|
***************************************************************************/
|
||||||
|
|
||||||
|
/* eq. (16): high-pass filter the input to generate the next value;
|
||||||
|
// push the current value into the circular buffer
|
||||||
|
//
|
||||||
|
// sdc_im1_d = sdc_d;
|
||||||
|
// sdc_d = sig;
|
||||||
|
// s_i_d = sdc_d;
|
||||||
|
// s_d = s_i_d;
|
||||||
|
// s_i_d = (float)(1.0 - gamma_d) * s_i_d
|
||||||
|
+ (float)(0.5 * (1.0 - gamma_d)) * (sdc_d - sdc_im1_d); */
|
||||||
|
|
||||||
|
|
||||||
|
/* Delete last sample from power estimate */
|
||||||
|
ec->y_tilde_i -= abs(get_cc_s(&ec->y_s, (1 << DEFAULT_ALPHA_YT_I) - 1 )) >> DEFAULT_ALPHA_YT_I;
|
||||||
|
/* push the reference data onto the circular buffer */
|
||||||
|
add_cc_s(&ec->y_s, iref);
|
||||||
|
|
||||||
|
/* eq. (2): compute r in fixed-point */
|
||||||
|
rs = CONVOLVE2(ec->a_s, ec->y_s.buf_d + ec->y_s.idx_d, ec->N_d);
|
||||||
|
rs >>= 15;
|
||||||
|
|
||||||
|
/* eq. (3): compute the output value (see figure 3) and the error
|
||||||
|
// note: the error is the same as the output signal when near-end
|
||||||
|
// speech is not present
|
||||||
|
*/
|
||||||
|
u = isig - rs;
|
||||||
|
|
||||||
|
add_cc_s(&ec->u_s, u);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Delete oldest part of received s_tilde */
|
||||||
|
ec->s_tilde_i -= abs(get_cc_s(&ec->s_s, (1 << DEFAULT_ALPHA_ST_I) - 1 ));
|
||||||
|
|
||||||
|
/* push the signal on the circular buffer, too */
|
||||||
|
add_cc_s(&ec->s_s, isig);
|
||||||
|
ec->s_tilde_i += abs(isig);
|
||||||
|
ec->y_tilde_i += abs(iref) >> DEFAULT_ALPHA_YT_I;
|
||||||
|
|
||||||
|
/* Add to our list of recent y_tilde's */
|
||||||
|
add_cc_s(&ec->y_tilde_s, ec->y_tilde_i);
|
||||||
|
|
||||||
|
/****************************************************************************
|
||||||
|
//
|
||||||
|
// flow B on pg. 428
|
||||||
|
//
|
||||||
|
****************************************************************************/
|
||||||
|
|
||||||
|
/* compute the new convergence factor
|
||||||
|
*/
|
||||||
|
if (!ec->HCNTR_d) {
|
||||||
|
Py_i = (ec->Ly_i >> DEFAULT_SIGMA_LY_I) * (ec->Ly_i >> DEFAULT_SIGMA_LY_I);
|
||||||
|
Py_i >>= 15;
|
||||||
|
} else {
|
||||||
|
Py_i = (1 << 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
printf("Py: %e, Py_i: %e\n", Py, Py_i * AMPL_SCALE_1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Vary rate of adaptation depending on position in the file
|
||||||
|
// Do not do this for the first (DEFAULT_UPDATE_TIME) secs after speech
|
||||||
|
// has begun of the file to allow the echo cancellor to estimate the
|
||||||
|
// channel accurately
|
||||||
|
*/
|
||||||
|
#if 0
|
||||||
|
if (ec->start_speech_d != 0 ){
|
||||||
|
if ( ec->i_d > (DEFAULT_T0 + ec->start_speech_d)*(SAMPLE_FREQ) ){
|
||||||
|
ec->beta2_d = max_cc_float(MIN_BETA,
|
||||||
|
DEFAULT_BETA1 * exp((-1/DEFAULT_TAU)*((ec->i_d/(float)SAMPLE_FREQ) -
|
||||||
|
DEFAULT_T0 -
|
||||||
|
ec->start_speech_d)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {ec->beta2_d = DEFAULT_BETA1;}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ec->beta2_i = DEFAULT_BETA1_I; /* Fixed point, inverted */
|
||||||
|
|
||||||
|
two_beta_i = (ec->beta2_i * Py_i) >> 15; /* Fixed point version, inverted */
|
||||||
|
if (!two_beta_i)
|
||||||
|
two_beta_i++;
|
||||||
|
|
||||||
|
/* Update Lu_i (Suppressed power estimate) */
|
||||||
|
ec->Lu_i -= abs(get_cc_s(&ec->u_s, (1 << DEFAULT_SIGMA_LU_I) - 1 )) ;
|
||||||
|
ec->Lu_i += abs(u);
|
||||||
|
|
||||||
|
/* eq. (10): update power estimate of the reference
|
||||||
|
*/
|
||||||
|
ec->Ly_i -= abs(get_cc_s(&ec->y_s, (1 << DEFAULT_SIGMA_LY_I) - 1)) ;
|
||||||
|
ec->Ly_i += abs(iref);
|
||||||
|
|
||||||
|
if (ec->Ly_i < DEFAULT_CUTOFF_I)
|
||||||
|
ec->Ly_i = DEFAULT_CUTOFF_I;
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
printf("Float: %e, Int: %e\n", ec->Ly_d, (ec->Ly_i >> DEFAULT_SIGMA_LY_I) * AMPL_SCALE_1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (ec->y_tilde_i > ec->max_y_tilde) {
|
||||||
|
/* New highest y_tilde with full life */
|
||||||
|
ec->max_y_tilde = ec->y_tilde_i;
|
||||||
|
ec->max_y_tilde_pos = ec->N_d - 1;
|
||||||
|
} else if (--ec->max_y_tilde_pos < 0) {
|
||||||
|
/* Time to find new max y tilde... */
|
||||||
|
ec->max_y_tilde = MAX16(ec->y_tilde_s.buf_d + ec->y_tilde_s.idx_d, ec->N_d, &ec->max_y_tilde_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((ec->s_tilde_i >> (DEFAULT_ALPHA_ST_I - 1)) > ec->max_y_tilde)
|
||||||
|
{
|
||||||
|
ec->HCNTR_d = DEFAULT_HANGT;
|
||||||
|
}
|
||||||
|
else if (ec->HCNTR_d > (int)0)
|
||||||
|
{
|
||||||
|
ec->HCNTR_d--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* update coefficients if no near-end speech and we have enough signal
|
||||||
|
* to bother trying to update.
|
||||||
|
*/
|
||||||
|
if (!ec->HCNTR_d && !(ec->i_d % DEFAULT_M) &&
|
||||||
|
(ec->Lu_i > MIN_UPDATE_THRESH_I)) {
|
||||||
|
// loop over all filter coefficients
|
||||||
|
//
|
||||||
|
for (k=0; k<ec->N_d; k++) {
|
||||||
|
|
||||||
|
// eq. (7): compute an expectation over M_d samples
|
||||||
|
//
|
||||||
|
int grad2;
|
||||||
|
grad2 = CONVOLVE2(ec->u_s.buf_d + ec->u_s.idx_d,
|
||||||
|
ec->y_s.buf_d + ec->y_s.idx_d + k, DEFAULT_M);
|
||||||
|
// eq. (7): update the coefficient
|
||||||
|
//
|
||||||
|
ec->a_i[k] += grad2 / two_beta_i;
|
||||||
|
ec->a_s[k] = ec->a_i[k] >> 16;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* paragraph below eq. (15): if no near-end speech,
|
||||||
|
// check for residual error suppression
|
||||||
|
*/
|
||||||
|
#ifndef NO_ECHO_SUPPRESSOR
|
||||||
|
#ifdef AGGRESSIVE_SUPPRESSOR
|
||||||
|
#ifdef AGGRESSIVE_TIMELIMIT /* This allows the aggressive suppressor to turn off after set amount of time */
|
||||||
|
if (ec->i_d > AGGRESSIVE_TIMELIMIT ) {
|
||||||
|
if ((ec->HCNTR_d == 0) && ((ec->Ly_i/(ec->Lu_i + 1)) > DEFAULT_SUPPR_I)) {
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I + 2)) + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
#endif
|
||||||
|
if ((ec->HCNTR_d < AGGRESSIVE_HCNTR) && (ec->Ly_i > (ec->Lu_i << 1))) {
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I)) + 1);
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I)) + 1);
|
||||||
|
}
|
||||||
|
#ifdef AGGRESSIVE_TIMELIMIT
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
if ((ec->HCNTR_d == 0) && ((ec->Ly_i/(ec->Lu_i + 1)) > DEFAULT_SUPPR_I)) {
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I + 2)) + 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
if ((ec->HCNTR_d == 0) && ((ec->Lu_d/ec->Ly_d) < DEFAULT_SUPPR) &&
|
||||||
|
(ec->Lu_d/ec->Ly_d > EC_MIN_DB_VALUE)) {
|
||||||
|
suppr_factor = (10/(float)(SUPPR_FLOOR-SUPPR_CEIL))*log(ec->Lu_d/ec->Ly_d)
|
||||||
|
- SUPPR_CEIL/(float)(SUPPR_FLOOR - SUPPR_CEIL);
|
||||||
|
|
||||||
|
u_suppr = pow(10.0,(suppr_factor)*RES_SUPR_FACTOR/10.0)*u_suppr;
|
||||||
|
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ec->i_d++;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct echo_can_state *echo_can_create(int len, int adaption_mode)
|
||||||
|
{
|
||||||
|
struct echo_can_state *ec;
|
||||||
|
int maxy;
|
||||||
|
int maxu;
|
||||||
|
maxy = len + DEFAULT_M;
|
||||||
|
maxu = DEFAULT_M;
|
||||||
|
if (maxy < (1 << DEFAULT_ALPHA_YT_I))
|
||||||
|
maxy = (1 << DEFAULT_ALPHA_YT_I);
|
||||||
|
if (maxy < (1 << DEFAULT_SIGMA_LY_I))
|
||||||
|
maxy = (1 << DEFAULT_SIGMA_LY_I);
|
||||||
|
if (maxu < (1 << DEFAULT_SIGMA_LU_I))
|
||||||
|
maxu = (1 << DEFAULT_SIGMA_LU_I);
|
||||||
|
ec = (struct echo_can_state *)MALLOC(sizeof(struct echo_can_state) +
|
||||||
|
4 + /* align */
|
||||||
|
sizeof(int) * len + /* a_i */
|
||||||
|
sizeof(short) * len + /* a_s */
|
||||||
|
2 * sizeof(short) * (maxy) + /* y_s */
|
||||||
|
2 * sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) + /* s_s */
|
||||||
|
2 * sizeof(short) * (maxu) + /* u_s */
|
||||||
|
2 * sizeof(short) * len); /* y_tilde_s */
|
||||||
|
if (ec) {
|
||||||
|
memset(ec, 0, sizeof(struct echo_can_state) +
|
||||||
|
4 + /* align */
|
||||||
|
sizeof(int) * len + /* a_i */
|
||||||
|
sizeof(short) * len + /* a_s */
|
||||||
|
2 * sizeof(short) * (maxy) + /* y_s */
|
||||||
|
2 * sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) + /* s_s */
|
||||||
|
2 * sizeof(short) * (maxu) + /* u_s */
|
||||||
|
2 * sizeof(short) * len); /* y_tilde_s */
|
||||||
|
init_cc(ec, len, maxy, maxu);
|
||||||
|
}
|
||||||
|
return ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int echo_can_traintap(struct echo_can_state *ec, int pos, short val)
|
||||||
|
{
|
||||||
|
/* Reset hang counter to avoid adjustments after
|
||||||
|
initial forced training */
|
||||||
|
ec->HCNTR_d = ec->N_d << 1;
|
||||||
|
if (pos >= ec->N_d)
|
||||||
|
return 1;
|
||||||
|
ec->a_i[pos] = val << 17;
|
||||||
|
ec->a_s[pos] = val << 1;
|
||||||
|
if (++pos >= ec->N_d)
|
||||||
|
return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
Important constants for tuning mec2 echo can
|
||||||
|
*/
|
||||||
|
#ifndef _MEC2_CONST_H
|
||||||
|
#define _MEC2_CONST_H
|
||||||
|
|
||||||
|
|
||||||
|
/* Convergence speed -- higher means slower */
|
||||||
|
#define DEFAULT_BETA1_I 2048
|
||||||
|
#define DEFAULT_SIGMA_LY_I 7
|
||||||
|
#define DEFAULT_SIGMA_LU_I 7
|
||||||
|
#define DEFAULT_ALPHA_ST_I 5
|
||||||
|
#define DEFAULT_ALPHA_YT_I 5
|
||||||
|
#define DEFAULT_CUTOFF_I 128
|
||||||
|
#define DEFAULT_HANGT 600
|
||||||
|
#define DEFAULT_SUPPR_I 16
|
||||||
|
#define MIN_UPDATE_THRESH_I 4096
|
||||||
|
#define DEFAULT_M 16
|
||||||
|
#define SUPPR_FLOOR -64
|
||||||
|
#define SUPPR_CEIL -24
|
||||||
|
#define RES_SUPR_FACTOR -20
|
||||||
|
#define AGGRESSIVE_HCNTR 160 /* 20ms */
|
||||||
|
|
||||||
|
/* Only use agressive echo cancellation for this amount of time then go back to normal cancelation */
|
||||||
|
/* #define AGGRESSIVE_TIMELIMIT 150000 */ /* 8 = 1ms */
|
||||||
|
|
||||||
|
#endif /* _MEC2_CONST_H */
|
||||||
|
|
|
@ -0,0 +1,682 @@
|
||||||
|
/*
|
||||||
|
* ECHO_CAN_MG2
|
||||||
|
*
|
||||||
|
* by Michael Gernoth
|
||||||
|
*
|
||||||
|
* Based upon kb1ec.h and mec2.h
|
||||||
|
*
|
||||||
|
* Copyright (C) 2002, Digium, Inc.
|
||||||
|
*
|
||||||
|
* This program is free software and may be used and
|
||||||
|
* distributed according to the terms of the GNU
|
||||||
|
* General Public License, incorporated herein by
|
||||||
|
* reference.
|
||||||
|
*
|
||||||
|
* Additional background on the techniques used in this code can be found in:
|
||||||
|
*
|
||||||
|
* Messerschmitt, David; Hedberg, David; Cole, Christopher; Haoui, Amine;
|
||||||
|
* Winship, Peter; "Digital Voice Echo Canceller with a TMS32020,"
|
||||||
|
* in Digital Signal Processing Applications with the TMS320 Family,
|
||||||
|
* pp. 415-437, Texas Instruments, Inc., 1986.
|
||||||
|
*
|
||||||
|
* A pdf of which is available by searching on the document title at http://www.ti.com/
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _MG2_ECHO_H
|
||||||
|
#define _MG2_ECHO_H
|
||||||
|
|
||||||
|
|
||||||
|
#define EC_TYPE "MG2"
|
||||||
|
|
||||||
|
#ifdef __KERNEL__
|
||||||
|
#include <linux/kernel.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#define MALLOC(a) kmalloc((a), GFP_KERNEL)
|
||||||
|
#define FREE(a) kfree(a)
|
||||||
|
#else
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#define MALLOC(a) malloc(a)
|
||||||
|
#define FREE(a) free(a)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define ABS(a) abs(a!=-32768?a:-32767)
|
||||||
|
|
||||||
|
#define RESTORE_COEFFS {\
|
||||||
|
int x;\
|
||||||
|
memcpy(ec->a_i, ec->c_i, ec->N_d*sizeof(int));\
|
||||||
|
for (x=0;x<ec->N_d;x++) {\
|
||||||
|
ec->a_s[x] = ec->a_i[x] >> 16;\
|
||||||
|
}\
|
||||||
|
ec->backup = BACKUP;\
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Uncomment to provide summary statistics for overall echo can performance every 4000 samples */
|
||||||
|
/* #define MEC2_STATS 4000 */
|
||||||
|
|
||||||
|
/* Uncomment to generate per-sample statistics - this will severely degrade system performance and audio quality */
|
||||||
|
/* #define MEC2_STATS_DETAILED */
|
||||||
|
|
||||||
|
/* Get optimized routines for math */
|
||||||
|
#include "dsp_arith.h"
|
||||||
|
|
||||||
|
/* Bring in definitions for the various constants and thresholds */
|
||||||
|
#include "dsp_mg2ec_const.h"
|
||||||
|
|
||||||
|
#ifndef NULL
|
||||||
|
#define NULL 0
|
||||||
|
#endif
|
||||||
|
#ifndef FALSE
|
||||||
|
#define FALSE 0
|
||||||
|
#endif
|
||||||
|
#ifndef TRUE
|
||||||
|
#define TRUE (!FALSE)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Generic circular buffer definition */
|
||||||
|
typedef struct {
|
||||||
|
/* Pointer to the relative 'start' of the buffer */
|
||||||
|
int idx_d;
|
||||||
|
/* The absolute size of the buffer */
|
||||||
|
int size_d;
|
||||||
|
/* The actual sample - twice as large as we need, however we do store values at idx_d and idx_d+size_d */
|
||||||
|
short *buf_d;
|
||||||
|
} echo_can_cb_s;
|
||||||
|
|
||||||
|
/* Echo canceller definition */
|
||||||
|
struct echo_can_state {
|
||||||
|
/* an arbitrary ID for this echo can - this really should be settable from the calling channel... */
|
||||||
|
int id;
|
||||||
|
|
||||||
|
/* absolute time - aka. sample number index - essentially the number of samples since this can was init'ed */
|
||||||
|
int i_d;
|
||||||
|
|
||||||
|
/* Pre-computed constants */
|
||||||
|
/* ---------------------- */
|
||||||
|
/* Number of filter coefficents */
|
||||||
|
int N_d;
|
||||||
|
/* Rate of adaptation of filter */
|
||||||
|
int beta2_i;
|
||||||
|
|
||||||
|
/* Accumulators for power computations */
|
||||||
|
/* ----------------------------------- */
|
||||||
|
/* reference signal power estimate - aka. Average absolute value of y(k) */
|
||||||
|
int Ly_i;
|
||||||
|
/* ... */
|
||||||
|
int Lu_i;
|
||||||
|
|
||||||
|
/* Accumulators for signal detectors */
|
||||||
|
/* --------------------------------- */
|
||||||
|
/* Power estimate of the recent past of the near-end hybrid signal - aka. Short-time average of: 2 x |s(i)| */
|
||||||
|
int s_tilde_i;
|
||||||
|
/* Power estimate of the recent past of the far-end receive signal - aka. Short-time average of: |y(i)| */
|
||||||
|
int y_tilde_i;
|
||||||
|
|
||||||
|
/* Near end speech detection counter - stores Hangover counter time remaining, in samples */
|
||||||
|
int HCNTR_d;
|
||||||
|
|
||||||
|
/* Circular buffers and coefficients */
|
||||||
|
/* --------------------------------- */
|
||||||
|
/* ... */
|
||||||
|
int *a_i;
|
||||||
|
/* ... */
|
||||||
|
short *a_s;
|
||||||
|
/* Backups */
|
||||||
|
int *b_i;
|
||||||
|
int *c_i;
|
||||||
|
/* Reference samples of far-end receive signal */
|
||||||
|
echo_can_cb_s y_s;
|
||||||
|
/* Reference samples of near-end signal */
|
||||||
|
echo_can_cb_s s_s;
|
||||||
|
/* Reference samples of near-end signal minus echo estimate */
|
||||||
|
echo_can_cb_s u_s;
|
||||||
|
/* Reference samples of far-end receive signal used to calculate short-time average */
|
||||||
|
echo_can_cb_s y_tilde_s;
|
||||||
|
|
||||||
|
/* Peak far-end receive signal */
|
||||||
|
/* --------------------------- */
|
||||||
|
/* Highest y_tilde value in the sample buffer */
|
||||||
|
short max_y_tilde;
|
||||||
|
/* Index of the sample containing the max_y_tilde value */
|
||||||
|
int max_y_tilde_pos;
|
||||||
|
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
/* Storage for performance statistics */
|
||||||
|
int cntr_nearend_speech_frames;
|
||||||
|
int cntr_residualcorrected_frames;
|
||||||
|
int cntr_residualcorrected_framesskipped;
|
||||||
|
int cntr_coeff_updates;
|
||||||
|
int cntr_coeff_missedupdates;
|
||||||
|
|
||||||
|
int avg_Lu_i_toolow;
|
||||||
|
int avg_Lu_i_ok;
|
||||||
|
#endif
|
||||||
|
short lastsig[256];
|
||||||
|
int lastpos;
|
||||||
|
int backup;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
static inline void init_cb_s(echo_can_cb_s *cb, int len, void *where)
|
||||||
|
{
|
||||||
|
cb->buf_d = (short *)where;
|
||||||
|
cb->idx_d = 0;
|
||||||
|
cb->size_d = len;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void add_cc_s(echo_can_cb_s *cb, short newval)
|
||||||
|
{
|
||||||
|
/* Can't use modulus because N+M isn't a power of two (generally) */
|
||||||
|
cb->idx_d--;
|
||||||
|
if (cb->idx_d < (int)0)
|
||||||
|
/* Whoops - the pointer to the 'start' wrapped around so reset it to the top of the buffer */
|
||||||
|
cb->idx_d += cb->size_d;
|
||||||
|
|
||||||
|
/* Load two copies into memory */
|
||||||
|
cb->buf_d[cb->idx_d] = newval;
|
||||||
|
cb->buf_d[cb->idx_d + cb->size_d] = newval;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline short get_cc_s(echo_can_cb_s *cb, int pos)
|
||||||
|
{
|
||||||
|
/* Load two copies into memory */
|
||||||
|
return cb->buf_d[cb->idx_d + pos];
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void init_cc(struct echo_can_state *ec, int N, int maxy, int maxu)
|
||||||
|
{
|
||||||
|
|
||||||
|
void *ptr = ec;
|
||||||
|
unsigned long tmp;
|
||||||
|
/* Double-word align past end of state */
|
||||||
|
ptr += sizeof(struct echo_can_state);
|
||||||
|
tmp = (unsigned long)ptr;
|
||||||
|
tmp += 3;
|
||||||
|
tmp &= ~3L;
|
||||||
|
ptr = (void *)tmp;
|
||||||
|
|
||||||
|
/* Reset parameters */
|
||||||
|
ec->N_d = N;
|
||||||
|
ec->beta2_i = DEFAULT_BETA1_I;
|
||||||
|
|
||||||
|
/* Allocate coefficient memory */
|
||||||
|
ec->a_i = ptr;
|
||||||
|
ptr += (sizeof(int) * ec->N_d);
|
||||||
|
ec->a_s = ptr;
|
||||||
|
ptr += (sizeof(short) * ec->N_d);
|
||||||
|
|
||||||
|
/* Allocate backup memory */
|
||||||
|
ec->b_i = ptr;
|
||||||
|
ptr += (sizeof(int) * ec->N_d);
|
||||||
|
ec->c_i = ptr;
|
||||||
|
ptr += (sizeof(int) * ec->N_d);
|
||||||
|
|
||||||
|
/* Reset Y circular buffer (short version) */
|
||||||
|
init_cb_s(&ec->y_s, maxy, ptr);
|
||||||
|
ptr += (sizeof(short) * (maxy) * 2);
|
||||||
|
|
||||||
|
/* Reset Sigma circular buffer (short version for FIR filter) */
|
||||||
|
init_cb_s(&ec->s_s, (1 << DEFAULT_ALPHA_ST_I), ptr);
|
||||||
|
ptr += (sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) * 2);
|
||||||
|
|
||||||
|
init_cb_s(&ec->u_s, maxu, ptr);
|
||||||
|
ptr += (sizeof(short) * maxu * 2);
|
||||||
|
|
||||||
|
/* Allocate a buffer for the reference signal power computation */
|
||||||
|
init_cb_s(&ec->y_tilde_s, ec->N_d, ptr);
|
||||||
|
|
||||||
|
/* Reset the absolute time index */
|
||||||
|
ec->i_d = (int)0;
|
||||||
|
|
||||||
|
/* Reset the power computations (for y and u) */
|
||||||
|
ec->Ly_i = DEFAULT_CUTOFF_I;
|
||||||
|
ec->Lu_i = DEFAULT_CUTOFF_I;
|
||||||
|
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
/* set the identity */
|
||||||
|
ec->id = (int)&ptr;
|
||||||
|
|
||||||
|
/* Reset performance stats */
|
||||||
|
ec->cntr_nearend_speech_frames = (int)0;
|
||||||
|
ec->cntr_residualcorrected_frames = (int)0;
|
||||||
|
ec->cntr_residualcorrected_framesskipped = (int)0;
|
||||||
|
ec->cntr_coeff_updates = (int)0;
|
||||||
|
ec->cntr_coeff_missedupdates = (int)0;
|
||||||
|
|
||||||
|
ec->avg_Lu_i_toolow = (int)0;
|
||||||
|
ec->avg_Lu_i_ok = (int)0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Reset the near-end speech detector */
|
||||||
|
ec->s_tilde_i = (int)0;
|
||||||
|
ec->y_tilde_i = (int)0;
|
||||||
|
ec->HCNTR_d = (int)0;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void echo_can_free(struct echo_can_state *ec)
|
||||||
|
{
|
||||||
|
FREE(ec);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline short echo_can_update(struct echo_can_state *ec, short iref, short isig)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* Declare local variables that are used more than once */
|
||||||
|
/* ... */
|
||||||
|
int k;
|
||||||
|
/* ... */
|
||||||
|
int rs;
|
||||||
|
/* ... */
|
||||||
|
short u;
|
||||||
|
/* ... */
|
||||||
|
int Py_i;
|
||||||
|
/* ... */
|
||||||
|
int two_beta_i;
|
||||||
|
|
||||||
|
/* flow A on pg. 428 */
|
||||||
|
/* eq. (16): high-pass filter the input to generate the next value;
|
||||||
|
* push the current value into the circular buffer
|
||||||
|
*
|
||||||
|
* sdc_im1_d = sdc_d;
|
||||||
|
* sdc_d = sig;
|
||||||
|
* s_i_d = sdc_d;
|
||||||
|
* s_d = s_i_d;
|
||||||
|
* s_i_d = (float)(1.0 - gamma_d) * s_i_d
|
||||||
|
* + (float)(0.5 * (1.0 - gamma_d)) * (sdc_d - sdc_im1_d);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Update the Far-end receive signal circular buffers and accumulators */
|
||||||
|
/* ------------------------------------------------------------------- */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->y_tilde_i -= abs(get_cc_s(&ec->y_s, (1 << DEFAULT_ALPHA_YT_I) - 1 )) >> DEFAULT_ALPHA_YT_I;
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->y_tilde_i += abs(iref) >> DEFAULT_ALPHA_ST_I;
|
||||||
|
/* Push a copy of the new sample into its circular buffer */
|
||||||
|
add_cc_s(&ec->y_s, iref);
|
||||||
|
|
||||||
|
|
||||||
|
/* eq. (2): compute r in fixed-point */
|
||||||
|
rs = CONVOLVE2(ec->a_s,
|
||||||
|
ec->y_s.buf_d + ec->y_s.idx_d,
|
||||||
|
ec->N_d);
|
||||||
|
rs >>= 15;
|
||||||
|
|
||||||
|
ec->lastsig[ec->lastpos++] = isig;
|
||||||
|
if (ec->lastpos >= 256)
|
||||||
|
ec->lastpos = 0;
|
||||||
|
|
||||||
|
for (k=0; k < 256; k++) {
|
||||||
|
if (isig != ec->lastsig[k])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isig == 0) {
|
||||||
|
u = 0;
|
||||||
|
} else if (k == 256) {
|
||||||
|
u = isig;
|
||||||
|
} else {
|
||||||
|
if (rs < -32768) {
|
||||||
|
rs = -32768;
|
||||||
|
ec->HCNTR_d = DEFAULT_HANGT;
|
||||||
|
RESTORE_COEFFS;
|
||||||
|
} else if (rs > 32767) {
|
||||||
|
rs = 32767;
|
||||||
|
ec->HCNTR_d = DEFAULT_HANGT;
|
||||||
|
RESTORE_COEFFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ABS(ABS(rs)-ABS(isig)) > MAX_SIGN_ERROR)
|
||||||
|
{
|
||||||
|
rs = 0;
|
||||||
|
RESTORE_COEFFS;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* eq. (3): compute the output value (see figure 3) and the error
|
||||||
|
* note: the error is the same as the output signal when near-end
|
||||||
|
* speech is not present
|
||||||
|
*/
|
||||||
|
u = isig - rs;
|
||||||
|
|
||||||
|
if (u / isig < 0)
|
||||||
|
u = isig - (rs >> 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Push a copy of the output value sample into its circular buffer */
|
||||||
|
add_cc_s(&ec->u_s, u);
|
||||||
|
|
||||||
|
if (!ec->backup) {
|
||||||
|
/* Backup coefficients periodically */
|
||||||
|
ec->backup = BACKUP;
|
||||||
|
memcpy(ec->c_i,ec->b_i,ec->N_d*sizeof(int));
|
||||||
|
memcpy(ec->b_i,ec->a_i,ec->N_d*sizeof(int));
|
||||||
|
} else
|
||||||
|
ec->backup--;
|
||||||
|
|
||||||
|
|
||||||
|
/* Update the Near-end hybrid signal circular buffers and accumulators */
|
||||||
|
/* ------------------------------------------------------------------- */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->s_tilde_i -= abs(get_cc_s(&ec->s_s, (1 << DEFAULT_ALPHA_ST_I) - 1 ));
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->s_tilde_i += abs(isig);
|
||||||
|
/* Push a copy of the new sample into it's circular buffer */
|
||||||
|
add_cc_s(&ec->s_s, isig);
|
||||||
|
|
||||||
|
|
||||||
|
/* Push a copy of the current short-time average of the far-end receive signal into it's circular buffer */
|
||||||
|
add_cc_s(&ec->y_tilde_s, ec->y_tilde_i);
|
||||||
|
|
||||||
|
/* flow B on pg. 428 */
|
||||||
|
|
||||||
|
/* If the hangover timer isn't running then compute the new convergence factor, otherwise set Py_i to 32768 */
|
||||||
|
if (!ec->HCNTR_d) {
|
||||||
|
Py_i = (ec->Ly_i >> DEFAULT_SIGMA_LY_I) * (ec->Ly_i >> DEFAULT_SIGMA_LY_I);
|
||||||
|
Py_i >>= 15;
|
||||||
|
} else {
|
||||||
|
Py_i = (1 << 15);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* Vary rate of adaptation depending on position in the file
|
||||||
|
* Do not do this for the first (DEFAULT_UPDATE_TIME) secs after speech
|
||||||
|
* has begun of the file to allow the echo cancellor to estimate the
|
||||||
|
* channel accurately
|
||||||
|
* Still needs conversion!
|
||||||
|
*/
|
||||||
|
|
||||||
|
if (ec->start_speech_d != 0 ){
|
||||||
|
if ( ec->i_d > (DEFAULT_T0 + ec->start_speech_d)*(SAMPLE_FREQ) ){
|
||||||
|
ec->beta2_d = max_cc_float(MIN_BETA, DEFAULT_BETA1 * exp((-1/DEFAULT_TAU)*((ec->i_d/(float)SAMPLE_FREQ) - DEFAULT_T0 - ec->start_speech_d)));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ec->beta2_d = DEFAULT_BETA1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Fixed point, inverted */
|
||||||
|
ec->beta2_i = DEFAULT_BETA1_I;
|
||||||
|
|
||||||
|
/* Fixed point version, inverted */
|
||||||
|
two_beta_i = (ec->beta2_i * Py_i) >> 15;
|
||||||
|
if (!two_beta_i)
|
||||||
|
two_beta_i++;
|
||||||
|
|
||||||
|
/* Update the Suppressed signal power estimate accumulator */
|
||||||
|
/* ------------------------------------------------------- */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->Lu_i -= abs(get_cc_s(&ec->u_s, (1 << DEFAULT_SIGMA_LU_I) - 1 )) ;
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->Lu_i += abs(u);
|
||||||
|
|
||||||
|
/* Update the Far-end reference signal power estimate accumulator */
|
||||||
|
/* -------------------------------------------------------------- */
|
||||||
|
/* eq. (10): update power estimate of the reference */
|
||||||
|
/* Delete the oldest sample from the power estimate accumulator */
|
||||||
|
ec->Ly_i -= abs(get_cc_s(&ec->y_s, (1 << DEFAULT_SIGMA_LY_I) - 1)) ;
|
||||||
|
/* Add the new sample to the power estimate accumulator */
|
||||||
|
ec->Ly_i += abs(iref);
|
||||||
|
|
||||||
|
if (ec->Ly_i < DEFAULT_CUTOFF_I)
|
||||||
|
ec->Ly_i = DEFAULT_CUTOFF_I;
|
||||||
|
|
||||||
|
|
||||||
|
/* Update the Peak far-end receive signal detected */
|
||||||
|
/* ----------------------------------------------- */
|
||||||
|
if (ec->y_tilde_i > ec->max_y_tilde) {
|
||||||
|
/* New highest y_tilde with full life */
|
||||||
|
ec->max_y_tilde = ec->y_tilde_i;
|
||||||
|
ec->max_y_tilde_pos = ec->N_d - 1;
|
||||||
|
} else if (--ec->max_y_tilde_pos < 0) {
|
||||||
|
/* Time to find new max y tilde... */
|
||||||
|
ec->max_y_tilde = MAX16(ec->y_tilde_s.buf_d + ec->y_tilde_s.idx_d, ec->N_d, &ec->max_y_tilde_pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Determine if near end speech was detected in this sample */
|
||||||
|
/* -------------------------------------------------------- */
|
||||||
|
if (((ec->s_tilde_i >> (DEFAULT_ALPHA_ST_I - 1)) > ec->max_y_tilde)
|
||||||
|
&& (ec->max_y_tilde > 0)) {
|
||||||
|
/* Then start the Hangover counter */
|
||||||
|
ec->HCNTR_d = DEFAULT_HANGT;
|
||||||
|
RESTORE_COEFFS;
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk(KERN_INFO "Reset near end speech timer with: s_tilde_i %d, stmnt %d, max_y_tilde %d\n", ec->s_tilde_i, (ec->s_tilde_i >> (DEFAULT_ALPHA_ST_I - 1)), ec->max_y_tilde);
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_nearend_speech_frames;
|
||||||
|
#endif
|
||||||
|
} else if (ec->HCNTR_d > (int)0) {
|
||||||
|
/* otherwise, if it's still non-zero, decrement the Hangover counter by one sample */
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_nearend_speech_frames;
|
||||||
|
#endif
|
||||||
|
ec->HCNTR_d--;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update coefficients if no near-end speech in this sample (ie. HCNTR_d = 0)
|
||||||
|
* and we have enough signal to bother trying to update.
|
||||||
|
* --------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
if (!ec->HCNTR_d && /* no near-end speech present */
|
||||||
|
!(ec->i_d % DEFAULT_M)) { /* we only update on every DEFAULM_M'th sample from the stream */
|
||||||
|
if (ec->Lu_i > MIN_UPDATE_THRESH_I) { /* there is sufficient energy above the noise floor to contain meaningful data */
|
||||||
|
/* so loop over all the filter coefficients */
|
||||||
|
#ifdef USED_COEFFS
|
||||||
|
int max_coeffs[USED_COEFFS];
|
||||||
|
int *pos;
|
||||||
|
|
||||||
|
if (ec->N_d > USED_COEFFS)
|
||||||
|
memset(max_coeffs, 0, USED_COEFFS*sizeof(int));
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "updating coefficients with: ec->Lu_i %9d\n", ec->Lu_i);
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
ec->avg_Lu_i_ok = ec->avg_Lu_i_ok + ec->Lu_i;
|
||||||
|
++ec->cntr_coeff_updates;
|
||||||
|
#endif
|
||||||
|
for (k=0; k < ec->N_d; k++) {
|
||||||
|
/* eq. (7): compute an expectation over M_d samples */
|
||||||
|
int grad2;
|
||||||
|
grad2 = CONVOLVE2(ec->u_s.buf_d + ec->u_s.idx_d,
|
||||||
|
ec->y_s.buf_d + ec->y_s.idx_d + k,
|
||||||
|
DEFAULT_M);
|
||||||
|
/* eq. (7): update the coefficient */
|
||||||
|
ec->a_i[k] += grad2 / two_beta_i;
|
||||||
|
ec->a_s[k] = ec->a_i[k] >> 16;
|
||||||
|
|
||||||
|
#ifdef USED_COEFFS
|
||||||
|
if (ec->N_d > USED_COEFFS) {
|
||||||
|
if (abs(ec->a_i[k]) > max_coeffs[USED_COEFFS-1]) {
|
||||||
|
/* More or less insertion-sort... */
|
||||||
|
pos = max_coeffs;
|
||||||
|
while (*pos > abs(ec->a_i[k]))
|
||||||
|
pos++;
|
||||||
|
|
||||||
|
if (*pos > max_coeffs[USED_COEFFS-1])
|
||||||
|
memmove(pos+1, pos, (USED_COEFFS-(pos-max_coeffs)-1)*sizeof(int));
|
||||||
|
|
||||||
|
*pos = abs(ec->a_i[k]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USED_COEFFS
|
||||||
|
/* Filter out irrelevant coefficients */
|
||||||
|
if (ec->N_d > USED_COEFFS)
|
||||||
|
for (k=0; k < ec->N_d; k++)
|
||||||
|
if (abs(ec->a_i[k]) < max_coeffs[USED_COEFFS-1])
|
||||||
|
ec->a_i[k] = ec->a_s[k] = 0;
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "insufficient signal to update coefficients ec->Lu_i %5d < %5d\n", ec->Lu_i, MIN_UPDATE_THRESH_I);
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
ec->avg_Lu_i_toolow = ec->avg_Lu_i_toolow + ec->Lu_i;
|
||||||
|
++ec->cntr_coeff_missedupdates;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* paragraph below eq. (15): if no near-end speech in the sample and
|
||||||
|
* the reference signal power estimate > cutoff threshold
|
||||||
|
* then perform residual error suppression
|
||||||
|
*/
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
if (ec->HCNTR_d == 0)
|
||||||
|
printk( KERN_INFO "possibily correcting frame with ec->Ly_i %9d ec->Lu_i %9d and expression %d\n", ec->Ly_i, ec->Lu_i, (ec->Ly_i/(ec->Lu_i + 1)));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef NO_ECHO_SUPPRESSOR
|
||||||
|
#ifdef AGGRESSIVE_SUPPRESSOR
|
||||||
|
if ((ec->HCNTR_d < AGGRESSIVE_HCNTR) && (ec->Ly_i > (ec->Lu_i << 1))) {
|
||||||
|
for (k=0; k < RESIDUAL_SUPRESSION_PASSES; k++) {
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I)) + 1);
|
||||||
|
}
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "aggresively correcting frame with ec->Ly_i %9d ec->Lu_i %9d expression %d\n", ec->Ly_i, ec->Lu_i, (ec->Ly_i/(ec->Lu_i + 1)));
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_residualcorrected_frames;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (ec->HCNTR_d == 0) {
|
||||||
|
if ((ec->Ly_i/(ec->Lu_i + 1)) > DEFAULT_SUPPR_I) {
|
||||||
|
for (k=0; k < RESIDUAL_SUPRESSION_PASSES; k++) {
|
||||||
|
u = u * (ec->Lu_i >> DEFAULT_SIGMA_LU_I) / ((ec->Ly_i >> (DEFAULT_SIGMA_LY_I + 2)) + 1);
|
||||||
|
}
|
||||||
|
#ifdef MEC2_STATS_DETAILED
|
||||||
|
printk( KERN_INFO "correcting frame with ec->Ly_i %9d ec->Lu_i %9d expression %d\n", ec->Ly_i, ec->Lu_i, (ec->Ly_i/(ec->Lu_i + 1)));
|
||||||
|
#endif
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
++ec->cntr_residualcorrected_frames;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
else {
|
||||||
|
++ec->cntr_residualcorrected_framesskipped;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
/* This will generate a non-linear supression factor, once converted */
|
||||||
|
if ((ec->HCNTR_d == 0) &&
|
||||||
|
((ec->Lu_d/ec->Ly_d) < DEFAULT_SUPPR) &&
|
||||||
|
(ec->Lu_d/ec->Ly_d > EC_MIN_DB_VALUE)) {
|
||||||
|
suppr_factor = (10 / (float)(SUPPR_FLOOR - SUPPR_CEIL)) * log(ec->Lu_d/ec->Ly_d)
|
||||||
|
- SUPPR_CEIL / (float)(SUPPR_FLOOR - SUPPR_CEIL);
|
||||||
|
u_suppr = pow(10.0, (suppr_factor) * RES_SUPR_FACTOR / 10.0) * u_suppr;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef MEC2_STATS
|
||||||
|
/* Periodically dump performance stats */
|
||||||
|
if ((ec->i_d % MEC2_STATS) == 0) {
|
||||||
|
/* make sure to avoid div0's! */
|
||||||
|
if (ec->cntr_coeff_missedupdates > 0)
|
||||||
|
ec->avg_Lu_i_toolow = (int)(ec->avg_Lu_i_toolow / ec->cntr_coeff_missedupdates);
|
||||||
|
else
|
||||||
|
ec->avg_Lu_i_toolow = -1;
|
||||||
|
|
||||||
|
if (ec->cntr_coeff_updates > 0)
|
||||||
|
ec->avg_Lu_i_ok = (ec->avg_Lu_i_ok / ec->cntr_coeff_updates);
|
||||||
|
else
|
||||||
|
ec->avg_Lu_i_ok = -1;
|
||||||
|
|
||||||
|
printk( KERN_INFO "%d: Near end speech: %5d Residuals corrected/skipped: %5d/%5d Coefficients updated ok/low sig: %3d/%3d Lu_i avg ok/low sig %6d/%5d\n",
|
||||||
|
ec->id,
|
||||||
|
ec->cntr_nearend_speech_frames,
|
||||||
|
ec->cntr_residualcorrected_frames, ec->cntr_residualcorrected_framesskipped,
|
||||||
|
ec->cntr_coeff_updates, ec->cntr_coeff_missedupdates,
|
||||||
|
ec->avg_Lu_i_ok, ec->avg_Lu_i_toolow);
|
||||||
|
|
||||||
|
ec->cntr_nearend_speech_frames = 0;
|
||||||
|
ec->cntr_residualcorrected_frames = 0;
|
||||||
|
ec->cntr_residualcorrected_framesskipped = 0;
|
||||||
|
ec->cntr_coeff_updates = 0;
|
||||||
|
ec->cntr_coeff_missedupdates = 0;
|
||||||
|
ec->avg_Lu_i_ok = 0;
|
||||||
|
ec->avg_Lu_i_toolow = 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Increment the sample index and return the corrected sample */
|
||||||
|
ec->i_d++;
|
||||||
|
return u;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline struct echo_can_state *echo_can_create(int len, int adaption_mode)
|
||||||
|
{
|
||||||
|
struct echo_can_state *ec;
|
||||||
|
int maxy;
|
||||||
|
int maxu;
|
||||||
|
maxy = len + DEFAULT_M;
|
||||||
|
maxu = DEFAULT_M;
|
||||||
|
if (maxy < (1 << DEFAULT_ALPHA_YT_I))
|
||||||
|
maxy = (1 << DEFAULT_ALPHA_YT_I);
|
||||||
|
if (maxy < (1 << DEFAULT_SIGMA_LY_I))
|
||||||
|
maxy = (1 << DEFAULT_SIGMA_LY_I);
|
||||||
|
if (maxu < (1 << DEFAULT_SIGMA_LU_I))
|
||||||
|
maxu = (1 << DEFAULT_SIGMA_LU_I);
|
||||||
|
ec = (struct echo_can_state *)MALLOC(sizeof(struct echo_can_state) +
|
||||||
|
4 + /* align */
|
||||||
|
sizeof(int) * len + /* a_i */
|
||||||
|
sizeof(short) * len + /* a_s */
|
||||||
|
sizeof(int) * len + /* b_i */
|
||||||
|
sizeof(int) * len + /* c_i */
|
||||||
|
2 * sizeof(short) * (maxy) + /* y_s */
|
||||||
|
2 * sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) + /* s_s */
|
||||||
|
2 * sizeof(short) * (maxu) + /* u_s */
|
||||||
|
2 * sizeof(short) * len); /* y_tilde_s */
|
||||||
|
if (ec) {
|
||||||
|
memset(ec, 0, sizeof(struct echo_can_state) +
|
||||||
|
4 + /* align */
|
||||||
|
sizeof(int) * len + /* a_i */
|
||||||
|
sizeof(short) * len + /* a_s */
|
||||||
|
sizeof(int) * len + /* b_i */
|
||||||
|
sizeof(int) * len + /* c_i */
|
||||||
|
2 * sizeof(short) * (maxy) + /* y_s */
|
||||||
|
2 * sizeof(short) * (1 << DEFAULT_ALPHA_ST_I) + /* s_s */
|
||||||
|
2 * sizeof(short) * (maxu) + /* u_s */
|
||||||
|
2 * sizeof(short) * len); /* y_tilde_s */
|
||||||
|
init_cc(ec, len, maxy, maxu);
|
||||||
|
}
|
||||||
|
return ec;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline int echo_can_traintap(struct echo_can_state *ec, int pos, short val)
|
||||||
|
{
|
||||||
|
/* Set the hangover counter to the length of the can to
|
||||||
|
* avoid adjustments occuring immediately after initial forced training
|
||||||
|
*/
|
||||||
|
ec->HCNTR_d = ec->N_d << 1;
|
||||||
|
|
||||||
|
if (pos >= ec->N_d) {
|
||||||
|
memcpy(ec->b_i,ec->a_i,ec->N_d*sizeof(int));
|
||||||
|
memcpy(ec->c_i,ec->a_i,ec->N_d*sizeof(int));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ec->a_i[pos] = val << 17;
|
||||||
|
ec->a_s[pos] = val << 1;
|
||||||
|
|
||||||
|
if (++pos >= ec->N_d) {
|
||||||
|
memcpy(ec->b_i,ec->a_i,ec->N_d*sizeof(int));
|
||||||
|
memcpy(ec->c_i,ec->a_i,ec->N_d*sizeof(int));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
|
@ -0,0 +1,97 @@
|
||||||
|
/*
|
||||||
|
Important constants for tuning mg2 echo can
|
||||||
|
*/
|
||||||
|
#ifndef _MG2_CONST_H
|
||||||
|
#define _MG2_CONST_H
|
||||||
|
|
||||||
|
|
||||||
|
/* Convergence (aka. adaptation) speed -- higher means slower */
|
||||||
|
#define DEFAULT_BETA1_I 2048
|
||||||
|
|
||||||
|
/* Constants for various power computations */
|
||||||
|
#define DEFAULT_SIGMA_LY_I 7
|
||||||
|
#define DEFAULT_SIGMA_LU_I 7
|
||||||
|
#define DEFAULT_ALPHA_ST_I 5 /* near-end speech detection sensitivity factor */
|
||||||
|
#define DEFAULT_ALPHA_YT_I 5
|
||||||
|
|
||||||
|
#define DEFAULT_CUTOFF_I 128
|
||||||
|
|
||||||
|
/* Define the near-end speech hangover counter: if near-end speech
|
||||||
|
* is declared, hcntr is set equal to hangt (see pg. 432)
|
||||||
|
*/
|
||||||
|
#define DEFAULT_HANGT 600 /* in samples, so 600 samples = 75ms */
|
||||||
|
|
||||||
|
/* define the residual error suppression threshold */
|
||||||
|
#define DEFAULT_SUPPR_I 16 /* 16 = -24db */
|
||||||
|
|
||||||
|
/* This is the minimum reference signal power estimate level
|
||||||
|
* that will result in filter adaptation.
|
||||||
|
* If this is too low then background noise will cause the filter
|
||||||
|
* coefficients to constantly be updated.
|
||||||
|
*/
|
||||||
|
#define MIN_UPDATE_THRESH_I 4096
|
||||||
|
|
||||||
|
/* The number of samples used to update coefficients using the
|
||||||
|
* the block update method (M). It should be related back to the
|
||||||
|
* length of the echo can.
|
||||||
|
* ie. it only updates coefficients when (sample number MOD default_m) = 0
|
||||||
|
*
|
||||||
|
* Getting this wrong may cause an oops. Consider yourself warned!
|
||||||
|
*/
|
||||||
|
#define DEFAULT_M 16 /* every 16th sample */
|
||||||
|
|
||||||
|
/* If AGGRESSIVE supression is enabled, then we start cancelling residual
|
||||||
|
* echos again even while there is potentially the very end of a near-side
|
||||||
|
* signal present.
|
||||||
|
* This defines how many samples of DEFAULT_HANGT can remain before we
|
||||||
|
* kick back in
|
||||||
|
*/
|
||||||
|
#define AGGRESSIVE_HCNTR 160 /* in samples, so 160 samples = 20ms */
|
||||||
|
|
||||||
|
/* This knob controls the number of passes the residual echo supression
|
||||||
|
* algorithm makes.
|
||||||
|
*/
|
||||||
|
#ifdef AGGRESSIVE_SUPPRESSOR
|
||||||
|
#define RESIDUAL_SUPRESSION_PASSES 2
|
||||||
|
#else
|
||||||
|
#define RESIDUAL_SUPRESSION_PASSES 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Treat sample as error if it has a different sign as the
|
||||||
|
* input signal and is this number larger in ABS() as
|
||||||
|
* the input-signal */
|
||||||
|
#define MAX_SIGN_ERROR 3000
|
||||||
|
|
||||||
|
/* Number of coefficients really used for calculating the
|
||||||
|
* simulated echo. The value specifies how many of the
|
||||||
|
* biggest coefficients are used for calculating rs.
|
||||||
|
* This helps on long echo-tails by artificially limiting
|
||||||
|
* the number of coefficients for the calculation and
|
||||||
|
* preventing overflows.
|
||||||
|
* Comment this to deactivate the code */
|
||||||
|
#define USED_COEFFS 64
|
||||||
|
|
||||||
|
/* Backup coefficients every this number of samples */
|
||||||
|
#define BACKUP 256
|
||||||
|
|
||||||
|
/***************************************************************/
|
||||||
|
/* The following knobs are not implemented in the current code */
|
||||||
|
|
||||||
|
/* we need a dynamic level of suppression varying with the ratio of the
|
||||||
|
power of the echo to the power of the reference signal this is
|
||||||
|
done so that we have a smoother background.
|
||||||
|
we have a higher suppression when the power ratio is closer to
|
||||||
|
suppr_ceil and reduces logarithmically as we approach suppr_floor.
|
||||||
|
*/
|
||||||
|
#define SUPPR_FLOOR -64
|
||||||
|
#define SUPPR_CEIL -24
|
||||||
|
|
||||||
|
/* in a second departure, we calculate the residual error suppression
|
||||||
|
* as a percentage of the reference signal energy level. The threshold
|
||||||
|
* is defined in terms of dB below the reference signal.
|
||||||
|
*/
|
||||||
|
#define RES_SUPR_FACTOR -20
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* _MG2_CONST_H */
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,187 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* mISDN driver for Colognechip HFC-S mini Evaluation Card
|
||||||
|
*
|
||||||
|
* Authors : Martin Bachem, Joerg Ciesielski
|
||||||
|
* Contact : info@colognechip.com
|
||||||
|
*
|
||||||
|
* 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, 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __HFCMINI_H__
|
||||||
|
#define __HFCMINI_H__
|
||||||
|
|
||||||
|
#include "channel.h"
|
||||||
|
#include "hfcsmcc.h"
|
||||||
|
|
||||||
|
|
||||||
|
#define BRIDGE_UNKWOWN 0
|
||||||
|
#define BRIDGE_HFCPCI 1
|
||||||
|
|
||||||
|
/* use HFC-S PCI as PCI Bridge */
|
||||||
|
#define HFCBRIDGE BRIDGE_HFCPCI
|
||||||
|
#define SPIN_LOCK_HFCSMINI_REGISTER
|
||||||
|
|
||||||
|
#define DRIVER_NAME "HFCMINI"
|
||||||
|
#define CHIP_ID_HFCSMINI CHIP_ID
|
||||||
|
|
||||||
|
#define MAX_CHAN 4 /* D, B1, B2, PCM */
|
||||||
|
|
||||||
|
/* flags in _u16 port mode */
|
||||||
|
#define PORT_UNUSED 0x0000
|
||||||
|
#define PORT_MODE_NT 0x0001
|
||||||
|
#define PORT_MODE_TE 0x0002
|
||||||
|
#define PORT_MODE_BUS_MASTER 0x0004
|
||||||
|
#define PORT_MODE_UP 0x0008
|
||||||
|
#define PORT_MODE_EXCH_POL 0x0010
|
||||||
|
#define PORT_MODE_LOOP 0x0020
|
||||||
|
#define NT_TIMER 0x8000
|
||||||
|
|
||||||
|
|
||||||
|
/* NT / TE defines */
|
||||||
|
#define NT_T1_COUNT 12 /* number of 8ms interrupts for G2 timeout */
|
||||||
|
#define CLK_DLY_TE 0x0e /* CLKDEL in TE mode */
|
||||||
|
#define CLK_DLY_NT 0x6c /* CLKDEL in NT mode */
|
||||||
|
#define STA_ACTIVATE 0x60 /* start activation in A_SU_WR_STA */
|
||||||
|
#define STA_DEACTIVATE 0x40 /* start deactivation in A_SU_WR_STA */
|
||||||
|
#define LIF_MODE_NT 0x04 /* Line Interface NT mode */
|
||||||
|
|
||||||
|
|
||||||
|
/* HFC-S mini 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_TESTLOOP_B1 0x05
|
||||||
|
#define HFC_L1_TESTLOOP_B2 0x06
|
||||||
|
|
||||||
|
/* FIFO handling support values */
|
||||||
|
#define FIFO_IRQ_OFF 0
|
||||||
|
#define FIFO_IRQ_ON 1
|
||||||
|
#define FIFO_DISABLE 0
|
||||||
|
#define FIFO_ENABLE 1
|
||||||
|
#define FIFO_MASK_TX 0x55
|
||||||
|
#define FIFO_MASK_RX 0xAA
|
||||||
|
#define HDLC_PAR_BCH 0 /* init value for all B-channel fifos */
|
||||||
|
#define HDLC_PAR_DCH (M1_BIT_CNT*2) /* same for D- and E-channel */
|
||||||
|
#define CON_HDLC_B_TRANS (M_HDLC_TRP | M1_TRP_IRQ*2) /* transparent mode B-channel 32 byte threshold */
|
||||||
|
#define CON_HDLC_B_HDLC (M1_TRP_IRQ*2) /* HDLC mode b-channel */
|
||||||
|
#define CON_HDLC_D_HDLC (M1_TRP_IRQ*2 | M_IFF) /* HDLC mode D-channel, 1 fill mode */
|
||||||
|
#define CON_HDLC_B_LOOP (M1_TRP_IRQ*2 | M1_DATA_FLOW*6) /* B-channel loopback mode */
|
||||||
|
#define HFCSMINI_RX_THRESHOLD 32
|
||||||
|
#define HFCSMINI_TX_THRESHOLD 96
|
||||||
|
#define DCH_RX_SIZE 267
|
||||||
|
#define BCH_RX_SIZE 2051
|
||||||
|
#define BCH_RX_SIZE_TRANS 64
|
||||||
|
|
||||||
|
/* DEBUG flags, use combined value for module parameter debug=x */
|
||||||
|
#define DEBUG_HFC_INIT 0x0001
|
||||||
|
#define DEBUG_HFC_MODE 0x0002
|
||||||
|
#define DEBUG_HFC_S0_STATES 0x0004
|
||||||
|
#define DEBUG_HFC_IRQ 0x0008
|
||||||
|
#define DEBUG_HFC_FIFO_ERR 0x0010
|
||||||
|
#define DEBUG_HFC_DTRACE 0x2000
|
||||||
|
#define DEBUG_HFC_BTRACE 0x4000 /* very(!) heavy messageslog load */
|
||||||
|
#define DEBUG_HFC_FIFO 0x8000 /* very(!) heavy messageslog load */
|
||||||
|
|
||||||
|
|
||||||
|
/* private driver_data */
|
||||||
|
typedef struct {
|
||||||
|
int chip_id;
|
||||||
|
char *device_name;
|
||||||
|
} hfcsmini_param;
|
||||||
|
|
||||||
|
struct _hfcmini_hw;
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
/* hardware structure */
|
||||||
|
/**********************/
|
||||||
|
typedef struct _hfcmini_hw {
|
||||||
|
|
||||||
|
struct list_head list;
|
||||||
|
__u32 irq_cnt; /* count irqs */
|
||||||
|
struct tasklet_struct tasklet; /* interrupt bottom half */
|
||||||
|
spinlock_t mlock; /* mISDN mq lock */
|
||||||
|
spinlock_t rlock; /* register access lock */
|
||||||
|
|
||||||
|
int cardnum;
|
||||||
|
__u8 param_idx; /* used to access module param arrays */
|
||||||
|
__u8 testirq;
|
||||||
|
int irq;
|
||||||
|
int iobase;
|
||||||
|
u_char *membase;
|
||||||
|
u_char *hw_membase;
|
||||||
|
struct pci_dev *pdev;
|
||||||
|
hfcsmini_param driver_data;
|
||||||
|
char card_name[60];
|
||||||
|
|
||||||
|
int max_fifo; /* always 4 fifos per port */
|
||||||
|
__u8 max_z; /* fifo depth -1 */
|
||||||
|
|
||||||
|
channel_t chan[MAX_CHAN]; /* line interfaces */
|
||||||
|
|
||||||
|
__u8 fifomask;
|
||||||
|
|
||||||
|
/* HFC-S MINI regsister */
|
||||||
|
reg_r_chip_id chip_id; /* Chip ID */
|
||||||
|
|
||||||
|
reg_r_pcm_md0 pcm_md0; /* PCM config */
|
||||||
|
reg_r_pcm_md1 pcm_md1; /* PCM config */
|
||||||
|
reg_r_pcm_md2 pcm_md2; /* PCM config */
|
||||||
|
|
||||||
|
reg_r_ti ti; /* timer interrupt configuration */
|
||||||
|
|
||||||
|
reg_r_fifo_irqmsk fifo_irqmsk; /* FIFO interrupt mask */
|
||||||
|
reg_r_fifo_irq fifo_irq; /* FIFO interrupt state */
|
||||||
|
|
||||||
|
reg_r_misc_irqmsk misc_irqmsk; /* MISC interrupt mask */
|
||||||
|
reg_r_misc_irq misc_irq; /* MISC interrupt state */
|
||||||
|
|
||||||
|
reg_r_st_ctrl0 st_ctrl0;
|
||||||
|
reg_r_st_ctrl2 st_ctrl2;
|
||||||
|
|
||||||
|
int nt_timer;
|
||||||
|
__u8 dpid; /* DChannel Protocoll ID */
|
||||||
|
__u16 portmode; /* NT/TE */
|
||||||
|
|
||||||
|
} hfcsmini_hw;
|
||||||
|
|
||||||
|
|
||||||
|
/* function prototypes */
|
||||||
|
int setup_channel(hfcsmini_hw * hw, __u8 channel, int protocol);
|
||||||
|
void hfcsmini_write_fifo(hfcsmini_hw * hw, __u8 channel);
|
||||||
|
void hfcsmini_read_fifo(hfcsmini_hw * hw, __u8 channel);
|
||||||
|
void print_fc(hfcsmini_hw * hw, __u8 fifo);
|
||||||
|
void setup_fifo(hfcsmini_hw * hw, int fifo, __u8 hdlcreg, __u8 con_reg, __u8 irq_enable, __u8 enable);
|
||||||
|
void setup_s(hfcsmini_hw * hw, __u8 bc, __u8 enable);
|
||||||
|
void disable_interrupts(hfcsmini_hw * hw);
|
||||||
|
void enable_interrupts(hfcsmini_hw * hw);
|
||||||
|
static void release_card(hfcsmini_hw * hw);
|
||||||
|
|
||||||
|
|
||||||
|
#if HFCBRIDGE == BRIDGE_HFCPCI
|
||||||
|
int init_pci_bridge(hfcsmini_hw * hw);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* HFC-S MINI register access functions */
|
||||||
|
static inline void hfcsmini_sel_reg(hfcsmini_hw * hw, __u8 reg_addr);
|
||||||
|
static inline __u8 read_hfcsmini(hfcsmini_hw * hw, __u8 reg_addr);
|
||||||
|
static inline __u8 read_hfcsmini_irq(hfcsmini_hw * hw, __u8 reg_addr);
|
||||||
|
static inline __u8 read_hfcsmini_stable(hfcsmini_hw * hw, __u8 reg_addr);
|
||||||
|
static inline void write_hfcsmini(hfcsmini_hw * hw, __u8 reg_addr, __u8 value);
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* __hfcsmini_H__ */
|
File diff suppressed because it is too large
Load Diff
|
@ -1,182 +0,0 @@
|
||||||
/* $Id$
|
|
||||||
*
|
|
||||||
* hw_lock.h Hardware locking inline routines
|
|
||||||
*
|
|
||||||
* Author Karsten Keil (keil@isdn4linux.de)
|
|
||||||
*
|
|
||||||
* This file is (c) under GNU PUBLIC LICENSE
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Description of the locking mechanism
|
|
||||||
*
|
|
||||||
* The locking must grant serialisized and atomic
|
|
||||||
* access to the ISDN hardware registers, if the lock
|
|
||||||
* is aquired no other process or IRQ is alloed to
|
|
||||||
* access ISDN hardware registers.
|
|
||||||
*
|
|
||||||
* In general here are 3 possible entry points:
|
|
||||||
* 1. the ISDN interrupt routine
|
|
||||||
* 2. ISDN timer routines in the hardware module
|
|
||||||
* 3. messages that came from upper layers
|
|
||||||
*
|
|
||||||
* Since most work must be do in the interrupt routine
|
|
||||||
* (to grant minimum IRQ latency) and only few things with
|
|
||||||
* need direct HW access must be done for messages from upper
|
|
||||||
* layers, we should allow other IRQs in our IRQ routines and
|
|
||||||
* only block our own routines in this case. Since the common IRQ
|
|
||||||
* routines allready mask the same IRQ, we only need to protect us
|
|
||||||
* from timer and uper layers. The disadvantage is, that we need to
|
|
||||||
* disable local IRQ for entry points 2. and 3., but since the routines
|
|
||||||
* which need hardware access are well known and small, the impact
|
|
||||||
* is very small.
|
|
||||||
*
|
|
||||||
* We have a two stage locking to make this working:
|
|
||||||
* A spinlock which protect the state LOCK Flag (STATE_FLAG_BUSY) and
|
|
||||||
* also protect us from local IRQs from the entry points 2 and 3.
|
|
||||||
*
|
|
||||||
* In the hardware IRQ we aquire the spinlock, set the STATE_FLAG_BUSY
|
|
||||||
* LOCK Flag and then release the spinlock. It can never happen that
|
|
||||||
* the STATE_FLAG_BUSY is already set in this case, see later.
|
|
||||||
*
|
|
||||||
* In the other cases (from timer or upper layers) we aquire the spinlock
|
|
||||||
* test_and_set the STATE_FLAG_BUSY LOCK Flag, if it was allready set
|
|
||||||
* (a ISDN IRQ is running on the other CPU) we schedule timeout or add a other
|
|
||||||
* small timeout.
|
|
||||||
* If it was not set, we have the lock and we don't release the spinlock until we have
|
|
||||||
* done the harware work.
|
|
||||||
*
|
|
||||||
* To avoid any kind of deadlocking, it is important that we release the lock
|
|
||||||
* before we call functions that deliver to upper layers.
|
|
||||||
* To leave the impact of disabled local IRQ small, it is important to only protect
|
|
||||||
* small areas where hardware is accessed.
|
|
||||||
*
|
|
||||||
* The following routines handle the lock in the entry point from upper layers and other
|
|
||||||
* none IRQ cases (module init/exit stuff).
|
|
||||||
*
|
|
||||||
* They never called directly, but via the wrappers assigned to the instance
|
|
||||||
* inst.lock / inst.unlock pointers.
|
|
||||||
*
|
|
||||||
* Here are two defines which can be used for DEBUGING and PROFILING
|
|
||||||
* SPIN_DEBUG and LOCK_STATISTIC
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
#ifndef __hw_lock__
|
|
||||||
#define __hw_lock__
|
|
||||||
|
|
||||||
typedef struct _mISDN_HWlock {
|
|
||||||
u_long flags;
|
|
||||||
spinlock_t lock;
|
|
||||||
#ifdef SPIN_DEBUG
|
|
||||||
void *spin_adr;
|
|
||||||
void *busy_adr;
|
|
||||||
#endif
|
|
||||||
volatile u_long state;
|
|
||||||
#ifdef LOCK_STATISTIC
|
|
||||||
u_int try_ok;
|
|
||||||
u_int try_wait;
|
|
||||||
u_int try_inirq;
|
|
||||||
u_int try_mult;
|
|
||||||
u_int irq_ok;
|
|
||||||
u_int irq_fail;
|
|
||||||
#endif
|
|
||||||
} mISDN_HWlock_t;
|
|
||||||
|
|
||||||
#define STATE_FLAG_BUSY 1
|
|
||||||
#define STATE_FLAG_INIRQ 2
|
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
* returns 0 if the lock was aquired
|
|
||||||
* returns 1 if nowait != 0 and the lock is not aquired
|
|
||||||
*/
|
|
||||||
static inline int lock_HW(mISDN_HWlock_t *lock, int nowait)
|
|
||||||
{
|
|
||||||
u_long flags;
|
|
||||||
#ifdef LOCK_STATISTIC
|
|
||||||
int wait = 0;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
spin_lock_irqsave(&lock->lock, flags);
|
|
||||||
#ifdef SPIN_DEBUG
|
|
||||||
lock->spin_adr = __builtin_return_address(0);
|
|
||||||
#endif
|
|
||||||
while (test_and_set_bit(STATE_FLAG_BUSY, &lock->state)) {
|
|
||||||
/* allready busy so we delay */
|
|
||||||
spin_unlock_irqrestore(&lock->lock, flags);
|
|
||||||
#ifdef SPIN_DEBUG
|
|
||||||
lock->spin_adr = NULL;
|
|
||||||
#endif
|
|
||||||
if (nowait) {
|
|
||||||
#ifdef LOCK_STATISTIC
|
|
||||||
lock->try_wait++;
|
|
||||||
#endif
|
|
||||||
return(1);
|
|
||||||
}
|
|
||||||
/* delay 1 jiffie is enought */
|
|
||||||
if (in_interrupt()) {
|
|
||||||
/* Should never happen */
|
|
||||||
#ifdef LOCK_STATISTIC
|
|
||||||
lock->try_inirq++;
|
|
||||||
#endif
|
|
||||||
printk(KERN_ERR "lock_HW: try to schedule in IRQ state(%lx)\n",
|
|
||||||
lock->state);
|
|
||||||
mdelay(1);
|
|
||||||
} else {
|
|
||||||
#ifdef LOCK_STATISTIC
|
|
||||||
if (wait++)
|
|
||||||
lock->try_mult++;
|
|
||||||
else
|
|
||||||
lock->try_wait++;
|
|
||||||
#endif
|
|
||||||
schedule_timeout(1);
|
|
||||||
}
|
|
||||||
spin_lock_irqsave(&lock->lock, flags);
|
|
||||||
#ifdef SPIN_DEBUG
|
|
||||||
lock->spin_adr = __builtin_return_address(0);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
/* get the LOCK */
|
|
||||||
lock->flags = flags;
|
|
||||||
#ifdef SPIN_DEBUG
|
|
||||||
lock->busy_adr = __builtin_return_address(0);
|
|
||||||
#endif
|
|
||||||
#ifdef LOCK_STATISTIC
|
|
||||||
if (!wait)
|
|
||||||
lock->try_ok++;
|
|
||||||
#endif
|
|
||||||
return(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void unlock_HW(mISDN_HWlock_t *lock)
|
|
||||||
{
|
|
||||||
if (!test_and_clear_bit(STATE_FLAG_BUSY, &lock->state)) {
|
|
||||||
printk(KERN_ERR "lock_HW: STATE_FLAG_BUSY not locked state(%lx)\n",
|
|
||||||
lock->state);
|
|
||||||
}
|
|
||||||
#ifdef SPIN_DEBUG
|
|
||||||
lock->busy_adr = NULL;
|
|
||||||
lock->spin_adr = NULL;
|
|
||||||
#endif
|
|
||||||
spin_unlock_irqrestore(&lock->lock, lock->flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void lock_HW_init(mISDN_HWlock_t *lock)
|
|
||||||
{
|
|
||||||
spin_lock_init(&lock->lock);
|
|
||||||
lock->state = 0;
|
|
||||||
#ifdef SPIN_DEBUG
|
|
||||||
lock->busy_adr = NULL;
|
|
||||||
lock->spin_adr = NULL;
|
|
||||||
#endif
|
|
||||||
#ifdef LOCK_STATISTIC
|
|
||||||
lock->try_ok = 0;
|
|
||||||
lock->try_wait = 0;
|
|
||||||
lock->try_inirq = 0;
|
|
||||||
lock->try_mult = 0;
|
|
||||||
lock->irq_ok = 0;
|
|
||||||
lock->irq_fail = 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,598 @@
|
||||||
|
/*
|
||||||
|
* loop.c loop driver for looped bchannel pairs
|
||||||
|
*
|
||||||
|
* Author Andreas Eversberg (jolly@jolly.de)
|
||||||
|
*
|
||||||
|
* 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, 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* module parameters:
|
||||||
|
* interfaces:
|
||||||
|
Number of loop interfaces. Default is 1.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/config.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
|
||||||
|
#include "channel.h"
|
||||||
|
#include "layer1.h"
|
||||||
|
#include "debug.h"
|
||||||
|
#include <linux/isdn_compat.h>
|
||||||
|
|
||||||
|
#include "loop.h"
|
||||||
|
|
||||||
|
static const char *loop_revision = "$Revision$";
|
||||||
|
|
||||||
|
static int loop_cnt;
|
||||||
|
|
||||||
|
static mISDNobject_t loop_obj;
|
||||||
|
|
||||||
|
static char LoopName[] = "loop";
|
||||||
|
|
||||||
|
|
||||||
|
/****************/
|
||||||
|
/* module stuff */
|
||||||
|
/****************/
|
||||||
|
|
||||||
|
static int interfaces;
|
||||||
|
static int debug;
|
||||||
|
|
||||||
|
#ifdef MODULE
|
||||||
|
MODULE_AUTHOR("Andreas Eversberg");
|
||||||
|
#ifdef MODULE_LICENSE
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
#endif
|
||||||
|
MODULE_PARM(interfaces, "1i");
|
||||||
|
MODULE_PARM(debug, "1i");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/****************************/
|
||||||
|
/* Layer 1 D-channel access */
|
||||||
|
/****************************/
|
||||||
|
|
||||||
|
/* message transfer from layer 1.
|
||||||
|
*/
|
||||||
|
static int loop_l1hw(mISDNinstance_t *inst, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
channel_t *dch = container_of(inst, channel_t, inst);
|
||||||
|
loop_t *hc;
|
||||||
|
int ret = 0;
|
||||||
|
mISDN_head_t *hh;
|
||||||
|
|
||||||
|
hh = mISDN_HEAD_P(skb);
|
||||||
|
hc = dch->inst.privat;
|
||||||
|
|
||||||
|
if (debug & DEBUG_LOOP_MSG)
|
||||||
|
printk(KERN_DEBUG "%s: unsupported prim %x\n", __FUNCTION__, hh->prim);
|
||||||
|
ret = -EINVAL;
|
||||||
|
if (!ret)
|
||||||
|
dev_kfree_skb(skb);
|
||||||
|
return(ret);
|
||||||
|
}
|
||||||
|
/******************************/
|
||||||
|
/* Layer2 -> Layer 1 Transfer */
|
||||||
|
/******************************/
|
||||||
|
|
||||||
|
/* messages from layer 2 to layer 1 are processed here.
|
||||||
|
*/
|
||||||
|
static int
|
||||||
|
loop_l2l1(mISDNinstance_t *inst, struct sk_buff *skb)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
channel_t *bch = container_of(inst, channel_t, inst);
|
||||||
|
int ret = -EINVAL;
|
||||||
|
mISDN_head_t *hh;
|
||||||
|
loop_t *hc;
|
||||||
|
struct sk_buff *nskb;
|
||||||
|
|
||||||
|
hh = mISDN_HEAD_P(skb);
|
||||||
|
hc = bch->inst.privat;
|
||||||
|
ch = bch->channel;
|
||||||
|
|
||||||
|
if ((hh->prim == PH_DATA_REQ)
|
||||||
|
|| (hh->prim == (DL_DATA | REQUEST))) {
|
||||||
|
if (skb->len <= 0) {
|
||||||
|
printk(KERN_WARNING "%s: skb too small\n", __FUNCTION__);
|
||||||
|
return(-EINVAL);
|
||||||
|
}
|
||||||
|
if (skb->len > MAX_DATA_MEM) {
|
||||||
|
printk(KERN_WARNING "%s: skb too large\n", __FUNCTION__);
|
||||||
|
return(-EINVAL);
|
||||||
|
}
|
||||||
|
if ((nskb = skb_clone(skb, GFP_ATOMIC)))
|
||||||
|
queue_ch_frame(hc->bch[ch^1], INDICATION, MISDN_ID_ANY, nskb);
|
||||||
|
skb_trim(skb, 0);
|
||||||
|
return(mISDN_queueup_newhead(inst, 0, hh->prim | CONFIRM, hh->dinfo, skb));
|
||||||
|
} else if ((hh->prim == (PH_ACTIVATE | REQUEST))
|
||||||
|
|| (hh->prim == (DL_ESTABLISH | REQUEST))) {
|
||||||
|
/* activate B-channel if not already activated */
|
||||||
|
skb_trim(skb, 0);
|
||||||
|
return(mISDN_queueup_newhead(inst, 0, hh->prim | CONFIRM, ret, skb));
|
||||||
|
} else if ((hh->prim == (PH_DEACTIVATE | REQUEST))
|
||||||
|
|| (hh->prim == (DL_RELEASE | REQUEST))
|
||||||
|
|| ((hh->prim == (PH_CONTROL | REQUEST) && (hh->dinfo == HW_DEACTIVATE)))) {
|
||||||
|
skb_trim(skb, 0);
|
||||||
|
return(mISDN_queueup_newhead(inst, 0, hh->prim | CONFIRM, ret, skb));
|
||||||
|
} else
|
||||||
|
if (hh->prim == (PH_CONTROL | REQUEST)) {
|
||||||
|
switch (hh->dinfo) {
|
||||||
|
default:
|
||||||
|
printk(KERN_DEBUG "%s: unknown PH_CONTROL info %x\n", __FUNCTION__, hh->dinfo);
|
||||||
|
ret = -EINVAL;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
printk(KERN_WARNING "%s: unknown prim(%x)\n", __FUNCTION__, hh->prim);
|
||||||
|
ret = -EINVAL;
|
||||||
|
}
|
||||||
|
if (!ret) {
|
||||||
|
dev_kfree_skb(skb);
|
||||||
|
}
|
||||||
|
return(ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/**************************
|
||||||
|
* remove card from stack *
|
||||||
|
**************************/
|
||||||
|
|
||||||
|
static void
|
||||||
|
loop_delete(loop_t *hc)
|
||||||
|
{
|
||||||
|
int ch;
|
||||||
|
u_long flags;
|
||||||
|
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: entered\n", __FUNCTION__);
|
||||||
|
|
||||||
|
/* free channels */
|
||||||
|
if (hc->dch) {
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: free D-channel\n", __FUNCTION__);
|
||||||
|
mISDN_freechannel(hc->dch);
|
||||||
|
kfree(hc->dch);
|
||||||
|
hc->dch = NULL;
|
||||||
|
}
|
||||||
|
ch = 0;
|
||||||
|
while(ch < LOOP_CHANNELS) {
|
||||||
|
if (hc->bch[ch]) {
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: free B-channel %d\n", __FUNCTION__, ch);
|
||||||
|
mISDN_freechannel(hc->bch[ch]);
|
||||||
|
kfree(hc->bch[ch]);
|
||||||
|
hc->bch[ch] = NULL;
|
||||||
|
}
|
||||||
|
ch++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove us from list and delete */
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_WARNING "%s: remove instance from list\n", __FUNCTION__);
|
||||||
|
spin_lock_irqsave(&loop_obj.lock, flags);
|
||||||
|
list_del(&hc->list);
|
||||||
|
spin_unlock_irqrestore(&loop_obj.lock, flags);
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_WARNING "%s: delete instance\n", __FUNCTION__);
|
||||||
|
kfree(hc);
|
||||||
|
loop_cnt--;
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_WARNING "%s: card successfully removed\n", __FUNCTION__);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
loop_manager(void *data, u_int prim, void *arg)
|
||||||
|
{
|
||||||
|
loop_t *hc;
|
||||||
|
mISDNinstance_t *inst = data;
|
||||||
|
struct sk_buff *skb;
|
||||||
|
channel_t *dch = NULL;
|
||||||
|
channel_t *bch = NULL;
|
||||||
|
int ch = 0;
|
||||||
|
u_long flags;
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
MGR_HASPROTOCOL_HANDLER(prim,arg,&loop_obj)
|
||||||
|
printk(KERN_ERR "%s: no data prim %x arg %p\n", __FUNCTION__, prim, arg);
|
||||||
|
return(-EINVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* find channel and card */
|
||||||
|
spin_lock_irqsave(&loop_obj.lock, flags);
|
||||||
|
list_for_each_entry(hc, &loop_obj.ilist, list) {
|
||||||
|
if (hc->dch) if (&hc->dch->inst == inst) {
|
||||||
|
dch = hc->dch;
|
||||||
|
spin_unlock_irqrestore(&loop_obj.lock, flags);
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: D-channel data %p prim %x arg %p\n", __FUNCTION__, data, prim, arg);
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
|
||||||
|
ch = 0;
|
||||||
|
while(ch < LOOP_CHANNELS) {
|
||||||
|
if (hc->bch[ch]) if (&hc->bch[ch]->inst == inst) {
|
||||||
|
bch = hc->bch[ch];
|
||||||
|
spin_unlock_irqrestore(&loop_obj.lock, flags);
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: B-channel %d (0..%d) data %p prim %x arg %p\n", __FUNCTION__, ch, LOOP_CHANNELS-1, data, prim, arg);
|
||||||
|
goto found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&loop_obj.lock, flags);
|
||||||
|
printk(KERN_ERR "%s: no card/channel found data %p prim %x arg %p\n", __FUNCTION__, data, prim, arg);
|
||||||
|
return(-EINVAL);
|
||||||
|
|
||||||
|
found:
|
||||||
|
switch(prim) {
|
||||||
|
case MGR_REGLAYER | CONFIRM:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_REGLAYER\n", __FUNCTION__);
|
||||||
|
mISDN_setpara(dch, &inst->st->para);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MGR_UNREGLAYER | REQUEST:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_UNREGLAYER\n", __FUNCTION__);
|
||||||
|
if (dch) {
|
||||||
|
if ((skb = create_link_skb(PH_CONTROL | REQUEST, HW_DEACTIVATE, 0, NULL, 0))) {
|
||||||
|
if (loop_l1hw(inst, skb)) dev_kfree_skb(skb);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
if (bch) {
|
||||||
|
if ((skb = create_link_skb(PH_CONTROL | REQUEST, 0, 0, NULL, 0))) {
|
||||||
|
if (loop_l2l1(inst, skb)) dev_kfree_skb(skb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop_obj.ctrl(inst, MGR_UNREGLAYER | REQUEST, NULL);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MGR_CLRSTPARA | INDICATION:
|
||||||
|
arg = NULL;
|
||||||
|
// fall through
|
||||||
|
case MGR_ADDSTPARA | INDICATION:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_***STPARA\n", __FUNCTION__);
|
||||||
|
mISDN_setpara(dch, arg);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MGR_RELEASE | INDICATION:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_RELEASE = remove port from mISDN\n", __FUNCTION__);
|
||||||
|
if (dch) {
|
||||||
|
loop_delete(hc); /* hc is free */
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#ifdef FIXME
|
||||||
|
case MGR_CONNECT | REQUEST:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_CONNECT\n", __FUNCTION__);
|
||||||
|
return(mISDN_ConnectIF(inst, arg));
|
||||||
|
|
||||||
|
case MGR_SETIF | REQUEST:
|
||||||
|
case MGR_SETIF | INDICATION:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_SETIF\n", __FUNCTION__);
|
||||||
|
if (dch)
|
||||||
|
return(mISDN_SetIF(inst, arg, prim, loop_l1hw, NULL, dch));
|
||||||
|
if (bch)
|
||||||
|
return(mISDN_SetIF(inst, arg, prim, loop_l2l1, NULL, bch));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MGR_DISCONNECT | REQUEST:
|
||||||
|
case MGR_DISCONNECT | INDICATION:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_DISCONNECT\n", __FUNCTION__);
|
||||||
|
return(mISDN_DisConnectIF(inst, arg));
|
||||||
|
#endif
|
||||||
|
#if 0
|
||||||
|
case MGR_SELCHANNEL | REQUEST:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_SELCHANNEL\n", __FUNCTION__);
|
||||||
|
if (!dch) {
|
||||||
|
printk(KERN_WARNING "%s(MGR_SELCHANNEL|REQUEST): selchannel not dinst\n", __FUNCTION__);
|
||||||
|
return(-EINVAL);
|
||||||
|
}
|
||||||
|
return(SelFreeBChannel(hc, ch, arg));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case MGR_SETSTACK | INDICATION:
|
||||||
|
if (debug & DEBUG_LOOP_MGR)
|
||||||
|
printk(KERN_DEBUG "%s: MGR_SETSTACK\n", __FUNCTION__);
|
||||||
|
if (bch && inst->pid.global==2) {
|
||||||
|
if ((skb = create_link_skb(PH_ACTIVATE | REQUEST, 0, 0, NULL, 0))) {
|
||||||
|
if (loop_l2l1(inst, skb)) dev_kfree_skb(skb);
|
||||||
|
}
|
||||||
|
if (inst->pid.protocol[2] == ISDN_PID_L2_B_TRANS)
|
||||||
|
mISDN_queue_data(inst, FLG_MSG_UP, DL_ESTABLISH | INDICATION, 0, 0, NULL, 0);
|
||||||
|
else mISDN_queue_data(inst, FLG_MSG_UP, PH_ACTIVATE | INDICATION, 0, 0, NULL, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
PRIM_NOT_HANDLED(MGR_CTRLREADY | INDICATION);
|
||||||
|
PRIM_NOT_HANDLED(MGR_GLOBALOPT | REQUEST);
|
||||||
|
default:
|
||||||
|
printk(KERN_WARNING "%s: prim %x not handled\n", __FUNCTION__, prim);
|
||||||
|
return(-EINVAL);
|
||||||
|
}
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************
|
||||||
|
* create cards instance *
|
||||||
|
*************************/
|
||||||
|
|
||||||
|
static int __devinit loop_new(void)
|
||||||
|
{
|
||||||
|
int ret_err=0;
|
||||||
|
int ch;
|
||||||
|
loop_t *hc;
|
||||||
|
mISDN_pid_t pid;
|
||||||
|
mISDNstack_t *dst = NULL; /* make gcc happy */
|
||||||
|
channel_t *dch;
|
||||||
|
channel_t *bch;
|
||||||
|
u_long flags;
|
||||||
|
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: Registering loop driver #%d\n", __FUNCTION__, loop_cnt+1);
|
||||||
|
|
||||||
|
/* allocate structure */
|
||||||
|
if (!(hc = kmalloc(sizeof(loop_t), GFP_ATOMIC))) {
|
||||||
|
printk(KERN_ERR "No kmem for loop driver\n");
|
||||||
|
ret_err = -ENOMEM;
|
||||||
|
goto free_object;
|
||||||
|
}
|
||||||
|
memset(hc, 0, sizeof(loop_t));
|
||||||
|
hc->idx = loop_cnt;
|
||||||
|
hc->id = loop_cnt + 1;
|
||||||
|
|
||||||
|
sprintf(hc->name, "LOOP#%d", loop_cnt+1);
|
||||||
|
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: (after APPEND_TO_LIST)\n", __FUNCTION__);
|
||||||
|
|
||||||
|
spin_lock_irqsave(&loop_obj.lock, flags);
|
||||||
|
list_add_tail(&hc->list, &loop_obj.ilist);
|
||||||
|
spin_unlock_irqrestore(&loop_obj.lock, flags);
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: (after APPEND_TO_LIST)\n", __FUNCTION__);
|
||||||
|
|
||||||
|
spin_lock_init(&hc->lock);
|
||||||
|
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: Registering D-channel, card(%d)\n", __FUNCTION__, loop_cnt+1);
|
||||||
|
dch = kmalloc(sizeof(channel_t), GFP_ATOMIC);
|
||||||
|
if (!dch) {
|
||||||
|
ret_err = -ENOMEM;
|
||||||
|
goto free_channels;
|
||||||
|
}
|
||||||
|
memset(dch, 0, sizeof(channel_t));
|
||||||
|
dch->channel = 0;
|
||||||
|
//dch->debug = debug;
|
||||||
|
dch->inst.obj = &loop_obj;
|
||||||
|
dch->inst.hwlock = &hc->lock;
|
||||||
|
mISDN_init_instance(&dch->inst, &loop_obj, hc, loop_l1hw);
|
||||||
|
dch->inst.pid.layermask = ISDN_LAYER(0);
|
||||||
|
sprintf(dch->inst.name, "LOOP%d", loop_cnt+1);
|
||||||
|
if (mISDN_initchannel(dch, MSK_INIT_DCHANNEL, MAX_DATA_MEM)) {
|
||||||
|
ret_err = -ENOMEM;
|
||||||
|
goto free_channels;
|
||||||
|
}
|
||||||
|
hc->dch = dch;
|
||||||
|
|
||||||
|
ch=0;
|
||||||
|
while(ch < LOOP_CHANNELS) {
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: Registering B-channel, card(%d) ch(%d)\n", __FUNCTION__, loop_cnt+1, ch);
|
||||||
|
bch = kmalloc(sizeof(channel_t), GFP_ATOMIC);
|
||||||
|
if (!bch) {
|
||||||
|
ret_err = -ENOMEM;
|
||||||
|
goto free_channels;
|
||||||
|
}
|
||||||
|
memset(bch, 0, sizeof(channel_t));
|
||||||
|
bch->channel = ch;
|
||||||
|
mISDN_init_instance(&bch->inst, &loop_obj, hc, loop_l2l1);
|
||||||
|
bch->inst.pid.layermask = ISDN_LAYER(0);
|
||||||
|
bch->inst.hwlock = &hc->lock;
|
||||||
|
//bch->debug = debug;
|
||||||
|
sprintf(bch->inst.name, "%s B%d",
|
||||||
|
dch->inst.name, ch+1);
|
||||||
|
if (mISDN_initchannel(bch, MSK_INIT_BCHANNEL, MAX_DATA_MEM)) {
|
||||||
|
kfree(bch);
|
||||||
|
ret_err = -ENOMEM;
|
||||||
|
goto free_channels;
|
||||||
|
}
|
||||||
|
hc->bch[ch] = bch;
|
||||||
|
#ifdef FIXME // TODO
|
||||||
|
if (bch->dev) {
|
||||||
|
bch->dev->wport.pif.func = loop_l2l1;
|
||||||
|
bch->dev->wport.pif.fdata = bch;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ch++;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* set D-channel */
|
||||||
|
mISDN_set_dchannel_pid(&pid, 0x00, ISDN_LAYER(0));
|
||||||
|
pid.protocol[0] = ISDN_PID_L0_LOOP;
|
||||||
|
pid.layermask = ISDN_LAYER(0);
|
||||||
|
|
||||||
|
/* add stacks */
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: Adding d-stack: card(%d)\n", __FUNCTION__, loop_cnt+1);
|
||||||
|
if ((ret_err = loop_obj.ctrl(NULL, MGR_NEWSTACK | REQUEST, &dch->inst))) {
|
||||||
|
printk(KERN_ERR "MGR_ADDSTACK REQUEST dch err(%d)\n", ret_err);
|
||||||
|
free_release:
|
||||||
|
loop_delete(hc); /* hc is free */
|
||||||
|
goto free_object;
|
||||||
|
}
|
||||||
|
|
||||||
|
dst = dch->inst.st;
|
||||||
|
|
||||||
|
ch = 0;
|
||||||
|
while(ch < LOOP_CHANNELS) {
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: Adding b-stack: card(%d) B-channel(%d)\n", __FUNCTION__, loop_cnt+1, ch+1);
|
||||||
|
bch = hc->bch[ch];
|
||||||
|
if ((ret_err = loop_obj.ctrl(dst, MGR_NEWSTACK | REQUEST, &bch->inst))) {
|
||||||
|
printk(KERN_ERR "MGR_ADDSTACK bchan error %d\n", ret_err);
|
||||||
|
free_delstack:
|
||||||
|
loop_obj.ctrl(dst, MGR_DELSTACK | REQUEST, NULL);
|
||||||
|
goto free_release;
|
||||||
|
}
|
||||||
|
ch++;
|
||||||
|
}
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: (before MGR_SETSTACK REQUEST) layermask=0x%x\n", __FUNCTION__, pid.layermask);
|
||||||
|
|
||||||
|
if ((ret_err = loop_obj.ctrl(dst, MGR_SETSTACK | REQUEST, &pid))) {
|
||||||
|
printk(KERN_ERR "MGR_SETSTACK REQUEST dch err(%d)\n", ret_err);
|
||||||
|
goto free_delstack;
|
||||||
|
}
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: (after MGR_SETSTACK REQUEST)\n", __FUNCTION__);
|
||||||
|
|
||||||
|
/* delay some time */
|
||||||
|
set_current_state(TASK_UNINTERRUPTIBLE);
|
||||||
|
schedule_timeout((100*HZ)/1000); /* Timeout 100ms */
|
||||||
|
|
||||||
|
/* tell stack, that we are ready */
|
||||||
|
loop_obj.ctrl(dst, MGR_CTRLREADY | INDICATION, NULL);
|
||||||
|
|
||||||
|
loop_cnt++;
|
||||||
|
return(0);
|
||||||
|
|
||||||
|
/* if an error ocurred */
|
||||||
|
free_channels:
|
||||||
|
if (hc->dch) {
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: free D-channel\n", __FUNCTION__);
|
||||||
|
mISDN_freechannel(hc->dch);
|
||||||
|
kfree(hc->dch);
|
||||||
|
hc->dch = NULL;
|
||||||
|
}
|
||||||
|
ch = 0;
|
||||||
|
while(ch < LOOP_CHANNELS) {
|
||||||
|
if (hc->bch[ch]) {
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: free B-channel %d\n", __FUNCTION__, ch);
|
||||||
|
mISDN_freechannel(hc->bch[ch]);
|
||||||
|
kfree(hc->bch[ch]);
|
||||||
|
hc->bch[ch] = NULL;
|
||||||
|
}
|
||||||
|
ch++;
|
||||||
|
}
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: before REMOVE_FROM_LIST (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt);
|
||||||
|
spin_lock_irqsave(&loop_obj.lock, flags);
|
||||||
|
list_del(&hc->list);
|
||||||
|
spin_unlock_irqrestore(&loop_obj.lock, flags);
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: after REMOVE_FROM_LIST (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt);
|
||||||
|
kfree(hc);
|
||||||
|
|
||||||
|
free_object:
|
||||||
|
return(ret_err);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void __exit
|
||||||
|
loop_cleanup(void)
|
||||||
|
{
|
||||||
|
loop_t *hc,*next;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
/* unregister mISDN object */
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: entered (refcnt = %d loop_cnt = %d)\n", __FUNCTION__, loop_obj.refcnt, loop_cnt);
|
||||||
|
if ((err = mISDN_unregister(&loop_obj))) {
|
||||||
|
printk(KERN_ERR "Can't unregister Loop cards error(%d)\n", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* remove remaining devices, but this should never happen */
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: now checking ilist (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt);
|
||||||
|
|
||||||
|
list_for_each_entry_safe(hc, next, &loop_obj.ilist, list) {
|
||||||
|
printk(KERN_ERR "Loop card struct not empty refs %d\n", loop_obj.refcnt);
|
||||||
|
loop_delete(hc);
|
||||||
|
}
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: done (refcnt = %d loop_cnt = %d)\n", __FUNCTION__, loop_obj.refcnt, loop_cnt);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int __init
|
||||||
|
loop_init(void)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
char tmpstr[64];
|
||||||
|
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: init entered\n", __FUNCTION__);
|
||||||
|
|
||||||
|
strcpy(tmpstr, loop_revision);
|
||||||
|
printk(KERN_INFO "mISDN: loop-driver Rev. %s\n", mISDN_getrev(tmpstr));
|
||||||
|
|
||||||
|
memset(&loop_obj, 0, sizeof(loop_obj));
|
||||||
|
#ifdef MODULE
|
||||||
|
loop_obj.owner = THIS_MODULE;
|
||||||
|
#endif
|
||||||
|
spin_lock_init(&loop_obj.lock);
|
||||||
|
INIT_LIST_HEAD(&loop_obj.ilist);
|
||||||
|
loop_obj.name = LoopName;
|
||||||
|
loop_obj.own_ctrl = loop_manager;
|
||||||
|
loop_obj.DPROTO.protocol[0] = ISDN_PID_L0_LOOP;
|
||||||
|
loop_obj.BPROTO.protocol[1] = ISDN_PID_L1_B_64TRANS | ISDN_PID_L1_B_64HDLC;
|
||||||
|
loop_obj.BPROTO.protocol[2] = ISDN_PID_L2_B_TRANS | ISDN_PID_L2_B_RAWDEV;
|
||||||
|
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: registering loop_obj\n", __FUNCTION__);
|
||||||
|
if ((err = mISDN_register(&loop_obj))) {
|
||||||
|
printk(KERN_ERR "Can't register Loop cards error(%d)\n", err);
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
if (debug & DEBUG_LOOP_INIT)
|
||||||
|
printk(KERN_DEBUG "%s: new mISDN object (refcnt = %d)\n", __FUNCTION__, loop_obj.refcnt);
|
||||||
|
|
||||||
|
if (interfaces < 1)
|
||||||
|
interfaces = 1;
|
||||||
|
loop_cnt = 0;
|
||||||
|
while(loop_cnt < interfaces)
|
||||||
|
{
|
||||||
|
if ((err = loop_new()))
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err)
|
||||||
|
{
|
||||||
|
printk(KERN_ERR "error registering pci driver:%x\n",err);
|
||||||
|
loop_cleanup();
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
printk(KERN_INFO "%d devices registered\n", loop_cnt);
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#ifdef MODULE
|
||||||
|
module_init(loop_init);
|
||||||
|
module_exit(loop_cleanup);
|
||||||
|
#endif
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
* see notice in loop.c
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define DEBUG_LOOP_MSG 0x0001
|
||||||
|
#define DEBUG_LOOP_INIT 0x0004
|
||||||
|
#define DEBUG_LOOP_MGR 0x0008
|
||||||
|
|
||||||
|
#define MAX_FRAME_SIZE 2048
|
||||||
|
|
||||||
|
#define LOOP_CHANNELS 128
|
||||||
|
|
||||||
|
struct misdn_loop {
|
||||||
|
struct list_head list;
|
||||||
|
char name[32];
|
||||||
|
int idx; /* chip index for module parameters */
|
||||||
|
int id; /* chip number starting with 1 */
|
||||||
|
|
||||||
|
spinlock_t lock; /* the lock */
|
||||||
|
|
||||||
|
channel_t *dch;
|
||||||
|
channel_t *bch[LOOP_CHANNELS];
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct misdn_loop loop_t;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,174 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
* socket handling, transfer, receive-process for Voice over IP
|
||||||
|
*
|
||||||
|
* Author Andreas Eversberg (jolly@jolly.de)
|
||||||
|
*
|
||||||
|
* 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, 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/vmalloc.h>
|
||||||
|
#include <linux/config.h>
|
||||||
|
#include <linux/in.h>
|
||||||
|
#include <net/sock.h>
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
#include "socket.h"
|
||||||
|
#include <linux/isdn_compat.h>
|
||||||
|
|
||||||
|
|
||||||
|
/* socket thread */
|
||||||
|
static int
|
||||||
|
mISDN_socket_thread(void *data)
|
||||||
|
{
|
||||||
|
mISDN_socket_t *ms = (mISDN_socket_t *)data;
|
||||||
|
|
||||||
|
/* make daemon */
|
||||||
|
daemonize("mISDN-socket");
|
||||||
|
allow_signal(SIGTERM);
|
||||||
|
|
||||||
|
/* read loop */
|
||||||
|
while(!signal_lending(current)) {
|
||||||
|
ms->iov.iov_base = ms->rcvbuf;
|
||||||
|
ms->iov_len = sizeof(ms->rcvbuf);
|
||||||
|
ms->oldfs = get_fs();
|
||||||
|
set_fs(KERNEL_DS);
|
||||||
|
ms->rcvlen = sock_recvmsg(ms->socket, &ms->msg, sizeof(ms->rcvbuf), 0);
|
||||||
|
set_fs(oldfs);
|
||||||
|
if (ms->rcvlen>0) {
|
||||||
|
ms->func(ms, ms->pri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* show that we terminate */
|
||||||
|
ms->thread_pid = 0;
|
||||||
|
|
||||||
|
/* if we got killed, signal completion */
|
||||||
|
if (ms->thread_complete)
|
||||||
|
complete(&ms->thread_complete);
|
||||||
|
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Adds a new socket with receive process.
|
||||||
|
* func will be the pointer to the receive process.
|
||||||
|
* -> The packet information is included in the mISDN_socket_t.
|
||||||
|
* pri will be the private data when calling receive process.
|
||||||
|
* local/remote ip/port will be the tupple for sending an receiving udp data.
|
||||||
|
* -> local-ip must be 0 if any local ip is accepted.
|
||||||
|
*/
|
||||||
|
mISDN_socket_t
|
||||||
|
*mISDN_add_socket(sock_func_t *func, void *pri, u32 local_ip, u32 remote_ip, u16 local_port, u16 remote_port)
|
||||||
|
{
|
||||||
|
mISDN_socket_t *ms;
|
||||||
|
|
||||||
|
/* alloc memory structure */
|
||||||
|
if (!(sm = vmalloc(sizeof(mISDN_sock_t)))) {
|
||||||
|
printk(KERN_ERR "%s: No memory for mISDN_sock_t.\n", __FUNCTION__);
|
||||||
|
return(NULL);
|
||||||
|
}
|
||||||
|
memset(ms, 0, sizeof(mISDN_sock_t));
|
||||||
|
ms->func = func;
|
||||||
|
ms->pri = pri;
|
||||||
|
|
||||||
|
/* create socket */
|
||||||
|
if (sock_create(PF_INET, SOCK_DGRAM, IPPROTO_UDP, &ms->socket)) {
|
||||||
|
printk(KERN_ERR "%s: Failed to create socket.\n", __FUNCTION__);
|
||||||
|
mISDN_free_socket(ms);
|
||||||
|
return(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* bind socket */
|
||||||
|
ms->server.sin_family = AF_INET;
|
||||||
|
ms->server.sin_addr.s_addr = (local_ip)?local_ip:INADDR_ANY;
|
||||||
|
ms->server.sin_port = htons((unsigned short)local_port);
|
||||||
|
if (ms->socket->ops->bind(ms->socket, (struct sockaddr *)ms->server, sizeof(ms->server))) {
|
||||||
|
printk(KERN_ERR "%s: Failed to bind socket.\n", __FUNCTION__);
|
||||||
|
mISDN_free_socket(ms);
|
||||||
|
return(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check sk */
|
||||||
|
if (ms->socket->sk == NULL) {
|
||||||
|
printk(KERN_ERR "%s: socket->sk == NULL\n", __FUNCTION__);
|
||||||
|
return(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* build message */
|
||||||
|
ms->msg.msg_name = &ms->client;
|
||||||
|
ms->msg.msg_namelen = sizeof(ms->client);
|
||||||
|
ms->msg.msg_control = NULL;
|
||||||
|
ms->msg.msg_controllen = 0;
|
||||||
|
ms->msg.msg_iov = &ms->iov;
|
||||||
|
ms->msg.msg_iovlen = 1;
|
||||||
|
|
||||||
|
/* create receive process */
|
||||||
|
if ((ms->thread_pid = kernel_thread(mISDN_socket_thread, ms, CLONE_KERNEL)) < 0) {
|
||||||
|
printk(KERN_ERR "%s: Failed to create receive process.\n", __FUNCTION__);
|
||||||
|
mISDN_free_socket(ms);
|
||||||
|
return(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* free given socket with all resources
|
||||||
|
*/
|
||||||
|
void
|
||||||
|
mISDN_free_socket(mISDN_sock_t *ms)
|
||||||
|
{
|
||||||
|
if (!ms)
|
||||||
|
return;
|
||||||
|
|
||||||
|
/* kill thread */
|
||||||
|
if (ms->thread_pid) {
|
||||||
|
DECLARE_COMPLETION(thread_complete);
|
||||||
|
ms->thread_complete = &thread_complete;
|
||||||
|
kill_proc(ms->thread_pid, SIGTERM, 0);
|
||||||
|
wait_for_completion(&thread_complete);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* release socket */
|
||||||
|
if (ms->socket)
|
||||||
|
sock_release(ms->socket);
|
||||||
|
|
||||||
|
/* free memory structure */
|
||||||
|
vfree(sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
* transfer a frame via given socket
|
||||||
|
*/
|
||||||
|
int
|
||||||
|
mISDN_send_socket(mISDN_sock_t *ms, u8 *buf, int len)
|
||||||
|
{
|
||||||
|
|
||||||
|
/* send packet */
|
||||||
|
ms->send_iov.iov_base = ms->sndbuf;
|
||||||
|
ms->send_iov_len = sizeof(ms->sndbuf);
|
||||||
|
ms->send_oldfs = get_fs();
|
||||||
|
set_fs(KERNEL_DS);
|
||||||
|
ms->rcvlen = sock_recvmsg(ms->socket, &ms->msg, sizeof(ms->rcvbuf), 0);
|
||||||
|
set_fs(oldfs);
|
||||||
|
if (ms->rcvlen>0) {
|
||||||
|
ms->func(ms, ms->pri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
* socket handling, transfer, receive-process for Voice over IP
|
||||||
|
*
|
||||||
|
* Author Andreas Eversberg (jolly@jolly.de)
|
||||||
|
*
|
||||||
|
* 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, 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct _mISDN_socket_t;
|
||||||
|
typedef void (sock_func_t)(_mISDN_socket_t *ms, void *pri);
|
||||||
|
|
||||||
|
typedef struct _mISDN_socket_t {
|
||||||
|
sock_func_t *func;
|
||||||
|
void *pri;
|
||||||
|
int thread_pid;
|
||||||
|
struct completion *thread_complete;
|
||||||
|
struct socket *socket;
|
||||||
|
struct sockaddr_in server, client;
|
||||||
|
struct sockaddr *address;
|
||||||
|
struct msghdr msg;
|
||||||
|
struct iovec iov;
|
||||||
|
mm_segment_t oldfs;
|
||||||
|
u8 rcvbuf[1500];
|
||||||
|
int rcvlen;
|
||||||
|
} mISDN_socket_t;
|
|
@ -0,0 +1,77 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* Author Karsten Keil (keil@isdn4linux.de)
|
||||||
|
*
|
||||||
|
* mISDN sysfs common defines
|
||||||
|
*
|
||||||
|
* This file is (c) under GNU PUBLIC LICENSE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include <linux/stringify.h>
|
||||||
|
|
||||||
|
extern ssize_t mISDN_show_pid_protocol(mISDN_pid_t *, char *);
|
||||||
|
extern ssize_t mISDN_show_pid_parameter(mISDN_pid_t *, char *);
|
||||||
|
|
||||||
|
static inline ssize_t show_pid_layermask(mISDN_pid_t *pid, char *buf)
|
||||||
|
{
|
||||||
|
return sprintf(buf, "0x%08x\n", pid->layermask);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline ssize_t show_pid_global(mISDN_pid_t *pid, char *buf)
|
||||||
|
{
|
||||||
|
return sprintf(buf, "0x%04x\n", pid->global);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline ssize_t show_pid_maxplen(mISDN_pid_t *pid, char *buf)
|
||||||
|
{
|
||||||
|
return sprintf(buf, "%d\n", pid->maxplen);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define MISDN_PROTO(_type, _name, _mode) \
|
||||||
|
static ssize_t show_protocol_##_name(struct class_device *class_dev, char *buf) \
|
||||||
|
{ \
|
||||||
|
_type##_t *p = to_##_type(class_dev); \
|
||||||
|
return(mISDN_show_pid_protocol(&p->_name, buf)); \
|
||||||
|
} \
|
||||||
|
struct class_device_attribute _type##_attr_protocol_##_name = \
|
||||||
|
__ATTR(protocol,_mode,show_protocol_##_name, NULL); \
|
||||||
|
static ssize_t show_parameter_##_name(struct class_device *class_dev, char *buf) \
|
||||||
|
{ \
|
||||||
|
_type##_t *p = to_##_type(class_dev); \
|
||||||
|
return(mISDN_show_pid_parameter(&p->_name, buf)); \
|
||||||
|
} \
|
||||||
|
struct class_device_attribute _type##_attr_parameter_##_name = \
|
||||||
|
__ATTR(parameter,_mode,show_parameter_##_name, NULL); \
|
||||||
|
static ssize_t show_layermask_##_name(struct class_device *class_dev, char *buf) \
|
||||||
|
{ \
|
||||||
|
_type##_t *p = to_##_type(class_dev); \
|
||||||
|
return(show_pid_layermask(&p->_name, buf)); \
|
||||||
|
} \
|
||||||
|
struct class_device_attribute _type##_attr_layermask_##_name = \
|
||||||
|
__ATTR(layermask,_mode,show_layermask_##_name, NULL); \
|
||||||
|
static ssize_t show_global_##_name(struct class_device *class_dev, char *buf) \
|
||||||
|
{ \
|
||||||
|
_type##_t *p = to_##_type(class_dev); \
|
||||||
|
return(show_pid_global(&p->_name, buf)); \
|
||||||
|
} \
|
||||||
|
struct class_device_attribute _type##_attr_global_##_name = \
|
||||||
|
__ATTR(global,_mode,show_global_##_name, NULL); \
|
||||||
|
static ssize_t show_maxplen_##_name(struct class_device *class_dev, char *buf) \
|
||||||
|
{ \
|
||||||
|
_type##_t *p = to_##_type(class_dev); \
|
||||||
|
return(show_pid_maxplen(&p->_name, buf)); \
|
||||||
|
} \
|
||||||
|
struct class_device_attribute _type##_attr_maxplen_##_name = \
|
||||||
|
__ATTR(maxplen,_mode,show_maxplen_##_name, NULL); \
|
||||||
|
static struct attribute *attr_##_name[] = { \
|
||||||
|
&_type##_attr_global_##_name.attr, \
|
||||||
|
&_type##_attr_layermask_##_name.attr, \
|
||||||
|
&_type##_attr_maxplen_##_name.attr, \
|
||||||
|
&_type##_attr_parameter_##_name.attr, \
|
||||||
|
&_type##_attr_protocol_##_name.attr, \
|
||||||
|
NULL \
|
||||||
|
}; \
|
||||||
|
static struct attribute_group _name##_group = { \
|
||||||
|
.name = __stringify(_name), \
|
||||||
|
.attrs = attr_##_name, \
|
||||||
|
}
|
|
@ -0,0 +1,124 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* Author Karsten Keil (keil@isdn4linux.de)
|
||||||
|
*
|
||||||
|
* mISDN sysfs stuff for isnstances
|
||||||
|
*
|
||||||
|
* This file is (c) under GNU PUBLIC LICENSE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "core.h"
|
||||||
|
#include "sysfs.h"
|
||||||
|
|
||||||
|
#define to_mISDNinstance(d) container_of(d, mISDNinstance_t, class_dev)
|
||||||
|
|
||||||
|
static ssize_t show_inst_id(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNinstance_t *inst = to_mISDNinstance(class_dev);
|
||||||
|
return sprintf(buf, "%08x\n", inst->id);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(id, S_IRUGO, show_inst_id, NULL);
|
||||||
|
|
||||||
|
static ssize_t show_inst_name(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNinstance_t *inst = to_mISDNinstance(class_dev);
|
||||||
|
return sprintf(buf, "%s\n", inst->name);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(name, S_IRUGO, show_inst_name, NULL);
|
||||||
|
|
||||||
|
static ssize_t show_inst_extentions(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNinstance_t *inst = to_mISDNinstance(class_dev);
|
||||||
|
return sprintf(buf, "%08x\n", inst->extentions);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(extentions, S_IRUGO, show_inst_extentions, NULL);
|
||||||
|
|
||||||
|
static ssize_t show_inst_regcnt(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNinstance_t *inst = to_mISDNinstance(class_dev);
|
||||||
|
return sprintf(buf, "%d\n", inst->regcnt);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(regcnt, S_IRUGO, show_inst_regcnt, NULL);
|
||||||
|
|
||||||
|
MISDN_PROTO(mISDNinstance, pid, S_IRUGO);
|
||||||
|
|
||||||
|
static void release_mISDN_inst(struct class_device *dev)
|
||||||
|
{
|
||||||
|
mISDNinstance_t *inst = to_mISDNinstance(dev);
|
||||||
|
|
||||||
|
if (inst->obj)
|
||||||
|
sysfs_remove_link(&dev->kobj, "obj");
|
||||||
|
sysfs_remove_group(&inst->class_dev.kobj, &pid_group);
|
||||||
|
printk(KERN_INFO "release instance class dev %s\n", dev->class_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct class inst_dev_class = {
|
||||||
|
.name = "mISDN-instances",
|
||||||
|
#ifndef CLASS_WITHOUT_OWNER
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
#endif
|
||||||
|
.release = &release_mISDN_inst,
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_register_sysfs_inst(mISDNinstance_t *inst) {
|
||||||
|
int err;
|
||||||
|
char name[8];
|
||||||
|
|
||||||
|
inst->class_dev.class = &inst_dev_class;
|
||||||
|
snprintf(inst->class_dev.class_id, BUS_ID_SIZE, "inst-%08x", inst->id);
|
||||||
|
err = class_device_register(&inst->class_dev);
|
||||||
|
if (err)
|
||||||
|
return(err);
|
||||||
|
class_device_create_file(&inst->class_dev, &class_device_attr_id);
|
||||||
|
class_device_create_file(&inst->class_dev, &class_device_attr_name);
|
||||||
|
class_device_create_file(&inst->class_dev, &class_device_attr_extentions);
|
||||||
|
class_device_create_file(&inst->class_dev, &class_device_attr_regcnt);
|
||||||
|
err = sysfs_create_group(&inst->class_dev.kobj, &pid_group);
|
||||||
|
if (err)
|
||||||
|
goto out_unreg;
|
||||||
|
if (inst->obj)
|
||||||
|
sysfs_create_link(&inst->class_dev.kobj, &inst->obj->class_dev.kobj, "obj");
|
||||||
|
if (inst->st) {
|
||||||
|
sprintf(name,"layer.%d", inst->id & LAYER_ID_MASK);
|
||||||
|
sysfs_create_link(&inst->st->class_dev.kobj, &inst->class_dev.kobj, name);
|
||||||
|
sysfs_create_link(&inst->class_dev.kobj, &inst->st->class_dev.kobj, "stack");
|
||||||
|
if (inst->st->mgr == inst) {
|
||||||
|
sysfs_create_link(&inst->st->class_dev.kobj, &inst->class_dev.kobj, "mgr");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(err);
|
||||||
|
|
||||||
|
out_unreg:
|
||||||
|
class_device_unregister(&inst->class_dev);
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mISDN_unregister_sysfs_inst(mISDNinstance_t *inst)
|
||||||
|
{
|
||||||
|
char name[8];
|
||||||
|
|
||||||
|
if (inst->id) {
|
||||||
|
if (inst->st) {
|
||||||
|
sprintf(name,"layer.%d", inst->id & LAYER_ID_MASK);
|
||||||
|
sysfs_remove_link(&inst->st->class_dev.kobj, name);
|
||||||
|
sysfs_remove_link(&inst->class_dev.kobj, "stack");
|
||||||
|
if (inst->st->mgr == inst)
|
||||||
|
sysfs_remove_link(&inst->st->class_dev.kobj, "mgr");
|
||||||
|
}
|
||||||
|
class_device_unregister(&inst->class_dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_sysfs_inst_init(void)
|
||||||
|
{
|
||||||
|
return(class_register(&inst_dev_class));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mISDN_sysfs_inst_cleanup(void)
|
||||||
|
{
|
||||||
|
class_unregister(&inst_dev_class);
|
||||||
|
}
|
|
@ -0,0 +1,153 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* Author Karsten Keil (keil@isdn4linux.de)
|
||||||
|
*
|
||||||
|
* mISDN sysfs object and common stuff
|
||||||
|
*
|
||||||
|
* This file is (c) under GNU PUBLIC LICENSE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "core.h"
|
||||||
|
#include "sysfs.h"
|
||||||
|
|
||||||
|
#define to_mISDNobject(d) container_of(d, mISDNobject_t, class_dev)
|
||||||
|
|
||||||
|
static ssize_t show_obj_name(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNobject_t *obj = to_mISDNobject(class_dev);
|
||||||
|
return sprintf(buf, "%s\n", obj->name);
|
||||||
|
}
|
||||||
|
|
||||||
|
static CLASS_DEVICE_ATTR(name, S_IRUGO, show_obj_name, NULL);
|
||||||
|
|
||||||
|
static ssize_t show_obj_id(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNobject_t *obj = to_mISDNobject(class_dev);
|
||||||
|
return sprintf(buf, "%d\n", obj->id);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(id, S_IRUGO, show_obj_id, NULL);
|
||||||
|
|
||||||
|
static ssize_t show_obj_refcnt(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNobject_t *obj = to_mISDNobject(class_dev);
|
||||||
|
return sprintf(buf, "%d\n", obj->refcnt);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(refcnt, S_IRUGO, show_obj_refcnt, NULL);
|
||||||
|
|
||||||
|
ssize_t mISDN_show_pid_protocol(mISDN_pid_t *pid, char *buf)
|
||||||
|
{
|
||||||
|
char *p = buf;
|
||||||
|
uint i;
|
||||||
|
|
||||||
|
for (i=0; i <= MAX_LAYER_NR; i++)
|
||||||
|
p += sprintf(p,"0x%08x,", pid->protocol[i]);
|
||||||
|
p--;
|
||||||
|
*p++ = '\n';
|
||||||
|
return (p -buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
ssize_t mISDN_show_pid_parameter(mISDN_pid_t *pid, char *buf)
|
||||||
|
{
|
||||||
|
char *p = buf, *t;
|
||||||
|
uint i, l;
|
||||||
|
|
||||||
|
for (i=0; i <= MAX_LAYER_NR; i++) {
|
||||||
|
if (pid->param[i]) {
|
||||||
|
t = pid->param[i];
|
||||||
|
l = *t++;
|
||||||
|
p += sprintf(p,"0x%02x,", l);
|
||||||
|
while(l--)
|
||||||
|
p += sprintf(p,"0x%02x,", *t++);
|
||||||
|
}else {
|
||||||
|
p += sprintf(p,"0x00,");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p--;
|
||||||
|
*p++ = '\n';
|
||||||
|
return (p -buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
MISDN_PROTO(mISDNobject, BPROTO, S_IRUGO);
|
||||||
|
MISDN_PROTO(mISDNobject, DPROTO, S_IRUGO);
|
||||||
|
|
||||||
|
static void release_mISDN_obj(struct class_device *dev)
|
||||||
|
{
|
||||||
|
mISDNobject_t *obj = to_mISDNobject(dev);
|
||||||
|
|
||||||
|
printk(KERN_INFO "release object class dev %s\n", dev->class_id);
|
||||||
|
if (obj->owner)
|
||||||
|
#ifdef MODULE_MKOBJ_POINTER
|
||||||
|
if (obj->owner->mkobj)
|
||||||
|
#endif
|
||||||
|
sysfs_remove_link(&dev->kobj, "module");
|
||||||
|
sysfs_remove_group(&obj->class_dev.kobj, &BPROTO_group);
|
||||||
|
sysfs_remove_group(&obj->class_dev.kobj, &DPROTO_group);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct class obj_dev_class = {
|
||||||
|
.name = "mISDN-objects",
|
||||||
|
#ifndef CLASS_WITHOUT_OWNER
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
#endif
|
||||||
|
.release = &release_mISDN_obj,
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_register_sysfs_obj(mISDNobject_t *obj) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
obj->class_dev.class = &obj_dev_class;
|
||||||
|
snprintf(obj->class_dev.class_id, BUS_ID_SIZE, "obj-%d", obj->id);
|
||||||
|
err = class_device_register(&obj->class_dev);
|
||||||
|
if (err)
|
||||||
|
goto out;
|
||||||
|
class_device_create_file(&obj->class_dev, &class_device_attr_id);
|
||||||
|
class_device_create_file(&obj->class_dev, &class_device_attr_name);
|
||||||
|
class_device_create_file(&obj->class_dev, &class_device_attr_refcnt);
|
||||||
|
err = sysfs_create_group(&obj->class_dev.kobj, &BPROTO_group);
|
||||||
|
if (err)
|
||||||
|
goto out_unreg;
|
||||||
|
err = sysfs_create_group(&obj->class_dev.kobj, &DPROTO_group);
|
||||||
|
if (err)
|
||||||
|
goto out_unreg;
|
||||||
|
if (obj->owner)
|
||||||
|
#ifdef MODULE_MKOBJ_POINTER
|
||||||
|
if (obj->owner->mkobj)
|
||||||
|
sysfs_create_link(&obj->class_dev.kobj, &obj->owner->mkobj->kobj, "module");
|
||||||
|
#else
|
||||||
|
sysfs_create_link(&obj->class_dev.kobj, &obj->owner->mkobj.kobj, "module");
|
||||||
|
#endif
|
||||||
|
return(err);
|
||||||
|
out_unreg:
|
||||||
|
class_device_unregister(&obj->class_dev);
|
||||||
|
out:
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_sysfs_init(void) {
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = class_register(&obj_dev_class);
|
||||||
|
if (err)
|
||||||
|
return(err);
|
||||||
|
err = mISDN_sysfs_inst_init();
|
||||||
|
if (err)
|
||||||
|
goto unreg_obj;
|
||||||
|
err = mISDN_sysfs_st_init();
|
||||||
|
if (err)
|
||||||
|
goto unreg_inst;
|
||||||
|
return(err);
|
||||||
|
unreg_inst:
|
||||||
|
mISDN_sysfs_inst_cleanup();
|
||||||
|
unreg_obj:
|
||||||
|
class_unregister(&obj_dev_class);
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mISDN_sysfs_cleanup(void) {
|
||||||
|
class_unregister(&obj_dev_class);
|
||||||
|
mISDN_sysfs_inst_cleanup();
|
||||||
|
mISDN_sysfs_st_cleanup();
|
||||||
|
}
|
|
@ -0,0 +1,270 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* Author Karsten Keil (keil@isdn4linux.de)
|
||||||
|
*
|
||||||
|
* mISDN sysfs stack stuff
|
||||||
|
*
|
||||||
|
* This file is (c) under GNU PUBLIC LICENSE
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#include "core.h"
|
||||||
|
#include "sysfs.h"
|
||||||
|
|
||||||
|
#define to_mISDNstack(d) container_of(d, mISDNstack_t, class_dev)
|
||||||
|
|
||||||
|
static ssize_t show_st_id(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(class_dev);
|
||||||
|
return sprintf(buf, "%08x\n", st->id);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(id, S_IRUGO, show_st_id, NULL);
|
||||||
|
|
||||||
|
static ssize_t show_st_status(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(class_dev);
|
||||||
|
return sprintf(buf, "0x%08lx\n", st->status);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t store_st_status(struct class_device *class_dev, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(class_dev);
|
||||||
|
ulong status;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
status = simple_strtol(buf, NULL, 0);
|
||||||
|
printk(KERN_DEBUG "%s: status %08lx\n", __FUNCTION__, status);
|
||||||
|
if (status == (1<<mISDN_STACK_INIT)) {
|
||||||
|
/* we want to make st->new_pid activ */
|
||||||
|
err = clear_stack(st, 1);
|
||||||
|
if (err) {
|
||||||
|
int_errtxt("clear_stack:%d", err);
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
err = set_stack(st ,&st->new_pid);
|
||||||
|
if (err) {
|
||||||
|
int_errtxt("set_stack:%d", err);
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
return(count);
|
||||||
|
} else if (status == (1<<mISDN_STACK_THREADSTART)) {
|
||||||
|
/* we want to start a new process after abort */
|
||||||
|
err = mISDN_start_stack_thread(st);
|
||||||
|
if (err) {
|
||||||
|
int_errtxt("start_stack_thread:%d", err);
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
return(count);
|
||||||
|
}
|
||||||
|
st->status = status;
|
||||||
|
wake_up_interruptible(&st->workq);
|
||||||
|
return(count);
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(status, S_IRUGO | S_IWUSR, show_st_status, store_st_status);
|
||||||
|
|
||||||
|
static ssize_t store_st_protocol(struct class_device *class_dev, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(class_dev);
|
||||||
|
ulong tmp;
|
||||||
|
char *p = (char *)buf;
|
||||||
|
u_int i;
|
||||||
|
|
||||||
|
memset(&st->new_pid.protocol, 0, (MAX_LAYER_NR + 1)*sizeof(st->new_pid.protocol[0]));
|
||||||
|
for (i=0; i<=MAX_LAYER_NR; i++) {
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
tmp = simple_strtol(p, &p, 0);
|
||||||
|
st->new_pid.protocol[i] = tmp;
|
||||||
|
if (*p)
|
||||||
|
p++;
|
||||||
|
}
|
||||||
|
if (*p)
|
||||||
|
int_errtxt("overflow");
|
||||||
|
return(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t store_st_layermask(struct class_device *class_dev, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(class_dev);
|
||||||
|
ulong mask = (1<<(MAX_LAYER_NR + 1)) -1;
|
||||||
|
|
||||||
|
st->new_pid.layermask = simple_strtol(buf, NULL, 0);
|
||||||
|
if (st->new_pid.layermask > mask) {
|
||||||
|
int_errtxt("overflow");
|
||||||
|
st->new_pid.layermask &= mask;
|
||||||
|
}
|
||||||
|
return(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ssize_t store_st_parameter(struct class_device *class_dev, const char *buf, size_t count)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(class_dev);
|
||||||
|
ulong tmp;
|
||||||
|
char *d, *p = (char *)buf;
|
||||||
|
u_int i, j, l;
|
||||||
|
|
||||||
|
memset(&st->new_pid.param, 0, (MAX_LAYER_NR + 1)*sizeof(st->new_pid.param[0]));
|
||||||
|
kfree(st->new_pid.pbuf);
|
||||||
|
l = 0;
|
||||||
|
for (i=0; i<=MAX_LAYER_NR; i++) {
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
tmp = simple_strtol(p, &p, 0);
|
||||||
|
if (*p)
|
||||||
|
p++;
|
||||||
|
if (tmp) {
|
||||||
|
j = tmp;
|
||||||
|
l += j+1;
|
||||||
|
while(j--) {
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
tmp = simple_strtol(p, &p, 0);
|
||||||
|
if (*p)
|
||||||
|
p++;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (*p)
|
||||||
|
int_errtxt("overflow");
|
||||||
|
if (l == 0) {
|
||||||
|
st->new_pid.maxplen = 0;
|
||||||
|
return(count);
|
||||||
|
}
|
||||||
|
st->new_pid.pbuf = kmalloc(l, GFP_ATOMIC);
|
||||||
|
if (!st->new_pid.pbuf)
|
||||||
|
return(-ENOMEM);
|
||||||
|
st->new_pid.maxplen = l;
|
||||||
|
d = st->new_pid.pbuf;
|
||||||
|
memset(d, 0, l);
|
||||||
|
p = (char *)buf;
|
||||||
|
for (i=0; i<=MAX_LAYER_NR; i++) {
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
tmp = simple_strtol(p, &p, 0);
|
||||||
|
if (*p)
|
||||||
|
p++;
|
||||||
|
if (tmp) {
|
||||||
|
j = tmp;
|
||||||
|
st->new_pid.param[i] = d;
|
||||||
|
*d++ = tmp & 0xff;
|
||||||
|
while(j--) {
|
||||||
|
if (!*p)
|
||||||
|
break;
|
||||||
|
tmp = simple_strtol(p, &p, 0);
|
||||||
|
*d++ = tmp & 0xff;
|
||||||
|
if (*p)
|
||||||
|
p++;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(count);
|
||||||
|
}
|
||||||
|
|
||||||
|
MISDN_PROTO(mISDNstack, pid, S_IRUGO);
|
||||||
|
MISDN_PROTO(mISDNstack, new_pid, S_IRUGO);
|
||||||
|
|
||||||
|
static ssize_t show_st_qlen(struct class_device *class_dev, char *buf)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(class_dev);
|
||||||
|
return sprintf(buf, "%d\n", skb_queue_len(&st->msgq));
|
||||||
|
}
|
||||||
|
static CLASS_DEVICE_ATTR(qlen, S_IRUGO, show_st_qlen, NULL);
|
||||||
|
|
||||||
|
static void release_mISDN_stack(struct class_device *dev)
|
||||||
|
{
|
||||||
|
mISDNstack_t *st = to_mISDNstack(dev);
|
||||||
|
char name[12];
|
||||||
|
|
||||||
|
sysfs_remove_group(&st->class_dev.kobj, &pid_group);
|
||||||
|
sysfs_remove_group(&st->class_dev.kobj, &new_pid_group);
|
||||||
|
printk(KERN_INFO "release stack class dev %s\n", dev->class_id);
|
||||||
|
if (st->parent) {
|
||||||
|
sysfs_remove_link(&dev->kobj, "parent");
|
||||||
|
snprintf(name, 12, "child%d", (CHILD_ID_MASK & st->id) >> 16);
|
||||||
|
sysfs_remove_link(&st->parent->class_dev.kobj, name);
|
||||||
|
}
|
||||||
|
if (st->master) {
|
||||||
|
sysfs_remove_link(&dev->kobj, "master");
|
||||||
|
snprintf(name, 12, "clone%d", (CLONE_ID_MASK & st->id) >> 16);
|
||||||
|
sysfs_remove_link(&st->master->class_dev.kobj, name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct class stack_dev_class = {
|
||||||
|
.name = "mISDN-stacks",
|
||||||
|
#ifndef CLASS_WITHOUT_OWNER
|
||||||
|
.owner = THIS_MODULE,
|
||||||
|
#endif
|
||||||
|
.release = &release_mISDN_stack,
|
||||||
|
};
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_register_sysfs_stack(mISDNstack_t *st)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
char name[12];
|
||||||
|
|
||||||
|
st->class_dev.class = &stack_dev_class;
|
||||||
|
if (st->id & FLG_CHILD_STACK)
|
||||||
|
snprintf(st->class_dev.class_id, BUS_ID_SIZE, "chst-%08x", st->id);
|
||||||
|
else if (st->id & FLG_CLONE_STACK)
|
||||||
|
snprintf(st->class_dev.class_id, BUS_ID_SIZE, "clst-%08x", st->id);
|
||||||
|
else
|
||||||
|
snprintf(st->class_dev.class_id, BUS_ID_SIZE, "st-%08x", st->id);
|
||||||
|
if (st->mgr)
|
||||||
|
st->class_dev.dev = st->mgr->class_dev.dev;
|
||||||
|
err = class_device_register(&st->class_dev);
|
||||||
|
if (err)
|
||||||
|
return(err);
|
||||||
|
err = sysfs_create_group(&st->class_dev.kobj, &pid_group);
|
||||||
|
if (err)
|
||||||
|
goto out_unreg;
|
||||||
|
mISDNstack_attr_protocol_new_pid.attr.mode |= S_IWUSR;
|
||||||
|
mISDNstack_attr_protocol_new_pid.store = store_st_protocol;
|
||||||
|
mISDNstack_attr_parameter_new_pid.attr.mode |= S_IWUSR;
|
||||||
|
mISDNstack_attr_parameter_new_pid.store = store_st_parameter;
|
||||||
|
mISDNstack_attr_layermask_new_pid.attr.mode |= S_IWUSR;
|
||||||
|
mISDNstack_attr_layermask_new_pid.store = store_st_layermask;
|
||||||
|
err = sysfs_create_group(&st->class_dev.kobj, &new_pid_group);
|
||||||
|
if (err)
|
||||||
|
goto out_unreg;
|
||||||
|
class_device_create_file(&st->class_dev, &class_device_attr_id);
|
||||||
|
class_device_create_file(&st->class_dev, &class_device_attr_qlen);
|
||||||
|
class_device_create_file(&st->class_dev, &class_device_attr_status);
|
||||||
|
if (st->parent) {
|
||||||
|
sysfs_create_link(&st->class_dev.kobj, &st->parent->class_dev.kobj, "parent");
|
||||||
|
snprintf(name, 12, "child%d", (CHILD_ID_MASK & st->id) >> 16);
|
||||||
|
sysfs_create_link(&st->parent->class_dev.kobj, &st->class_dev.kobj, name);
|
||||||
|
}
|
||||||
|
if (st->master) {
|
||||||
|
sysfs_create_link(&st->class_dev.kobj, &st->master->class_dev.kobj, "master");
|
||||||
|
snprintf(name, 12, "clone%d", (CLONE_ID_MASK & st->id) >> 16);
|
||||||
|
sysfs_create_link(&st->master->class_dev.kobj, &st->class_dev.kobj, name);
|
||||||
|
}
|
||||||
|
return(err);
|
||||||
|
|
||||||
|
out_unreg:
|
||||||
|
class_device_unregister(&st->class_dev);
|
||||||
|
return(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mISDN_unregister_sysfs_st(mISDNstack_t *st)
|
||||||
|
{
|
||||||
|
class_device_unregister(&st->class_dev);
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
mISDN_sysfs_st_init(void)
|
||||||
|
{
|
||||||
|
return(class_register(&stack_dev_class));
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
mISDN_sysfs_st_cleanup(void)
|
||||||
|
{
|
||||||
|
class_unregister(&stack_dev_class);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,134 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* PCI2PI Pci Bridge support for xhfc_su.c
|
||||||
|
*
|
||||||
|
* Authors : Martin Bachem, Joerg Ciesielski
|
||||||
|
* Contact : info@colognechip.com
|
||||||
|
*
|
||||||
|
* 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, 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/config.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/delay.h>
|
||||||
|
#include "xhfc_su.h"
|
||||||
|
#include "xhfc_pci2pi.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
static PCI2PI_cfg PCI2PI_config = {
|
||||||
|
/* default PI_INTELMX config */
|
||||||
|
.del_cs = 0,
|
||||||
|
.del_rd = 0,
|
||||||
|
.del_wr = 0,
|
||||||
|
.del_ale = 0,
|
||||||
|
.del_adr = 0,
|
||||||
|
.del_dout = 0,
|
||||||
|
.default_adr = 0x00,
|
||||||
|
.default_dout = 0x00,
|
||||||
|
.pi_mode = PI_MODE,
|
||||||
|
.setup = 1,
|
||||||
|
.hold = 1,
|
||||||
|
.cycle = 1,
|
||||||
|
.ale_adr_first = 0,
|
||||||
|
.ale_adr_setup = 0,
|
||||||
|
.ale_adr_hold = 1,
|
||||||
|
.ale_adr_wait = 0,
|
||||||
|
.pause_seq = 1,
|
||||||
|
.pause_end = 0,
|
||||||
|
.gpio_out = 0,
|
||||||
|
.status_int_enable = 1,
|
||||||
|
.pi_int_pol = 0,
|
||||||
|
.pi_wait_enable = 0,
|
||||||
|
.spi_cfg0 = 0,
|
||||||
|
.spi_cfg1 = 0,
|
||||||
|
.spi_cfg2 = 0,
|
||||||
|
.spi_cfg3 = 0,
|
||||||
|
.eep_recover = 4,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/***********************************/
|
||||||
|
/* initialise the XHFC PCI Bridge */
|
||||||
|
/* return 0 on success. */
|
||||||
|
/***********************************/
|
||||||
|
int
|
||||||
|
init_pci_bridge(xhfc_hw * hw)
|
||||||
|
{
|
||||||
|
int err = -ENODEV;
|
||||||
|
|
||||||
|
printk(KERN_INFO "%s %s: using PCI2PI Bridge at 0x%p\n",
|
||||||
|
hw->card_name, __FUNCTION__, hw->hw_membase);
|
||||||
|
|
||||||
|
/* test if Bridge regsiter accessable */
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_CS, 0x0);
|
||||||
|
if (ReadPCI2PI_u32(hw, PCI2PI_DEL_CS) == 0x00) {
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_CS, 0xFFFFFFFF);
|
||||||
|
if (ReadPCI2PI_u32(hw, PCI2PI_DEL_CS) == 0xF) {
|
||||||
|
err = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (err)
|
||||||
|
return (err);
|
||||||
|
|
||||||
|
/* enable hardware reset XHFC */
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_GPIO_OUT, GPIO_OUT_VAL);
|
||||||
|
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_PI_MODE, PCI2PI_config.pi_mode);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_CS, PCI2PI_config.del_cs);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_RD, PCI2PI_config.del_rd);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_WR, PCI2PI_config.del_wr);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_ALE, PCI2PI_config.del_ale);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_ADR, PCI2PI_config.del_adr);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEL_DOUT, PCI2PI_config.del_dout);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEFAULT_ADR, PCI2PI_config.default_adr);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_DEFAULT_DOUT,
|
||||||
|
PCI2PI_config.default_dout);
|
||||||
|
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_CYCLE_SHD, 0x80 * PCI2PI_config.setup
|
||||||
|
+ 0x40 * PCI2PI_config.hold + PCI2PI_config.cycle);
|
||||||
|
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_ALE_ADR_WHSF,
|
||||||
|
PCI2PI_config.ale_adr_first +
|
||||||
|
PCI2PI_config.ale_adr_setup * 2 +
|
||||||
|
PCI2PI_config.ale_adr_hold * 4 +
|
||||||
|
PCI2PI_config.ale_adr_wait * 8);
|
||||||
|
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_CYCLE_PAUSE,
|
||||||
|
0x10 * PCI2PI_config.pause_seq +
|
||||||
|
PCI2PI_config.pause_end);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_STATUS_INT_ENABLE,
|
||||||
|
PCI2PI_config.status_int_enable);
|
||||||
|
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_PI_INT_POL,
|
||||||
|
2 * PCI2PI_config.pi_wait_enable +
|
||||||
|
PCI2PI_config.pi_int_pol);
|
||||||
|
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_CFG0, PCI2PI_config.spi_cfg0);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_CFG1, PCI2PI_config.spi_cfg1);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_CFG2, PCI2PI_config.spi_cfg2);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_CFG3, PCI2PI_config.spi_cfg3);
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_EEP_RECOVER, PCI2PI_config.eep_recover);
|
||||||
|
ReadPCI2PI_u32(hw, PCI2PI_STATUS);
|
||||||
|
|
||||||
|
|
||||||
|
/* release hardware reset XHFC */
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_GPIO_OUT, GPIO_OUT_VAL | PCI2PI_GPIO7_NRST);
|
||||||
|
udelay(10);
|
||||||
|
|
||||||
|
return (err);
|
||||||
|
}
|
|
@ -0,0 +1,674 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* PCI2PI Pci Bridge support for xhfc_su.c
|
||||||
|
*
|
||||||
|
* Authors : Martin Bachem, Joerg Ciesielski
|
||||||
|
* Contact : info@colognechip.com
|
||||||
|
*
|
||||||
|
* 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, 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _XHFC_PCI2PI_H_
|
||||||
|
#define _XHFC_PCI2PI_H_
|
||||||
|
|
||||||
|
#include "xhfc24succ.h"
|
||||||
|
|
||||||
|
/* differnt PCI modes supported by PCI2PI */
|
||||||
|
#define PI_INTELNOMX 0
|
||||||
|
#define PI_INTELMX 1
|
||||||
|
#define PI_MOT 2
|
||||||
|
#define PI_MOTMX 3
|
||||||
|
#define PI_SPI 4
|
||||||
|
#define PI_EEPPRG 6
|
||||||
|
#define PI_AUTOEEP 7
|
||||||
|
|
||||||
|
/* PI_MODE to GPIO mapping */
|
||||||
|
#define PI_INTELMX_GPIO PCI2PI_GPIO3_MODE1 /* ALE pulse switches to MULTIPLEXED */
|
||||||
|
#define PI_INTELNOMX_GPIO PCI2PI_GPIO3_MODE1
|
||||||
|
#define PI_MOT_GPIO PCI2PI_GPIO2_MODE0
|
||||||
|
#define PI_MOTMX_GPIO PCI2PI_GPIO2_MODE0
|
||||||
|
#define PI_SPI_GPIO 0
|
||||||
|
#define PI_AUTOEEP_GPIO PCI2PI_GPIO2_MODE0 | PCI2PI_GPIO3_MODE1
|
||||||
|
|
||||||
|
|
||||||
|
/* PCI2PI GPIO to XHFC signal mapping */
|
||||||
|
#define PCI2PI_GPIO7_NRST 0x80
|
||||||
|
#define PCI2PI_GPIO6_TLA3 0x40
|
||||||
|
#define PCI2PI_GPIO5_TLA2 0x20
|
||||||
|
#define PCI2PI_GPIO4_TLA1 0x10
|
||||||
|
#define PCI2PI_GPIO3_MODE1 0x08
|
||||||
|
#define PCI2PI_GPIO2_MODE0 0x04
|
||||||
|
#define PCI2PI_GPIO1_BOND1 0x02
|
||||||
|
#define PCI2PI_GPIO0_BOND0 0x01
|
||||||
|
|
||||||
|
/* PCI2PI GPIO XHFC Bond out selection */
|
||||||
|
#define XHFC_1SU_BOND 0
|
||||||
|
#define XHFC_2SU_BOND PCI2PI_GPIO0_BOND0
|
||||||
|
#define XHFC_2S4U_BOND PCI2PI_GPIO1_BOND1
|
||||||
|
#define XHFC_4SU_BOND PCI2PI_GPIO1_BOND1 | PCI2PI_GPIO0_BOND0
|
||||||
|
|
||||||
|
|
||||||
|
/*******************************************************/
|
||||||
|
/*******************************************************/
|
||||||
|
|
||||||
|
/* Select processor interface mode and Bond option */
|
||||||
|
/* of PCI2PI bridge */
|
||||||
|
#define PI_MODE PI_INTELMX
|
||||||
|
#define XHFC_BOND XHFC_4SU_BOND
|
||||||
|
|
||||||
|
/*******************************************************/
|
||||||
|
/*******************************************************/
|
||||||
|
|
||||||
|
#if (PI_MODE == PI_INTELNOMX)
|
||||||
|
#define GPIO_OUT_VAL XHFC_BOND | PI_INTELNOMX_GPIO
|
||||||
|
|
||||||
|
#elif (PI_MODE == PI_INTELMX)
|
||||||
|
#define GPIO_OUT_VAL XHFC_BOND | PI_INTELMX_GPIO
|
||||||
|
|
||||||
|
#elif (PI_MODE == PI_MOT)
|
||||||
|
#define GPIO_OUT_VAL XHFC_BOND | PI_MOT_GPIO
|
||||||
|
|
||||||
|
#elif (PI_MODE == PI_MOTMX)
|
||||||
|
#define GPIO_OUT_VAL XHFC_BOND | PI_MOTMX_GPIO
|
||||||
|
|
||||||
|
#elif (PI_MODE == PI_SPI)
|
||||||
|
#define GPIO_OUT_VAL XHFC_BOND | PI_SPI_GPIO
|
||||||
|
|
||||||
|
#elif (PI_MODE == PI_AUTOEEP)
|
||||||
|
#define GPIO_OUT_VAL XHFC_BOND | PI_AUTOEEP_GPIO
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*******************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#define PCI2PI_VENDORID 0x1397
|
||||||
|
|
||||||
|
#define PCI2PI_DEVICEID 0xA003
|
||||||
|
#define MAX_PCI2PI 2
|
||||||
|
|
||||||
|
#define RV_PCI2PI_OK 0
|
||||||
|
#define RV_PCI2PI_ERROR 1
|
||||||
|
|
||||||
|
/* PCI2PI register definitions */
|
||||||
|
#define PCI2PI_OFFSET 0x00000800
|
||||||
|
#define PCI2PI_DEL_CS 4*0x00 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_DEL_RD 4*0x01 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_DEL_WR 4*0x02 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_DEL_ALE 4*0x03 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_DEL_ADR 4*0x04 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_DEL_DOUT 4*0x05 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_DEFAULT_ADR 4*0x06 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_DEFAULT_DOUT 4*0x07 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
PCI2PI_PI_MODE bit 2..0:
|
||||||
|
000: Intel non multiplexed
|
||||||
|
001: Intel multiplexed
|
||||||
|
010: Motorola
|
||||||
|
100: SPI Motorola
|
||||||
|
110: EEPROM programming mode throug PCIIF00 Target
|
||||||
|
111: XHFC AutoEEPROM mode
|
||||||
|
*/
|
||||||
|
#define PCI2PI_PI_MODE 4*0x08 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
#define PCI2PI_CYCLE_SHD 4*0x09 + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_ALE_ADR_WHSF 4*0x0a + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_CYCLE_PAUSE 4*0x0b + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_GPIO_OUT 4*0x0c + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_G1 4*0x0e + PCI2PI_OFFSET
|
||||||
|
#define PCI2PI_G0 4*0x0f + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/*
|
||||||
|
bit0: is set by PI_INT active, is reseted by reading this register
|
||||||
|
bit1: is set by PI_WAIT active, is reseted by reading this register
|
||||||
|
*/
|
||||||
|
#define PCI2PI_STATUS 4*0x10 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
|
||||||
|
/* bit0: enable PCI interrupt output */
|
||||||
|
#define PCI2PI_STATUS_INT_ENABLE 4*0x11 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/*
|
||||||
|
bit0: 0 = low active interrupt is detected at PI_INT
|
||||||
|
bit0: 1 = high active interrupt is detected at PI_INT
|
||||||
|
*/
|
||||||
|
#define PCI2PI_PI_INT_POL 4*0x12 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/* SPI registers */
|
||||||
|
/* 32 bit SPI master data output register */
|
||||||
|
#define PCI2PI_SPI_MO_DATA 4*0x20 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/* 32 bit SPI master data input register */
|
||||||
|
#define PCI2PI_SPI_MI_DATA 4*0x21 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/*
|
||||||
|
bit 0: 0 SPI bits are processing on the serial input/output
|
||||||
|
bit 0: 1 SPI bits are processed, new data can be written or read
|
||||||
|
bit 1..31: unused
|
||||||
|
*/
|
||||||
|
#define PCI2PI_SPI_STATUS 4*0x22 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/*
|
||||||
|
bit 0: spi clock polarity, defines level for SPISEL1
|
||||||
|
bit 1: spi clock phase, defines sampling edge, 0?, 1?
|
||||||
|
bit 2: 0MSB first (default) , 1LSB first
|
||||||
|
bit 3: 1SPI clock permanent during SPISEL1
|
||||||
|
*/
|
||||||
|
#define PCI2PI_SPI_CFG0 4*0x28 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/*
|
||||||
|
bit 0..3: spi clock frequency, SPI clock period (value+1) x 2 x PCIperiod
|
||||||
|
0: 2 PCIperiods
|
||||||
|
1:
|
||||||
|
*/
|
||||||
|
#define PCI2PI_SPI_CFG1 4*0x29 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/* bit 0..3: SPI Device SEL: defines level of D0..D3, XHFC SPI address */
|
||||||
|
#define PCI2PI_SPI_CFG2 4*0x2A + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/*
|
||||||
|
bit 0: 1spi master out permanent driven
|
||||||
|
bit 1: 1SPISEL remains low between bytes of a sequence
|
||||||
|
bit 2: 1SPISEL remains low permanent
|
||||||
|
*/
|
||||||
|
#define PCI2PI_SPI_CFG3 4*0x2B + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
/* bit 0..3: default 0100 */
|
||||||
|
#define PCI2PI_EEP_RECOVER 4*0x30 + PCI2PI_OFFSET
|
||||||
|
|
||||||
|
typedef struct _PCI2PI_cfg {
|
||||||
|
__u32 del_cs; /* Bit 3..0, bit 3: 0.5 PCI clk,
|
||||||
|
bits 2..0: gate delay */
|
||||||
|
__u32 del_rd;
|
||||||
|
__u32 del_wr;
|
||||||
|
__u32 del_ale;
|
||||||
|
|
||||||
|
__u32 del_adr; /* delay between default address
|
||||||
|
value and selected address */
|
||||||
|
__u32 del_dout; /* delay between default data
|
||||||
|
value and written data */
|
||||||
|
__u32 default_adr; /* default address value bit 0..7 */
|
||||||
|
|
||||||
|
__u32 default_dout; /* default data value bit 0..7 */
|
||||||
|
|
||||||
|
|
||||||
|
__u32 pi_mode; /* pi_mode bit 2..0:
|
||||||
|
000: Intel non multiplexed
|
||||||
|
001: Intel multiplexed
|
||||||
|
010: Motorola
|
||||||
|
100: SPI Motorola
|
||||||
|
110: EEPROM programming mode throug PCIIF00 Target
|
||||||
|
111: XHFC AutoEEPROM mode
|
||||||
|
*/
|
||||||
|
|
||||||
|
__u32 setup; /* address/data setup berfore rd/wr/cs,
|
||||||
|
0 or 1 PCI clk */
|
||||||
|
|
||||||
|
__u32 hold; /* address/data hold after rd/wr/cs,
|
||||||
|
0 or 1 PCI clk */
|
||||||
|
|
||||||
|
__u32 cycle; /* bit 0 only
|
||||||
|
address/data adds 0 or 1 PCI clk to /rd/wr/cs
|
||||||
|
access time (cycle+1) PCI clk
|
||||||
|
*/
|
||||||
|
|
||||||
|
__u32 ale_adr_first; /* bit 0 = 0: address valid before ALE=1
|
||||||
|
bit 0 = 1: ALE=1 before adress valid */
|
||||||
|
|
||||||
|
__u32 ale_adr_setup; /* bit 0 = 0 setup for ALE/addr = 0s
|
||||||
|
bit 0 = 1:setup for ALE/addr = 1 PCI clk
|
||||||
|
ALE/addr depends on ale_adr_first setting */
|
||||||
|
|
||||||
|
__u32 ale_adr_hold; /* bit 0 = 0 hold for address after ALE: 0s
|
||||||
|
bit 0 = 1:hold for address after ALE: 1 PCI clk */
|
||||||
|
|
||||||
|
__u32 ale_adr_wait; /* bit 0 = 0: 0 PCI clk delay between address and data phase
|
||||||
|
bit 0 = 1: 1 PCI clk delay between address and data phase */
|
||||||
|
|
||||||
|
__u32 pause_seq; /* bit 0..3: number of PCI clocks between read/write
|
||||||
|
acceses due to DWORD/WORD burst access */
|
||||||
|
|
||||||
|
__u32 pause_end; /* bit 0..3: number of PCI clocks after DWORD/WORD
|
||||||
|
burst access /delays PCI_TRDY signal */
|
||||||
|
|
||||||
|
__u32 gpio_out; /* bit 0..7: GPIO output value */
|
||||||
|
|
||||||
|
__u32 status_int_enable; /* bit 0: enables PCI interrupt for PI_INT signal
|
||||||
|
bit 1: enables PCI interrupt for PI_NWAIT signal */
|
||||||
|
|
||||||
|
__u32 pi_int_pol; /* bit 0: polarity of PI_INT signal */
|
||||||
|
|
||||||
|
__u32 pi_wait_enable; /* access length can be controled by /wait signal
|
||||||
|
0 wait disabled, 1 wait enabled */
|
||||||
|
|
||||||
|
__u32 spi_cfg0;
|
||||||
|
__u32 spi_cfg1;
|
||||||
|
__u32 spi_cfg2;
|
||||||
|
__u32 spi_cfg3;
|
||||||
|
__u32 eep_recover;
|
||||||
|
|
||||||
|
} PCI2PI_cfg;
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
read and write functions to access registers of the PCI bridge
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline __u8
|
||||||
|
ReadPCI2PI_u8(xhfc_hw * hw, __u16 reg_addr)
|
||||||
|
{
|
||||||
|
return (*(volatile __u8 *) (hw->membase + reg_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __u16
|
||||||
|
ReadPCI2PI_u16(xhfc_hw * hw, __u16 reg_addr)
|
||||||
|
{
|
||||||
|
return (*(volatile __u16 *) (hw->membase + reg_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline __u32
|
||||||
|
ReadPCI2PI_u32(xhfc_hw * hw, __u16 reg_addr)
|
||||||
|
{
|
||||||
|
return (*(volatile __u32 *) (hw->membase + reg_addr));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
WritePCI2PI_u8(xhfc_hw * hw, __u16 reg_addr, __u8 value)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + reg_addr)) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
WritePCI2PI_u16(xhfc_hw * hw, __u16 reg_addr, __u16 value)
|
||||||
|
{
|
||||||
|
*((volatile __u16 *) (hw->membase + reg_addr)) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
WritePCI2PI_u32(xhfc_hw * hw, __u16 reg_addr, __u32 value)
|
||||||
|
{
|
||||||
|
*((volatile __u32 *) (hw->membase + reg_addr)) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
read and write functions to access a XHFC at the local bus interface of the PCI
|
||||||
|
bridge
|
||||||
|
|
||||||
|
there are two sets of functions to access the XHFC in the following different
|
||||||
|
interface modes:
|
||||||
|
|
||||||
|
multiplexed bus interface modes PI_INTELMX and PI_MOTMX
|
||||||
|
- these modes use a single (atomic) PCI cycle to read or write a XHFC register
|
||||||
|
|
||||||
|
non multiplexed bus interface modes PI_INTELNOMX, PI_MOTMX and PI_SPI
|
||||||
|
|
||||||
|
- these modes use a separate PCI cycles to select the XHFC register and to read
|
||||||
|
or write data. That means these register accesses are non atomic and could be
|
||||||
|
interrupted by an interrupt. The driver must take care that a register access in
|
||||||
|
these modes is not interrupted by its own interrupt handler.
|
||||||
|
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#if ((PI_MODE==PI_INTELMX) || (PI_MODE==PI_MOTMX))
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
functions for multiplexed access
|
||||||
|
*/
|
||||||
|
static inline __u8
|
||||||
|
read_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
return (*(volatile __u8 *) (hw->membase + (reg_addr << 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
read four bytes from the same register address
|
||||||
|
e.g. A_FIFO_DATA
|
||||||
|
this function is only defined for software compatibility here
|
||||||
|
*/
|
||||||
|
static inline __u32
|
||||||
|
read32_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
__u32 value;
|
||||||
|
|
||||||
|
value = (*(volatile __u8 *) (hw->membase + (reg_addr << 2)));
|
||||||
|
value |= (*(volatile __u8 *) (hw->membase + (reg_addr << 2))) << 8;
|
||||||
|
value |= (*(volatile __u8 *) (hw->membase + (reg_addr << 2))) << 16;
|
||||||
|
value |= (*(volatile __u8 *) (hw->membase + (reg_addr << 2))) << 24;
|
||||||
|
|
||||||
|
return (value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
write_xhfc(xhfc_hw * hw, __u8 reg_addr, __u8 value)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + (reg_addr << 2))) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
writes four bytes to the same register address
|
||||||
|
e.g. A_FIFO_DATA
|
||||||
|
this function is only defined for software compatibility here
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
write32_xhfc(xhfc_hw * hw, __u8 reg_addr, __u32 value)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + (reg_addr << 2))) = value & 0xff;
|
||||||
|
*((volatile __u8 *) (hw->membase + (reg_addr << 2))) = (value >>8) & 0xff;
|
||||||
|
*((volatile __u8 *) (hw->membase + (reg_addr << 2))) = (value >>16) & 0xff;
|
||||||
|
*((volatile __u8 *) (hw->membase + (reg_addr << 2))) = (value >>24) & 0xff;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* always reads a single byte with short read method
|
||||||
|
this allows to read ram based registers
|
||||||
|
that normally requires long read access times
|
||||||
|
*/
|
||||||
|
static inline __u8
|
||||||
|
sread_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
(*(volatile __u8 *) (hw->membase + (reg_addr << 2)));
|
||||||
|
return (*(volatile __u8 *) (hw->membase + (R_INT_DATA << 2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this function reads the currently selected regsiter from XHFC and is only
|
||||||
|
required for non multiplexed access modes. For multiplexed access modes this
|
||||||
|
function is only defined for for software compatibility. */
|
||||||
|
|
||||||
|
static inline __u8
|
||||||
|
read_xhfcregptr(xhfc_hw * hw)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this function writes the XHFC register address pointer and is only
|
||||||
|
required for non multiplexed access modes. For multiplexed access modes this
|
||||||
|
function is only defined for for software compatibility. */
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
write_xhfcregptr(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* PI_MODE==PI_INTELMX || PI_MODE==PI_MOTMX */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
|
#if PI_MODE==PI_INTELNOMX || PI_MODE==PI_MOT
|
||||||
|
/*
|
||||||
|
functions for non multiplexed access
|
||||||
|
|
||||||
|
XHFC register address pointer is accessed with PCI address A2=1 and XHFC data
|
||||||
|
port is accessed with PCI address A2=0
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline __u8
|
||||||
|
read_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + 4)) = reg_addr;
|
||||||
|
return (*(volatile __u8 *) (hw->membase));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
read four bytes from the same register address by using a 32bit PCI access. The
|
||||||
|
PCI bridge generates for 8 bit data read cycles at the local bus interface.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline __u32
|
||||||
|
read32_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + 4)) = reg_addr;
|
||||||
|
return (*(volatile __u32 *) hw->membase);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
write_xhfc(xhfc_hw * hw, __u8 reg_addr, __u8 value)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + 4)) = reg_addr;
|
||||||
|
*((volatile __u8 *) (hw->membase)) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
writes four bytes to the same register address (e.g. A_FIFO_DATA) by using a
|
||||||
|
32bit PCI access. The PCI bridge generates for 8 bit data write cycles at the
|
||||||
|
local bus interface.
|
||||||
|
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
write32_xhfc(xhfc_hw * hw, __u8 reg_addr, __u32 value)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + 4)) = reg_addr;
|
||||||
|
*((volatile __u32 *) (hw->membase)) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
reads a single byte with short read method (r*). This allows to read ram based
|
||||||
|
registers that normally requires long read access times
|
||||||
|
|
||||||
|
*/
|
||||||
|
static inline __u8
|
||||||
|
sread_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
|
||||||
|
*((volatile __u8 *) (hw->membase + 4)) = reg_addr;
|
||||||
|
(*(volatile __u8 *) (hw->membase));
|
||||||
|
*((volatile __u8 *) (hw->membase + 4)) = R_INT_DATA;
|
||||||
|
return (*(volatile __u8 *) (hw->membase));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
this function reads the currently selected regsiter from XHFC
|
||||||
|
|
||||||
|
*/
|
||||||
|
static inline __u8
|
||||||
|
read_xhfcregptr(xhfc_hw * hw)
|
||||||
|
{
|
||||||
|
return (*(volatile __u8 *) (hw->membase + 4));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this function writes the XHFC register address pointer */
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
write_xhfcregptr(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
*((volatile __u8 *) (hw->membase + 4)) = reg_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* PI_MODE==PI_INTELNOMX || PI_MODE==PI_MOT */
|
||||||
|
|
||||||
|
|
||||||
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
#if PI_MODE == PI_SPI
|
||||||
|
|
||||||
|
// SPI mode transaction bit definitions
|
||||||
|
#define SPI_ADDR 0x40
|
||||||
|
#define SPI_DATA 0x00
|
||||||
|
#define SPI_RD 0x80
|
||||||
|
#define SPI_WR 0x00
|
||||||
|
#define SPI_BROAD 0x20
|
||||||
|
#define SPI_MULTI 0x20
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
functions for SPI access
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
static inline __u8
|
||||||
|
read_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 32 clock SPI master transfer
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_MO_DATA, ((SPI_ADDR | SPI_WR) << 24) | (reg_addr << 16) | ((SPI_DATA | SPI_RD) << 8));
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// read data from the SPI data receive register and return one byte
|
||||||
|
return (__u8) (ReadPCI2PI_u32(hw, PCI2PI_SPI_MI_DATA) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
read four bytes from the same register address by using a SPI multiple read access
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
static inline __u32
|
||||||
|
read32_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 16 clock SPI master transfer
|
||||||
|
WritePCI2PI_u16(hw, PCI2PI_SPI_MO_DATA, ((SPI_ADDR | SPI_WR) << 8) | reg_addr);
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 8 clock SPI master transfer
|
||||||
|
WritePCI2PI_u8(hw, PCI2PI_SPI_MO_DATA, (SPI_DATA | SPI_RD | SPI_MULTI));
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 32 clock SPI master transfer
|
||||||
|
// output data is arbitrary
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_MO_DATA, 0);
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// read data from the SPI data receive register and return four bytes
|
||||||
|
return (__u32) be32_to_cpu(ReadPCI2PI_u32(hw, PCI2PI_SPI_MI_DATA));
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
write_xhfc(xhfc_hw * hw, __u8 reg_addr, __u8 value)
|
||||||
|
{
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 32 clock SPI master transfer
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_MO_DATA, ((SPI_ADDR | SPI_WR) << 24) | (reg_addr << 16) | ((SPI_DATA | SPI_WR) << 8) | value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
writes four bytes to the same register address (e.g. A_FIFO_DATA) by using a SPI
|
||||||
|
multiple write access
|
||||||
|
|
||||||
|
*/
|
||||||
|
static inline void
|
||||||
|
write32_xhfc(xhfc_hw * hw, __u8 reg_addr, __u32 value)
|
||||||
|
{
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 16 clock SPI master transfer
|
||||||
|
WritePCI2PI_u16(hw, PCI2PI_SPI_MO_DATA, ((SPI_ADDR | SPI_WR) << 8) | reg_addr);
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 8 clock SPI master transfer
|
||||||
|
WritePCI2PI_u8(hw, PCI2PI_SPI_MO_DATA, (SPI_DATA | SPI_WR | SPI_MULTI));
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 32 clock SPI master transfer
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_MO_DATA, cpu_to_be32(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
reads a single byte with short read method (r*). This allows to read ram based
|
||||||
|
registers that normally requires long read access times
|
||||||
|
|
||||||
|
*/
|
||||||
|
static inline __u8
|
||||||
|
sread_xhfc(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 32 clock SPI master transfer
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_MO_DATA ,((SPI_ADDR | SPI_WR) << 24) | (reg_addr << 16) | ((SPI_DATA | SPI_RD) << 8));
|
||||||
|
|
||||||
|
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 32 clock SPI master transfer to read R_INT_DATA register
|
||||||
|
WritePCI2PI_u32(hw, PCI2PI_SPI_MO_DATA, ((SPI_ADDR | SPI_WR) << 24) | (R_INT_DATA << 16) | ((SPI_DATA | SPI_RD) << 8));
|
||||||
|
|
||||||
|
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// read data from the SPI data receive register and return one byte
|
||||||
|
return (__u8) (ReadPCI2PI_u32(hw, PCI2PI_SPI_MI_DATA) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
this function reads the currently selected regsiter from XHFC
|
||||||
|
|
||||||
|
*/
|
||||||
|
static inline __u8
|
||||||
|
read_xhfcregptr(xhfc_hw * hw)
|
||||||
|
{
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 16 clock SPI master transfer
|
||||||
|
WritePCI2PI_u16(hw, PCI2PI_SPI_MO_DATA, ((SPI_ADDR | SPI_RD) << 8));
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// read data from the SPI data receive register and return one byte
|
||||||
|
return (__u8) (ReadPCI2PI_u32(hw, PCI2PI_SPI_MI_DATA) & 0xFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* this function writes the XHFC register address pointer */
|
||||||
|
|
||||||
|
static inline void
|
||||||
|
write_xhfcregptr(xhfc_hw * hw, __u8 reg_addr)
|
||||||
|
{
|
||||||
|
// wait until SPI master is idle
|
||||||
|
while (!(ReadPCI2PI_u32(hw, PCI2PI_SPI_STATUS) & 1));
|
||||||
|
// initiate a 16 clock SPI master transfer
|
||||||
|
WritePCI2PI_u16(hw, PCI2PI_SPI_MO_DATA, ((SPI_ADDR | SPI_WR) << 8) | reg_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* PI_MODE == PI_SPI */
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
/* Function Prototypes */
|
||||||
|
int init_pci_bridge(xhfc_hw * hw);
|
||||||
|
|
||||||
|
#endif /* _XHFC_PCI2PI_H_ */
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,211 @@
|
||||||
|
/* $Id$
|
||||||
|
*
|
||||||
|
* mISDN driver for Colognechip xHFC chip
|
||||||
|
*
|
||||||
|
* Authors : Martin Bachem, Joerg Ciesielski
|
||||||
|
* Contact : info@colognechip.com
|
||||||
|
*
|
||||||
|
* 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, 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.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _XHFC_SU_H_
|
||||||
|
#define _XHFC_SU_H_
|
||||||
|
|
||||||
|
#include <linux/timer.h>
|
||||||
|
#include "channel.h"
|
||||||
|
#include "xhfc24succ.h"
|
||||||
|
|
||||||
|
#define DRIVER_NAME "XHFC"
|
||||||
|
|
||||||
|
#ifndef CHIP_ID_2S4U
|
||||||
|
#define CHIP_ID_2S4U 0x62
|
||||||
|
#endif
|
||||||
|
#ifndef CHIP_ID_4SU
|
||||||
|
#define CHIP_ID_4SU 0x63
|
||||||
|
#endif
|
||||||
|
#ifndef CHIP_ID_1SU
|
||||||
|
#define CHIP_ID_1SU 0x60
|
||||||
|
#endif
|
||||||
|
#ifndef CHIP_ID_2SU
|
||||||
|
#define CHIP_ID_2SU 0x61
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* define bridge for chip register access */
|
||||||
|
#define BRIDGE_UNKWOWN 0
|
||||||
|
#define BRIDGE_PCI2PI 1 /* used at Cologne Chip AG's Evaluation Card */
|
||||||
|
#define BRIDGE BRIDGE_PCI2PI
|
||||||
|
|
||||||
|
|
||||||
|
#define MAX_PORT 4
|
||||||
|
#define CHAN_PER_PORT 4 /* D, B1, B2, PCM */
|
||||||
|
#define MAX_CHAN MAX_PORT * CHAN_PER_PORT
|
||||||
|
|
||||||
|
/* flags in _u16 port mode */
|
||||||
|
#define PORT_UNUSED 0x0000
|
||||||
|
#define PORT_MODE_NT 0x0001
|
||||||
|
#define PORT_MODE_TE 0x0002
|
||||||
|
#define PORT_MODE_S0 0x0004
|
||||||
|
#define PORT_MODE_UP 0x0008
|
||||||
|
#define PORT_MODE_EXCH_POL 0x0010
|
||||||
|
#define PORT_MODE_LOOP 0x0020
|
||||||
|
#define NT_TIMER 0x8000
|
||||||
|
|
||||||
|
|
||||||
|
/* NT / TE defines */
|
||||||
|
#define NT_T1_COUNT 25 /* number of 4ms interrupts for G2 timeout */
|
||||||
|
#define CLK_DLY_TE 0x0e /* CLKDEL in TE mode */
|
||||||
|
#define CLK_DLY_NT 0x6c /* CLKDEL in NT mode */
|
||||||
|
#define STA_ACTIVATE 0x60 /* start activation in A_SU_WR_STA */
|
||||||
|
#define STA_DEACTIVATE 0x40 /* start deactivation in A_SU_WR_STA */
|
||||||
|
#define LIF_MODE_NT 0x04 /* Line Interface NT mode */
|
||||||
|
#define XHFC_TIMER_T3 8000 /* 8s activation timer T3 */
|
||||||
|
#define XHFC_TIMER_T4 500 /* 500ms deactivation timer T4 */
|
||||||
|
|
||||||
|
/* 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_TESTLOOP_B1 0x05
|
||||||
|
#define HFC_L1_TESTLOOP_B2 0x06
|
||||||
|
|
||||||
|
/* xhfc Layer1 Flags (stored in xhfc_port_t->l1_flags) */
|
||||||
|
#define HFC_L1_ACTIVATING 1
|
||||||
|
#define HFC_L1_ACTIVATED 2
|
||||||
|
#define HFC_L1_DEACTTIMER 4
|
||||||
|
#define HFC_L1_ACTTIMER 8
|
||||||
|
|
||||||
|
#define FIFO_MASK_TX 0x55555555
|
||||||
|
#define FIFO_MASK_RX 0xAAAAAAAA
|
||||||
|
|
||||||
|
|
||||||
|
/* DEBUG flags, use combined value for module parameter debug=x */
|
||||||
|
#define DEBUG_HFC_INIT 0x0001
|
||||||
|
#define DEBUG_HFC_MODE 0x0002
|
||||||
|
#define DEBUG_HFC_S0_STATES 0x0004
|
||||||
|
#define DEBUG_HFC_IRQ 0x0008
|
||||||
|
#define DEBUG_HFC_FIFO_ERR 0x0010
|
||||||
|
#define DEBUG_HFC_DTRACE 0x2000
|
||||||
|
#define DEBUG_HFC_BTRACE 0x4000 /* very(!) heavy messageslog load */
|
||||||
|
#define DEBUG_HFC_FIFO 0x8000 /* very(!) heavy messageslog load */
|
||||||
|
|
||||||
|
#define USE_F0_COUNTER 1 /* akkumulate F0 counter diff every irq */
|
||||||
|
#define TRANSP_PACKET_SIZE 0 /* minium tranparent packet size for transmittion to upper layer */
|
||||||
|
|
||||||
|
|
||||||
|
/* private driver_data */
|
||||||
|
typedef struct {
|
||||||
|
int chip_id;
|
||||||
|
char *device_name;
|
||||||
|
} xhfc_param;
|
||||||
|
|
||||||
|
|
||||||
|
/* port struct for each S/U port */
|
||||||
|
typedef struct {
|
||||||
|
int idx;
|
||||||
|
|
||||||
|
__u8 dpid; /* DChannel Protocoll ID */
|
||||||
|
__u16 mode; /* NT/TE + ST/U */
|
||||||
|
int nt_timer;
|
||||||
|
|
||||||
|
u_long l1_flags;
|
||||||
|
struct timer_list t3_timer; /* timer 3 for activation/deactivation */
|
||||||
|
struct timer_list t4_timer; /* timer 4 for activation/deactivation */
|
||||||
|
|
||||||
|
/* chip registers */
|
||||||
|
reg_a_su_ctrl0 su_ctrl0;
|
||||||
|
reg_a_su_ctrl1 su_ctrl1;
|
||||||
|
reg_a_su_ctrl2 su_ctrl2;
|
||||||
|
reg_a_st_ctrl3 st_ctrl3;
|
||||||
|
} xhfc_port_t;
|
||||||
|
|
||||||
|
|
||||||
|
/* channel struct for each fifo */
|
||||||
|
typedef struct {
|
||||||
|
channel_t ch;
|
||||||
|
xhfc_port_t * port;
|
||||||
|
} xhfc_chan_t;
|
||||||
|
|
||||||
|
|
||||||
|
struct _xhfx_hw;
|
||||||
|
|
||||||
|
/**********************/
|
||||||
|
/* hardware structure */
|
||||||
|
/**********************/
|
||||||
|
typedef struct _xhfx_hw {
|
||||||
|
|
||||||
|
struct list_head list;
|
||||||
|
spinlock_t lock;
|
||||||
|
struct tasklet_struct tasklet; /* interrupt bottom half */
|
||||||
|
|
||||||
|
int cardnum;
|
||||||
|
__u8 param_idx; /* used to access module param arrays */
|
||||||
|
int ifnum;
|
||||||
|
__u8 testirq;
|
||||||
|
int irq;
|
||||||
|
int iobase;
|
||||||
|
int nt_mode;
|
||||||
|
u_char *membase;
|
||||||
|
u_char *hw_membase;
|
||||||
|
|
||||||
|
#if BRIDGE == BRIDGE_PCI2PI
|
||||||
|
struct pci_dev *pdev;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
xhfc_param driver_data;
|
||||||
|
char card_name[60];
|
||||||
|
|
||||||
|
int chip_id;
|
||||||
|
int num_ports; /* number of S and U interfaces */
|
||||||
|
int max_fifo; /* always 4 fifos per port */
|
||||||
|
__u8 max_z; /* fifo depth -1 */
|
||||||
|
|
||||||
|
xhfc_port_t port[MAX_PORT]; /* one for each Line intercace */
|
||||||
|
xhfc_chan_t chan[MAX_CHAN]; /* one each D/B/PCM channel */
|
||||||
|
|
||||||
|
__u32 irq_cnt; /* count irqs */
|
||||||
|
__u32 f0_cnt; /* last F0 counter value */
|
||||||
|
__u32 f0_akku; /* akkumulated f0 counter deltas */
|
||||||
|
|
||||||
|
/* chip registers */
|
||||||
|
reg_r_irq_ctrl irq_ctrl;
|
||||||
|
reg_r_misc_irqmsk misc_irqmsk; /* mask of enabled interrupt sources */
|
||||||
|
reg_r_misc_irq misc_irq; /* collect interrupt status bits */
|
||||||
|
|
||||||
|
reg_r_su_irqmsk su_irqmsk; /* mask of line interface state change interrupts */
|
||||||
|
reg_r_su_irq su_irq; /* collect interrupt status bits */
|
||||||
|
reg_r_ti_wd ti_wd; /* timer interval */
|
||||||
|
|
||||||
|
reg_r_pcm_md0 pcm_md0;
|
||||||
|
reg_r_pcm_md1 pcm_md1;
|
||||||
|
|
||||||
|
__u32 fifo_irq; /* fifo bl irq */
|
||||||
|
__u32 fifo_irqmsk; /* fifo bl irq */
|
||||||
|
|
||||||
|
} xhfc_hw;
|
||||||
|
|
||||||
|
|
||||||
|
/* function prototypes */
|
||||||
|
int setup_channel(xhfc_hw * hw, __u8 channel, int protocol);
|
||||||
|
void xhfc_write_fifo(xhfc_hw * hw, __u8 channel);
|
||||||
|
void xhfc_read_fifo(xhfc_hw * hw, __u8 channel);
|
||||||
|
void print_fc(xhfc_hw * hw, __u8 fifo);
|
||||||
|
void setup_fifo(xhfc_hw * hw, __u8 fifo, __u8 conhdlc, __u8 subcfg,
|
||||||
|
__u8 fifoctrl, __u8 enable);
|
||||||
|
void setup_su(xhfc_hw * hw, __u8 pt, __u8 bc, __u8 enable);
|
||||||
|
|
||||||
|
#endif /* _XHFC_SU_H_ */
|
|
@ -0,0 +1,544 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
#
|
||||||
|
# misdn-init init script
|
||||||
|
#
|
||||||
|
# Copyright (C) 2005, Nadi Sarrar
|
||||||
|
#
|
||||||
|
# Nadi Sarrar <nadi@beronet.com>
|
||||||
|
#
|
||||||
|
# This program is free software, distributed under the terms of
|
||||||
|
# the GNU General Public License
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# USAGE:
|
||||||
|
#
|
||||||
|
# /etc/init.d/misdn-init start|stop|restart|config|scan|help
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# CONFIGURATION:
|
||||||
|
#
|
||||||
|
# Path to your misdn-init.conf:
|
||||||
|
#
|
||||||
|
misdn_init_conf="/etc/misdn-init.conf"
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
|
||||||
|
|
||||||
|
MODPROBE=modprobe
|
||||||
|
RMMOD=rmmod
|
||||||
|
INSMOD=insmod
|
||||||
|
LSPCI=lspci
|
||||||
|
MKNOD=mknod
|
||||||
|
|
||||||
|
# HFC 8/4 (S0) Options
|
||||||
|
master_clock=16
|
||||||
|
|
||||||
|
# HFC-E1 Options
|
||||||
|
optical=16
|
||||||
|
los=18
|
||||||
|
ais=19
|
||||||
|
slip=20
|
||||||
|
nocrc4=24
|
||||||
|
|
||||||
|
# Card Settings
|
||||||
|
ulaw=8
|
||||||
|
dtmf=9
|
||||||
|
pcm_slave=11
|
||||||
|
|
||||||
|
|
||||||
|
function remove_preloaded_modules {
|
||||||
|
tmp=$(lsmod)
|
||||||
|
cards="hfcmulti hfcpci avmfritz w6692 hfcusb"
|
||||||
|
|
||||||
|
for i in $cards ; do
|
||||||
|
echo $tmp | grep $i >/dev/null && (echo "$i already loaded, removing it"; modprobe -r $i)
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function load_card_modules {
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
|
||||||
|
for line in $(sed -n -e '/^[^#]/p' ${misdn_init_conf});
|
||||||
|
do
|
||||||
|
var=`echo "${line}" | sed -e "s/^\(.*\)=.*/\1/"`
|
||||||
|
val=`echo "${line}" | sed -e "s/^.*=\(.*\)/\1/"`
|
||||||
|
|
||||||
|
case "${var}" in
|
||||||
|
card)
|
||||||
|
nr=`echo "${val}" | sed -e "s/^\([0-9]*\),.*/\1/"`
|
||||||
|
mod=`echo "${val}" | sed -e "s/^[^,]*,\([^,]*\).*/\1/"`
|
||||||
|
if [ ${#val} -gt $(echo "obase=10;${#nr}+${#mod}+1" | bc) ]; then
|
||||||
|
opns=`echo "${val}" | sed -e "s/^[^,]*,[^,]*,\(.*\)/\1/"`
|
||||||
|
else
|
||||||
|
opns=""
|
||||||
|
fi
|
||||||
|
case "${mod}" in
|
||||||
|
0x*)
|
||||||
|
hfcmulti[${nr}]=$(echo ${mod} | sed -e "s/^0x\([0-9]*\)/\1/")
|
||||||
|
let "hfcports = ${hfcports} + ${hfcmulti[${nr}]}"
|
||||||
|
IFS=$','
|
||||||
|
for li in ${opns}; do
|
||||||
|
hfcmulti[${nr}]=$(echo "obase=10;2^(${!li}-1)+${hfcmulti[${nr}]}" | bc)
|
||||||
|
done
|
||||||
|
IFS=$'\n'
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
other_card[${nr}]=${mod}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
te_ptp)
|
||||||
|
IFS=$','
|
||||||
|
for li in ${val}; do
|
||||||
|
layermask[${li}]="0xf"
|
||||||
|
protocol[${li}]=34 # 0x22 == 34
|
||||||
|
done
|
||||||
|
IFS=$'\n'
|
||||||
|
;;
|
||||||
|
te_ptmp)
|
||||||
|
IFS=$','
|
||||||
|
for li in ${val}; do
|
||||||
|
layermask[${li}]="0xf"
|
||||||
|
protocol[${li}]=2 # 0x2 == 2
|
||||||
|
done
|
||||||
|
IFS=$'\n'
|
||||||
|
;;
|
||||||
|
nt_*)
|
||||||
|
IFS=$','
|
||||||
|
for li in ${val}; do
|
||||||
|
layermask[${li}]="0x3"
|
||||||
|
protocol[${li}]=18 # 0x12 == 18
|
||||||
|
done
|
||||||
|
IFS=$'\n'
|
||||||
|
;;
|
||||||
|
te_capi_ptp)
|
||||||
|
IFS=$','
|
||||||
|
for li in ${val}; do
|
||||||
|
layermask[${li}]="0x0"
|
||||||
|
protocol[${li}]=34 # 0x22 == 34
|
||||||
|
done
|
||||||
|
IFS=$'\n'
|
||||||
|
|
||||||
|
addcapi=1
|
||||||
|
;;
|
||||||
|
te_capi_ptmp)
|
||||||
|
IFS=$','
|
||||||
|
for li in ${val}; do
|
||||||
|
layermask[${li}]="0x0"
|
||||||
|
protocol[${li}]=2 # 0x2 == 2
|
||||||
|
done
|
||||||
|
IFS=$'\n'
|
||||||
|
addcapi=1
|
||||||
|
;;
|
||||||
|
|
||||||
|
option)
|
||||||
|
port=`echo "${val}" | sed -e "s/^\([0-9]*\),.*/\1/"`
|
||||||
|
opt=`echo "${val}" | sed -e "s/^[0-9]*,\(.*\)/\1/"`
|
||||||
|
|
||||||
|
if [ -z ${protocol[${port}]} ]; then
|
||||||
|
protocol[${port}]="0"
|
||||||
|
fi
|
||||||
|
|
||||||
|
IFS=$','
|
||||||
|
for li in ${opt}; do
|
||||||
|
protocol[${port}]=$(echo "obase=10;2^(${!li}-1)+${protocol[${port}]}" | bc)
|
||||||
|
done
|
||||||
|
IFS=$'\n'
|
||||||
|
;;
|
||||||
|
poll)
|
||||||
|
poll=${val}
|
||||||
|
;;
|
||||||
|
pcm)
|
||||||
|
pcm=${val}
|
||||||
|
;;
|
||||||
|
debug)
|
||||||
|
debug=${val}
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
echo "unknown variable: ${var}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "-----------------------------------------"
|
||||||
|
echo " Loading module(s) for your misdn-cards:"
|
||||||
|
echo "-----------------------------------------"
|
||||||
|
|
||||||
|
card_index=1
|
||||||
|
port_index=1
|
||||||
|
while [ ! -z ${hfcmulti[${card_index}]} ] || [ ! -z ${other_card[${card_index}]} ];
|
||||||
|
do
|
||||||
|
if [ ! -z ${hfcmulti[${card_index}]} ]; then
|
||||||
|
# MODPROBE COMMAND FOR hfcmulti CARD
|
||||||
|
hfcmulti_type="type="
|
||||||
|
hfcmulti_prot="protocol="
|
||||||
|
hfcmulti_layer="layermask="
|
||||||
|
while [ ! -z ${hfcmulti[${card_index}]} ];
|
||||||
|
do
|
||||||
|
hfcmulti_type="${hfcmulti_type}$(echo "obase=16;\"0x\";${hfcmulti[${card_index}]}" | bc ),"
|
||||||
|
let "card_index = ${card_index} + 1"
|
||||||
|
done
|
||||||
|
while [ ${hfcports} -gt 0 ];
|
||||||
|
do
|
||||||
|
if [ ! -z ${protocol[${port_index}]} ]; then
|
||||||
|
hfcmulti_prot="${hfcmulti_prot}$(echo "obase=16;\"0x\";${protocol[${port_index}]}" | bc),"
|
||||||
|
else
|
||||||
|
hfcmulti_prot="${hfcmulti_prot}0x2,"
|
||||||
|
fi
|
||||||
|
if [ ! -z ${layermask[${port_index}]} ]; then
|
||||||
|
hfcmulti_layer="${hfcmulti_layer}${layermask[${port_index}]},"
|
||||||
|
else
|
||||||
|
hfcmulti_layer="${hfcmulti_layer}0xf,"
|
||||||
|
fi
|
||||||
|
let "port_index = ${port_index} + 1"
|
||||||
|
let "hfcports = ${hfcports} - 1"
|
||||||
|
done
|
||||||
|
hfcmulti_type="$(echo ${hfcmulti_type} | sed -e 's/^\(.*\),$/\1/')"
|
||||||
|
hfcmulti_prot="$(echo ${hfcmulti_prot} | sed -e 's/^\(.*\),$/\1/')"
|
||||||
|
hfcmulti_layer="$(echo ${hfcmulti_layer} | sed -e 's/^\(.*\),$/\1/')"
|
||||||
|
hfcmulti_cmd="modprobe hfcmulti ${hfcmulti_type} ${hfcmulti_prot} ${hfcmulti_layer}"
|
||||||
|
if [ ! -z ${poll} ]; then
|
||||||
|
hfcmulti_cmd="${hfcmulti_cmd} poll=${poll}"
|
||||||
|
fi
|
||||||
|
if [ ! -z ${pcm} ]; then
|
||||||
|
hfcmulti_cmd="${hfcmulti_cmd} pcm=${pcm}"
|
||||||
|
fi
|
||||||
|
if [ ! -z ${debug} ]; then
|
||||||
|
hfcmulti_cmd="${hfcmulti_cmd} debug=${debug}"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo ${hfcmulti_cmd}
|
||||||
|
eval ${hfcmulti_cmd}
|
||||||
|
else
|
||||||
|
# MODPROBE COMMAND FOR _NON_ hfcmulti CARD
|
||||||
|
other_cmd="modprobe ${other_card[${card_index}]}"
|
||||||
|
if [ ! -z ${protocol[${port_index}]} ]; then
|
||||||
|
other_prot="protocol=$(echo "obase=16;\"0x\";${protocol[${port_index}]}" | bc),"
|
||||||
|
else
|
||||||
|
other_prot="protocol=0x2,"
|
||||||
|
fi
|
||||||
|
if [ ! -z ${layermask[${port_index}]} ]; then
|
||||||
|
other_layer="layermask=${layermask[${port_index}]},"
|
||||||
|
else
|
||||||
|
other_layer="layermask=0xf,"
|
||||||
|
fi
|
||||||
|
|
||||||
|
let "prev = ${card_index}"
|
||||||
|
let "card_index = ${card_index} + 1"
|
||||||
|
let "port_index = ${port_index} + 1"
|
||||||
|
while [ "${other_card[${card_index}]}" == "${other_card[${prev}]}" ];
|
||||||
|
do
|
||||||
|
if [ ! -z ${protocol[${port_index}]} ]; then
|
||||||
|
other_prot="${other_prot}$(echo "obase=16;\"0x\";${protocol[${port_index}]}" | bc),"
|
||||||
|
else
|
||||||
|
other_prot="${other_prot}0x2,"
|
||||||
|
fi
|
||||||
|
if [ ! -z ${layermask[${port_index}]} ]; then
|
||||||
|
other_layer="${other_layer}${layermask[${port_index}]},"
|
||||||
|
else
|
||||||
|
other_layer="${other_layer}0xf,"
|
||||||
|
fi
|
||||||
|
let "prev = ${card_index}"
|
||||||
|
let "card_index = ${card_index} + 1"
|
||||||
|
let "port_index = ${port_index} + 1"
|
||||||
|
done
|
||||||
|
|
||||||
|
other_prot="$(echo ${other_prot} | sed -e 's/^\(.*\),$/\1/')"
|
||||||
|
other_layer="$(echo ${other_layer} | sed -e 's/^\(.*\),$/\1/')"
|
||||||
|
other_cmd="${other_cmd} ${other_prot} ${other_layer}"
|
||||||
|
echo "${other_cmd}"
|
||||||
|
eval ${other_cmd}
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function unload_card_modules {
|
||||||
|
|
||||||
|
IFS=$'\n'
|
||||||
|
|
||||||
|
for line in $(sed -ne '/^[^#]/p' ${misdn_init_conf});
|
||||||
|
do
|
||||||
|
var=`echo "${line}" | sed -e "s/^\(.*\)=.*/\1/"`
|
||||||
|
val=`echo "${line}" | sed -e "s/^.*=\(.*\)/\1/"`
|
||||||
|
|
||||||
|
case "${var}" in
|
||||||
|
card)
|
||||||
|
nr=`echo "${val}" | sed -e "s/^\([0-9]*\),.*/\1/"`
|
||||||
|
mod=`echo "${val}" | sed -e "s/^[^,]*,\([^,]*\).*/\1/"`
|
||||||
|
case "${mod}" in
|
||||||
|
0x*)
|
||||||
|
modulelist[${nr}]=hfcmulti
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
modulelist[${nr}]=${mod}
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "-------------------------------------------"
|
||||||
|
echo " Unloading module(s) for your misdn-cards:"
|
||||||
|
echo "-------------------------------------------"
|
||||||
|
|
||||||
|
rmmod_cmd="${RMMOD} ${modulelist[1]}"
|
||||||
|
echo "${rmmod_cmd}"
|
||||||
|
eval ${rmmod_cmd}
|
||||||
|
|
||||||
|
index=2
|
||||||
|
prev=1
|
||||||
|
while [ ! -z ${modulelist[${index}]} ];
|
||||||
|
do
|
||||||
|
if [ ${modulelist[${index}]} != ${modulelist[${prev}]} ]; then
|
||||||
|
rmmod_cmd="${RMMOD} ${modulelist[${index}]}"
|
||||||
|
echo "${rmmod_cmd}"
|
||||||
|
eval ${rmmod_cmd}
|
||||||
|
fi
|
||||||
|
let "prev = ${index}"
|
||||||
|
let "index = ${index} + 1"
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_misdn_init_conf {
|
||||||
|
|
||||||
|
cardcount=1
|
||||||
|
cardconf=""
|
||||||
|
IFS=$'\n'
|
||||||
|
NL="
|
||||||
|
"
|
||||||
|
|
||||||
|
function die {
|
||||||
|
echo "[!!] ${1}"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
function addcard {
|
||||||
|
cardline="${cardline}card=${cardcount},${1}${NL}"
|
||||||
|
let "cardcount = ${cardcount} + 1"
|
||||||
|
}
|
||||||
|
|
||||||
|
function addport {
|
||||||
|
let "portcount = ${portcount} + ${1}"
|
||||||
|
}
|
||||||
|
|
||||||
|
portcount=0
|
||||||
|
|
||||||
|
for line in $(${LSPCI} -n -d 1397:30b1); do
|
||||||
|
addcard "0x1"
|
||||||
|
addport 1
|
||||||
|
done
|
||||||
|
for line in $(${LSPCI} -n -d 1397:08b4); do
|
||||||
|
addcard "0x4"
|
||||||
|
addport 4
|
||||||
|
done
|
||||||
|
for line in $(${LSPCI} -n -d 1397:16b8); do
|
||||||
|
addcard "0x8"
|
||||||
|
addport 8
|
||||||
|
done
|
||||||
|
for line in $(${LSPCI} -n | grep "1397:\(2bd\(0\|6\|7\|8\|9\|a\|b\|c\)\|b100\)\|1043:0675\|0871:ffa\(1\|2\)\|1051:0100\|15b0:2bd0\|114f:007\(0\|1\|2\|3\)\|13d1:2bd1\|182d:3069"); do
|
||||||
|
addcard "hfcpci"
|
||||||
|
addport 1
|
||||||
|
done
|
||||||
|
for line in $(${LSPCI} -n -d 1244:0xa00); do
|
||||||
|
addcard "avmfritz"
|
||||||
|
addport 1
|
||||||
|
done
|
||||||
|
for line in $(${LSPCI} -n -d 1050:6692); do
|
||||||
|
addcard "w6692pci"
|
||||||
|
addport 1
|
||||||
|
done
|
||||||
|
if [ "${1}" == "scan" ]; then
|
||||||
|
echo "[OK] found the following devices:"
|
||||||
|
echo "${cardline}[ii] run \"/etc/init.d/misdn-init config\" to store this information to ${misdn_init_conf}"
|
||||||
|
else
|
||||||
|
|
||||||
|
index=1
|
||||||
|
portline="te_ptmp="
|
||||||
|
while [ ${index} -le ${portcount} ]; do
|
||||||
|
portline="${portline}${index},"
|
||||||
|
let "index = ${index} + 1"
|
||||||
|
done
|
||||||
|
portline="$(echo ${portline} | sed -e 's/^\(.*\),$/\1/')"
|
||||||
|
|
||||||
|
misdn_cfg_pt1="#
|
||||||
|
# Configuration file for your misdn hardware
|
||||||
|
#
|
||||||
|
# Usage: /etc/init.d/misdn-init start|stop|restart|config|scan|help
|
||||||
|
#
|
||||||
|
|
||||||
|
#
|
||||||
|
# Card Settings
|
||||||
|
#
|
||||||
|
# Syntax: card=<number>,<type>[,<option>...]
|
||||||
|
#
|
||||||
|
# <number> count your cards beginning with 1
|
||||||
|
# <type> either 0x1,0x4 or 0x8 for your hfcmulti hardware,
|
||||||
|
# or the name of your card driver module.
|
||||||
|
# <option> ulaw - uLaw (instead of aLaw)
|
||||||
|
# dtmf - enable DTMF detection on all B-channels
|
||||||
|
# pcm_slave - set PCM bus into slave mode
|
||||||
|
#"
|
||||||
|
misdn_cfg_pt2="#
|
||||||
|
# Port settings
|
||||||
|
#
|
||||||
|
# Syntax: <port_type>=<port_number>[,<port_number>...]
|
||||||
|
#
|
||||||
|
# <port_type> te_ptp - TE-Mode, PTP
|
||||||
|
# te_ptmp - TE-Mode, PTMP
|
||||||
|
# te_capi_ptp - TE-Mode (capi), PTP
|
||||||
|
# te_capi_ptmp - TE-Mode (capi), PTMP
|
||||||
|
# nt_ptp - NT-Mode, PTP
|
||||||
|
# nt_ptmp - NT-Mode, PTMP
|
||||||
|
# <port_number> port that should be considered
|
||||||
|
#"
|
||||||
|
misdn_cfg_pt3="#
|
||||||
|
# Port Options
|
||||||
|
#
|
||||||
|
# Syntax: option=<port_number>,<option>[,<option>...]
|
||||||
|
#
|
||||||
|
# <option> master_clock - use master clock for this S/T interface
|
||||||
|
# (only once per chip, only for HFC 8/4)
|
||||||
|
# optical - optical (only HFC-E1)
|
||||||
|
# los - report LOS (only HFC-E1)
|
||||||
|
# ais - report AIS (only HFC-E1)
|
||||||
|
# slip - report SLIP (only HFC-E1)
|
||||||
|
# nocrc4 - turn off crc4 mode use double frame instead
|
||||||
|
# (only HFC-E1)
|
||||||
|
#
|
||||||
|
#option=1,master_clock
|
||||||
|
#option=2,ais,nocrc4
|
||||||
|
#option=3,optical,los,ais,slip
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# General Options for your hfcmulti hardware
|
||||||
|
#
|
||||||
|
# poll=<number>
|
||||||
|
#
|
||||||
|
# Only one poll value must be given for all cards.
|
||||||
|
# Give the number of samples for each fifo process.
|
||||||
|
# By default 128 is used. Decrease to reduce delay, increase to
|
||||||
|
# reduce cpu load. If unsure, don't mess with it!!!
|
||||||
|
# Valid is 32, 64, 128, 256.
|
||||||
|
#
|
||||||
|
# pcm=<number>
|
||||||
|
#
|
||||||
|
# Give the id of the PCM bus. All PCM busses with the same ID
|
||||||
|
# are expected to be connected and have equal slots.
|
||||||
|
# Only one chip of the PCM bus must be master, the others slave.
|
||||||
|
# -1 means no support of PCM bus.
|
||||||
|
#
|
||||||
|
# debug=<number>
|
||||||
|
#
|
||||||
|
# Enable debugging (see hfc_multi.h for debug options).
|
||||||
|
#
|
||||||
|
poll=128
|
||||||
|
#pcm=1
|
||||||
|
debug=0"
|
||||||
|
|
||||||
|
if [ -f ${misdn_init_conf} ]; then
|
||||||
|
cp "${misdn_init_conf}" "${misdn_init_conf}.save" || die "could not backup your existing ${misdn_init_conf}!"
|
||||||
|
echo "[OK] ${misdn_init_conf} already present. backing it up to ${misdn_init_conf}.save"
|
||||||
|
fi
|
||||||
|
echo "${misdn_cfg_pt1}${NL}${cardline}${NL}${misdn_cfg_pt2}${NL}${portline}${NL}${NL}${misdn_cfg_pt3}" > ${misdn_init_conf} || die "could not write to /etc/misdn-init.conf!"
|
||||||
|
#echo "${misdn_cfg_pt1}${NL}${cardline}${NL}${misdn_cfg_pt2}${NL}${portline}${NL}${NL}${misdn_cfg_pt3}" > testconf || die "could not write to /etc/misdn-init.conf!"
|
||||||
|
|
||||||
|
echo "[OK] ${misdn_init_conf} created. It's now safe to run \"/etc/init.d/misdn-init start\""
|
||||||
|
if [ ${portcount} -gt 1 ]; then
|
||||||
|
echo "[ii] make your ports (1-${portcount}) available in asterisk by editing \"/etc/asterisk/misdn.conf\""
|
||||||
|
elif [ ${portcount} -eq 1 ]; then
|
||||||
|
echo "[ii] make your port (1) available in asterisk by editing \"/etc/asterisk/misdn.conf\""
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
function check_cfg_file {
|
||||||
|
if [ ! -f ${misdn_init_conf} ]; then
|
||||||
|
echo "[!!] failed to load: ${misdn_init_conf}"
|
||||||
|
echo "run \"/etc/init.d/misdn-init config\" to scan your devices and generate a basic config file."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
# MAIN #############
|
||||||
|
|
||||||
|
case "$1" in
|
||||||
|
start|--start)
|
||||||
|
|
||||||
|
remove_preloaded_modules
|
||||||
|
|
||||||
|
check_cfg_file
|
||||||
|
|
||||||
|
$MODPROBE mISDN_core debug=0
|
||||||
|
$MODPROBE mISDN_l1 debug=0
|
||||||
|
$MODPROBE mISDN_l2 debug=0
|
||||||
|
$MODPROBE l3udss1 debug=0
|
||||||
|
$MODPROBE mISDN_dsp debug=0x0 options=0x0
|
||||||
|
$MODPROBE mISDN_capi
|
||||||
|
|
||||||
|
load_card_modules
|
||||||
|
|
||||||
|
sleep 1
|
||||||
|
|
||||||
|
if [ ! -e /dev/mISDN ]; then
|
||||||
|
$MKNOD /dev/mISDN c 46 0
|
||||||
|
echo "[i] creating device node: /dev/mISDN"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
|
||||||
|
stop|--stop)
|
||||||
|
|
||||||
|
check_cfg_file
|
||||||
|
|
||||||
|
unload_card_modules
|
||||||
|
|
||||||
|
for mod in $(lsmod | sed -ne '/Module/!{s/\([^ ]*\).*/\1/;p}');
|
||||||
|
do
|
||||||
|
case "${mod}" in
|
||||||
|
mISDN_capi | mISDN_dsp | l3udss1 | mISDN_l2 | mISDN_l1 | mISDN_isac | mISDN_core)
|
||||||
|
eval "${RMMOD} ${mod}"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
;;
|
||||||
|
|
||||||
|
restart|--restart)
|
||||||
|
|
||||||
|
check_cfg_file
|
||||||
|
|
||||||
|
sh $0 stop
|
||||||
|
sleep 2 # some phones will release tei when layer 1 is down
|
||||||
|
sh $0 start
|
||||||
|
;;
|
||||||
|
|
||||||
|
config|--config)
|
||||||
|
|
||||||
|
create_misdn_init_conf
|
||||||
|
|
||||||
|
;;
|
||||||
|
|
||||||
|
scan|--scan)
|
||||||
|
|
||||||
|
create_misdn_init_conf scan
|
||||||
|
|
||||||
|
;;
|
||||||
|
|
||||||
|
help|--help)
|
||||||
|
echo "Usage: $0 {start|stop|restart|config|scan|help}"
|
||||||
|
exit 0
|
||||||
|
;;
|
||||||
|
|
||||||
|
*)
|
||||||
|
echo "Usage: $0 {start|stop|restart|config|scan|help}"
|
||||||
|
exit 2
|
||||||
|
;;
|
||||||
|
|
||||||
|
esac
|
||||||
|
|
Loading…
Reference in New Issue