c36b6cd738
Signed-off-by: Oron Peled <oron.peled@xorcom.com> Acked-By: Tzafrir Cohen <tzafrir.cohen@xorcom.com> git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/branches/2.5@10391 a0bf4364-ded3-4de4-8d8a-66a801d63aff
1244 lines
33 KiB
C
1244 lines
33 KiB
C
/*
|
|
* Written by Oron Peled <oron@actcom.co.il>
|
|
* Copyright (C) 2004, Xorcom
|
|
*
|
|
* Derived from ztdummy
|
|
*
|
|
* Copyright (C) 2002, Hermes Softlab
|
|
* Copyright (C) 2004, Digium, Inc.
|
|
*
|
|
* All rights reserved.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
|
*
|
|
*/
|
|
#include <linux/version.h>
|
|
|
|
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0)
|
|
# warning "This module is tested only with 2.6 kernels"
|
|
#endif
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/module.h>
|
|
#include <linux/device.h>
|
|
#include <linux/init.h>
|
|
#include <linux/delay.h> /* for udelay */
|
|
#include <linux/interrupt.h>
|
|
#include <linux/proc_fs.h>
|
|
#include <dahdi/kernel.h>
|
|
#include "xbus-core.h"
|
|
#include "xproto.h"
|
|
#include "xpp_dahdi.h"
|
|
#include "parport_debug.h"
|
|
|
|
static const char rcsid[] = "$Id$";
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
struct proc_dir_entry *xpp_proc_toplevel = NULL;
|
|
#define PROC_DIR "xpp"
|
|
#define PROC_XPD_SUMMARY "summary"
|
|
#endif
|
|
|
|
#define MAX_QUEUE_LEN 10000
|
|
#define DELAY_UNTIL_DIALTONE 3000
|
|
|
|
DEF_PARM(int, debug, 0, 0644, "Print DBG statements");
|
|
static DEF_PARM_BOOL(dahdi_autoreg, 0, 0644, "Register spans automatically (1) or not (0)");
|
|
static DEF_PARM_BOOL(prefmaster, 0, 0644, "Do we want to be dahdi preferred sync master");
|
|
// DEF_ARRAY(int, pcmtx, 4, 0, "Forced PCM values to transmit");
|
|
|
|
#include "dahdi_debug.h"
|
|
|
|
static void phonedev_cleanup(xpd_t *xpd);
|
|
|
|
#ifdef DEBUG_SYNC_PARPORT
|
|
/*
|
|
* Use parallel port to sample our PCM sync and diagnose quality and
|
|
* potential problems. A logic analizer or a scope should be connected
|
|
* to the data bits of the parallel port.
|
|
*
|
|
* Array parameter: Choose the two xbuses Id's to sample.
|
|
* This can be changed on runtime as well. Example:
|
|
* echo "3,5" > /sys/module/xpp/parameters/parport_xbuses
|
|
*/
|
|
static int parport_xbuses[2] = { 0, 1 };
|
|
unsigned int parport_xbuses_num_values;
|
|
#if LINUX_VERSION_CODE > KERNEL_VERSION(2,6,9)
|
|
module_param_array(parport_xbuses, int, &parport_xbuses_num_values, 0577);
|
|
#else
|
|
module_param_array(parport_xbuses, int, parport_xbuses_num_values, 0577);
|
|
#endif
|
|
MODULE_PARM_DESC(parport_xbuses, "Id's of xbuses to sample (1-2)");
|
|
|
|
/*
|
|
* Flip a single bit in the parallel port:
|
|
* - The bit number is either bitnum0 or bitnum1
|
|
* - Bit is selected by xbus number from parport_xbuses[]
|
|
*/
|
|
void xbus_flip_bit(xbus_t *xbus, unsigned int bitnum0, unsigned int bitnum1)
|
|
{
|
|
int num = xbus->num;
|
|
|
|
if(num == parport_xbuses[0])
|
|
flip_parport_bit(bitnum0);
|
|
if(num == parport_xbuses[1])
|
|
flip_parport_bit(bitnum1);
|
|
}
|
|
EXPORT_SYMBOL(xbus_flip_bit);
|
|
#endif
|
|
|
|
static atomic_t num_registered_spans = ATOMIC_INIT(0);
|
|
|
|
int total_registered_spans(void)
|
|
{
|
|
return atomic_read(&num_registered_spans);
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
static int xpd_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data);
|
|
#endif
|
|
|
|
/*------------------------- XPD Management -------------------------*/
|
|
|
|
/*
|
|
* Called by put_xpd() when XPD has no more references.
|
|
*/
|
|
static void xpd_destroy(struct kref *kref)
|
|
{
|
|
xpd_t *xpd;
|
|
|
|
xpd = kref_to_xpd(kref);
|
|
XPD_DBG(DEVICES, xpd, "%s\n", __func__);
|
|
xpd_device_unregister(xpd);
|
|
}
|
|
|
|
int refcount_xpd(xpd_t *xpd)
|
|
{
|
|
struct kref *kref = &xpd->kref;
|
|
|
|
return atomic_read(&kref->refcount);
|
|
}
|
|
|
|
xpd_t *get_xpd(const char *msg, xpd_t *xpd)
|
|
{
|
|
XPD_DBG(DEVICES, xpd, "%s: refcount_xpd=%d\n",
|
|
msg, refcount_xpd(xpd));
|
|
kref_get(&xpd->kref);
|
|
return xpd;
|
|
}
|
|
|
|
void put_xpd(const char *msg, xpd_t *xpd)
|
|
{
|
|
XPD_DBG(DEVICES, xpd, "%s: refcount_xpd=%d\n",
|
|
msg, refcount_xpd(xpd));
|
|
kref_put(&xpd->kref, xpd_destroy);
|
|
}
|
|
|
|
static void xpd_proc_remove(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
#ifdef CONFIG_PROC_FS
|
|
if(xpd->proc_xpd_dir) {
|
|
if(xpd->proc_xpd_summary) {
|
|
XPD_DBG(PROC, xpd, "Removing proc '%s'\n", PROC_XPD_SUMMARY);
|
|
remove_proc_entry(PROC_XPD_SUMMARY, xpd->proc_xpd_dir);
|
|
xpd->proc_xpd_summary = NULL;
|
|
}
|
|
XPD_DBG(PROC, xpd, "Removing %s/%s proc directory\n",
|
|
xbus->busname, xpd->xpdname);
|
|
remove_proc_entry(xpd->xpdname, xbus->proc_xbus_dir);
|
|
xpd->proc_xpd_dir = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int xpd_proc_create(xbus_t *xbus, xpd_t *xpd)
|
|
{
|
|
#ifdef CONFIG_PROC_FS
|
|
XPD_DBG(PROC, xpd, "Creating proc directory\n");
|
|
xpd->proc_xpd_dir = proc_mkdir(xpd->xpdname, xbus->proc_xbus_dir);
|
|
if(!xpd->proc_xpd_dir) {
|
|
XPD_ERR(xpd, "Failed to create proc directory\n");
|
|
goto err;
|
|
}
|
|
xpd->proc_xpd_summary = create_proc_read_entry(PROC_XPD_SUMMARY, 0444, xpd->proc_xpd_dir,
|
|
xpd_read_proc, xpd);
|
|
if(!xpd->proc_xpd_summary) {
|
|
XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_XPD_SUMMARY);
|
|
goto err;
|
|
}
|
|
SET_PROC_DIRENTRY_OWNER(xpd->proc_xpd_summary);
|
|
#endif
|
|
return 0;
|
|
#ifdef CONFIG_PROC_FS
|
|
err:
|
|
xpd_proc_remove(xbus, xpd);
|
|
return -EFAULT;
|
|
#endif
|
|
}
|
|
|
|
void xpd_free(xpd_t *xpd)
|
|
{
|
|
xbus_t *xbus = NULL;
|
|
|
|
if(!xpd)
|
|
return;
|
|
if(xpd->xproto)
|
|
xproto_put(xpd->xproto); /* was taken in xpd_alloc() */
|
|
xpd->xproto = NULL;
|
|
xbus = xpd->xbus;
|
|
if(!xbus)
|
|
return;
|
|
XPD_DBG(DEVICES, xpd, "\n");
|
|
xpd_proc_remove(xbus, xpd);
|
|
xbus_xpd_unbind(xbus, xpd);
|
|
phonedev_cleanup(xpd);
|
|
KZFREE(xpd);
|
|
DBG(DEVICES, "refcount_xbus=%d\n", refcount_xbus(xbus));
|
|
/*
|
|
* This must be last, so the xbus cannot be released before the xpd
|
|
*/
|
|
put_xbus(__FUNCTION__, xbus); /* was taken in xpd_alloc() */
|
|
}
|
|
|
|
/*
|
|
* Synchronous part of XPD detection.
|
|
* Called from new_card()
|
|
*/
|
|
int create_xpd(xbus_t *xbus, const xproto_table_t *proto_table,
|
|
int unit,
|
|
int subunit,
|
|
byte type,
|
|
byte subtype,
|
|
int subunits,
|
|
int subunit_ports,
|
|
byte port_dir)
|
|
{
|
|
xpd_t *xpd = NULL;
|
|
bool to_phone;
|
|
|
|
BUG_ON(type == XPD_TYPE_NOMODULE);
|
|
to_phone = BIT(subunit) & port_dir;
|
|
BUG_ON(!xbus);
|
|
xpd = xpd_byaddr(xbus, unit, subunit);
|
|
if(xpd) {
|
|
XPD_NOTICE(xpd, "XPD at %d%d already exists\n",
|
|
unit, subunit);
|
|
return 0;
|
|
}
|
|
if(subunit_ports <= 0 || subunit_ports > CHANNELS_PERXPD) {
|
|
XBUS_NOTICE(xbus, "Illegal number of ports %d for XPD %d%d\n",
|
|
subunit_ports, unit, subunit);
|
|
return 0;
|
|
}
|
|
xpd = proto_table->xops->card_new(xbus, unit, subunit, proto_table, subtype, subunits, subunit_ports, to_phone);
|
|
if(!xpd) {
|
|
XBUS_NOTICE(xbus, "card_new(%d,%d,%d,%d,%d) failed. Ignored.\n",
|
|
unit, subunit, proto_table->type, subtype, to_phone);
|
|
return -EINVAL;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void xpd_post_init(xpd_t *xpd)
|
|
{
|
|
XPD_DBG(DEVICES, xpd, "\n");
|
|
if(dahdi_autoreg)
|
|
dahdi_register_xpd(xpd);
|
|
}
|
|
|
|
#ifdef CONFIG_PROC_FS
|
|
|
|
/**
|
|
* Prints a general procfs entry for the bus, under xpp/BUSNAME/summary
|
|
* @page TODO: figure out procfs
|
|
* @start TODO: figure out procfs
|
|
* @off TODO: figure out procfs
|
|
* @count TODO: figure out procfs
|
|
* @eof TODO: figure out procfs
|
|
* @data an xbus_t pointer with the bus data.
|
|
*/
|
|
static int xpd_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
|
|
{
|
|
int len = 0;
|
|
xpd_t *xpd = data;
|
|
int i;
|
|
|
|
if(!xpd)
|
|
goto out;
|
|
|
|
len += sprintf(page + len, "%s (%s, card %s, span %d)\n"
|
|
"timing_priority: %d\n"
|
|
"timer_count: %d span->mainttimer=%d\n"
|
|
,
|
|
xpd->xpdname, xpd->type_name,
|
|
(xpd->card_present) ? "present" : "missing",
|
|
(SPAN_REGISTERED(xpd)) ? PHONEDEV(xpd).span.spanno : 0,
|
|
PHONEDEV(xpd).timing_priority,
|
|
xpd->timer_count, PHONEDEV(xpd).span.mainttimer
|
|
);
|
|
len += sprintf(page + len, "xpd_state: %s (%d)\n",
|
|
xpd_statename(xpd->xpd_state), xpd->xpd_state);
|
|
len += sprintf(page + len, "open_counter=%d refcount=%d\n",
|
|
atomic_read(&PHONEDEV(xpd).open_counter), refcount_xpd(xpd));
|
|
len += sprintf(page + len, "Address: U=%d S=%d\n", xpd->addr.unit, xpd->addr.subunit);
|
|
len += sprintf(page + len, "Subunits: %d\n", xpd->subunits);
|
|
len += sprintf(page + len, "Type: %d.%d\n\n", xpd->type, xpd->subtype);
|
|
len += sprintf(page + len, "pcm_len=%d\n\n", PHONEDEV(xpd).pcm_len);
|
|
len += sprintf(page + len, "wanted_pcm_mask=0x%04X\n\n", PHONEDEV(xpd).wanted_pcm_mask);
|
|
len += sprintf(page + len, "mute_dtmf=0x%04X\n\n", PHONEDEV(xpd).mute_dtmf);
|
|
len += sprintf(page + len, "STATES:");
|
|
len += sprintf(page + len, "\n\t%-17s: ", "output_relays");
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(page + len, "%d ", IS_SET(PHONEDEV(xpd).digital_outputs, i));
|
|
}
|
|
len += sprintf(page + len, "\n\t%-17s: ", "input_relays");
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(page + len, "%d ", IS_SET(PHONEDEV(xpd).digital_inputs, i));
|
|
}
|
|
len += sprintf(page + len, "\n\t%-17s: ", "offhook");
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(page + len, "%d ", IS_OFFHOOK(xpd, i));
|
|
}
|
|
len += sprintf(page + len, "\n\t%-17s: ", "oht_pcm_pass");
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(page + len, "%d ", IS_SET(PHONEDEV(xpd).oht_pcm_pass, i));
|
|
}
|
|
len += sprintf(page + len, "\n\t%-17s: ", "msg_waiting");
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(page + len, "%d ", PHONEDEV(xpd).msg_waiting[i]);
|
|
}
|
|
len += sprintf(page + len, "\n\t%-17s: ", "ringing");
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(page + len, "%d ", PHONEDEV(xpd).ringing[i]);
|
|
}
|
|
len += sprintf(page + len, "\n\t%-17s: ", "no_pcm");
|
|
for_each_line(xpd, i) {
|
|
len += sprintf(page + len, "%d ", IS_SET(PHONEDEV(xpd).no_pcm, i));
|
|
}
|
|
#if 1
|
|
if(SPAN_REGISTERED(xpd)) {
|
|
len += sprintf(page + len, "\nPCM:\n | [readchunk] | [writechunk] | W D");
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
|
|
byte rchunk[DAHDI_CHUNKSIZE];
|
|
byte wchunk[DAHDI_CHUNKSIZE];
|
|
byte *rp;
|
|
byte *wp;
|
|
int j;
|
|
|
|
if(IS_SET(PHONEDEV(xpd).digital_outputs, i))
|
|
continue;
|
|
if(IS_SET(PHONEDEV(xpd).digital_inputs, i))
|
|
continue;
|
|
if(IS_SET(PHONEDEV(xpd).digital_signalling, i))
|
|
continue;
|
|
rp = chan->readchunk;
|
|
wp = chan->writechunk;
|
|
memcpy(rchunk, rp, DAHDI_CHUNKSIZE);
|
|
memcpy(wchunk, wp, DAHDI_CHUNKSIZE);
|
|
len += sprintf(page + len, "\n port %2d> | ", i);
|
|
for(j = 0; j < DAHDI_CHUNKSIZE; j++) {
|
|
len += sprintf(page + len, "%02X ", rchunk[j]);
|
|
}
|
|
len += sprintf(page + len, " | ");
|
|
for(j = 0; j < DAHDI_CHUNKSIZE; j++) {
|
|
len += sprintf(page + len, "%02X ", wchunk[j]);
|
|
}
|
|
len += sprintf(page + len, " | %c",
|
|
(IS_SET(PHONEDEV(xpd).wanted_pcm_mask, i))?'+':' ');
|
|
len += sprintf(page + len, " %c",
|
|
(IS_SET(PHONEDEV(xpd).mute_dtmf, i))?'-':' ');
|
|
}
|
|
}
|
|
#endif
|
|
#if 0
|
|
if(SPAN_REGISTERED(xpd)) {
|
|
len += sprintf(page + len, "\nSignalling:\n");
|
|
for_each_line(xpd, i) {
|
|
struct dahdi_chan *chan = XPD_CHAN(xpd, i);
|
|
len += sprintf(page + len, "\t%2d> sigcap=0x%04X sig=0x%04X\n", i, chan->sigcap, chan->sig);
|
|
}
|
|
}
|
|
#endif
|
|
len += sprintf(page + len, "\nCOUNTERS:\n");
|
|
for(i = 0; i < XPD_COUNTER_MAX; i++) {
|
|
len += sprintf(page + len, "\t\t%-20s = %d\n",
|
|
xpd_counters[i].name, xpd->counters[i]);
|
|
}
|
|
len += sprintf(page + len, "<-- len=%d\n", len);
|
|
out:
|
|
if (len <= off+count)
|
|
*eof = 1;
|
|
*start = page + off;
|
|
len -= off;
|
|
if (len > count)
|
|
len = count;
|
|
if (len < 0)
|
|
len = 0;
|
|
return len;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
const char *xpd_statename(enum xpd_state st)
|
|
{
|
|
switch(st) {
|
|
case XPD_STATE_START: return "START";
|
|
case XPD_STATE_INIT_REGS: return "INIT_REGS";
|
|
case XPD_STATE_READY: return "READY";
|
|
case XPD_STATE_NOHW: return "NOHW";
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
bool xpd_setstate(xpd_t *xpd, enum xpd_state newstate)
|
|
{
|
|
BUG_ON(!xpd);
|
|
XPD_DBG(DEVICES, xpd, "%s: %s (%d) -> %s (%d)\n", __FUNCTION__,
|
|
xpd_statename(xpd->xpd_state), xpd->xpd_state,
|
|
xpd_statename(newstate), newstate);
|
|
switch(newstate) {
|
|
case XPD_STATE_START:
|
|
goto badstate;
|
|
case XPD_STATE_INIT_REGS:
|
|
if(xpd->xpd_state != XPD_STATE_START)
|
|
goto badstate;
|
|
if(xpd->addr.subunit != 0) {
|
|
XPD_NOTICE(xpd,
|
|
"%s: Moving to %s allowed only for subunit 0\n",
|
|
__FUNCTION__, xpd_statename(newstate));
|
|
goto badstate;
|
|
}
|
|
break;
|
|
case XPD_STATE_READY:
|
|
if(xpd->addr.subunit == 0) {
|
|
/* Unit 0 script initialize registers of all subunits */
|
|
if(xpd->xpd_state != XPD_STATE_INIT_REGS)
|
|
goto badstate;
|
|
} else {
|
|
if(xpd->xpd_state != XPD_STATE_START)
|
|
goto badstate;
|
|
}
|
|
break;
|
|
case XPD_STATE_NOHW:
|
|
break;
|
|
default:
|
|
XPD_ERR(xpd, "%s: Unknown newstate=%d\n", __FUNCTION__, newstate);
|
|
}
|
|
xpd->xpd_state = newstate;
|
|
return 1;
|
|
badstate:
|
|
XPD_NOTICE(xpd, "%s: cannot transition: %s (%d) -> %s (%d)\n",
|
|
__FUNCTION__,
|
|
xpd_statename(xpd->xpd_state), xpd->xpd_state,
|
|
xpd_statename(newstate), newstate);
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* Cleanup/initialize phonedev
|
|
*/
|
|
static void phonedev_cleanup(xpd_t *xpd)
|
|
{
|
|
struct phonedev *phonedev = &PHONEDEV(xpd);
|
|
unsigned int x;
|
|
|
|
for (x = 0; x < phonedev->channels; x++) {
|
|
if (phonedev->chans[x]) {
|
|
KZFREE(phonedev->chans[x]);
|
|
}
|
|
if (phonedev->ec[x])
|
|
KZFREE(phonedev->ec[x]);
|
|
}
|
|
}
|
|
|
|
__must_check static int phonedev_init(xpd_t *xpd, const xproto_table_t *proto_table,
|
|
int channels, xpp_line_t no_pcm)
|
|
{
|
|
struct phonedev *phonedev = &PHONEDEV(xpd);
|
|
unsigned int x;
|
|
|
|
spin_lock_init(&phonedev->lock_recompute_pcm);
|
|
phonedev->channels = channels;
|
|
phonedev->no_pcm = no_pcm;
|
|
phonedev->offhook_state = 0x0; /* ONHOOK */
|
|
phonedev->phoneops = proto_table->phoneops;
|
|
phonedev->digital_outputs = 0;
|
|
phonedev->digital_inputs = 0;
|
|
atomic_set(&phonedev->dahdi_registered, 0);
|
|
atomic_set(&phonedev->open_counter, 0);
|
|
for (x = 0; x < phonedev->channels; x++) {
|
|
if (!(phonedev->chans[x] = KZALLOC(sizeof(*(phonedev->chans[x])), GFP_KERNEL))) {
|
|
ERR("%s: Unable to allocate channel %d\n", __FUNCTION__, x);
|
|
goto err;
|
|
}
|
|
phonedev->ec[x] = KZALLOC(sizeof(*(phonedev->ec[x])),
|
|
GFP_KERNEL);
|
|
if (!phonedev->ec[x]) {
|
|
ERR("%s: Unable to allocate ec state %d\n", __func__,
|
|
x);
|
|
goto err;
|
|
}
|
|
}
|
|
return 0;
|
|
err:
|
|
phonedev_cleanup(xpd);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
/*
|
|
* xpd_alloc - Allocator for new XPD's
|
|
*
|
|
*/
|
|
__must_check xpd_t *xpd_alloc(xbus_t *xbus,
|
|
int unit, int subunit,
|
|
int subtype, int subunits,
|
|
size_t privsize, const xproto_table_t *proto_table, int channels)
|
|
{
|
|
xpd_t *xpd = NULL;
|
|
size_t alloc_size = sizeof(xpd_t) + privsize;
|
|
int type = proto_table->type;
|
|
xpp_line_t no_pcm = 0;
|
|
|
|
BUG_ON(!proto_table);
|
|
XBUS_DBG(DEVICES, xbus, "type=%d channels=%d (alloc_size=%zd)\n",
|
|
type, channels, alloc_size);
|
|
if(channels > CHANNELS_PERXPD) {
|
|
XBUS_ERR(xbus, "%s: type=%d: too many channels %d\n",
|
|
__FUNCTION__, type, channels);
|
|
goto err;
|
|
}
|
|
|
|
if((xpd = KZALLOC(alloc_size, GFP_KERNEL)) == NULL) {
|
|
XBUS_ERR(xbus, "%s: type=%d: Unable to allocate memory\n",
|
|
__FUNCTION__, type);
|
|
goto err;
|
|
}
|
|
xpd->priv = (byte *)xpd + sizeof(xpd_t);
|
|
spin_lock_init(&xpd->lock);
|
|
xpd->card_present = 0;
|
|
xpd->type = proto_table->type;
|
|
xpd->xproto = proto_table;
|
|
xpd->xops = proto_table->xops;
|
|
xpd->xpd_state = XPD_STATE_START;
|
|
xpd->subtype = subtype;
|
|
xpd->subunits = subunits;
|
|
kref_init(&xpd->kref);
|
|
|
|
/* For USB-1 disable some channels */
|
|
if(MAX_SEND_SIZE(xbus) < RPACKET_SIZE(GLOBAL, PCM_WRITE)) {
|
|
no_pcm = 0x7F | PHONEDEV(xpd).digital_outputs | PHONEDEV(xpd).digital_inputs;
|
|
XBUS_NOTICE(xbus, "max xframe size = %d, disabling some PCM channels. no_pcm=0x%04X\n",
|
|
MAX_SEND_SIZE(xbus), PHONEDEV(xpd).no_pcm);
|
|
}
|
|
if (phonedev_init(xpd, proto_table, channels, no_pcm) < 0)
|
|
goto err;
|
|
xbus_xpd_bind(xbus, xpd, unit, subunit);
|
|
if(xpd_proc_create(xbus, xpd) < 0)
|
|
goto err;
|
|
/*
|
|
* This makes sure the xbus cannot be removed before this xpd
|
|
* is removed in xpd_free()
|
|
*/
|
|
xbus = get_xbus(__func__, xbus->num); /* returned in xpd_free() */
|
|
xproto_get(type); /* will be returned in xpd_free() */
|
|
return xpd;
|
|
err:
|
|
if(xpd) {
|
|
xpd_proc_remove(xbus, xpd);
|
|
phonedev_cleanup(xpd);
|
|
KZFREE(xpd);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/*
|
|
* Try our best to make asterisk close all channels related to
|
|
* this Astribank:
|
|
* - Set span state to DAHDI_ALARM_NOTOPEN in all relevant spans.
|
|
* - Notify dahdi afterwards about spans (so it can see all changes at once).
|
|
* - Also send DAHDI_EVENT_REMOVED on all channels.
|
|
*/
|
|
void xbus_request_removal(xbus_t *xbus)
|
|
{
|
|
unsigned long flags;
|
|
int i;
|
|
|
|
for(i = 0; i < MAX_XPDS; i++) {
|
|
xpd_t *xpd = xpd_of(xbus, i);
|
|
if(xpd) {
|
|
XPD_DBG(DEVICES, xpd, "\n");
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
xpd->card_present = 0;
|
|
xpd_setstate(xpd, XPD_STATE_NOHW);
|
|
PHONEDEV(xpd).span.alarms = DAHDI_ALARM_NOTOPEN;
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
}
|
|
}
|
|
/* Now notify dahdi */
|
|
for(i = 0; i < MAX_XPDS; i++) {
|
|
xpd_t *xpd = xpd_of(xbus, i);
|
|
if(xpd) {
|
|
if(SPAN_REGISTERED(xpd)) {
|
|
int j;
|
|
|
|
dahdi_alarm_notify(&PHONEDEV(xpd).span);
|
|
XPD_DBG(DEVICES, xpd, "Queuing DAHDI_EVENT_REMOVED on all channels to ask user to release them\n");
|
|
for (j=0; j<PHONEDEV(xpd).span.channels; j++) {
|
|
dahdi_qevent_lock(XPD_CHAN(xpd, j),DAHDI_EVENT_REMOVED);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The xpd isn't open by anyone, we can unregister it and free it
|
|
*/
|
|
void xpd_remove(xpd_t *xpd)
|
|
{
|
|
BUG_ON(!xpd);
|
|
XPD_INFO(xpd, "Remove\n");
|
|
dahdi_unregister_xpd(xpd);
|
|
CALL_XMETHOD(card_remove, xpd);
|
|
xpd_free(xpd);
|
|
}
|
|
|
|
void update_xpd_status(xpd_t *xpd, int alarm_flag)
|
|
{
|
|
struct dahdi_span *span = &PHONEDEV(xpd).span;
|
|
|
|
if(!SPAN_REGISTERED(xpd)) {
|
|
// XPD_NOTICE(xpd, "%s: XPD is not registered. Skipping.\n", __FUNCTION__);
|
|
return;
|
|
}
|
|
switch (alarm_flag) {
|
|
case DAHDI_ALARM_NONE:
|
|
xpd->last_response = jiffies;
|
|
break;
|
|
default:
|
|
// Nothing
|
|
break;
|
|
}
|
|
if(span->alarms == alarm_flag)
|
|
return;
|
|
XPD_DBG(GENERAL, xpd, "Update XPD alarms: %s -> %02X\n", PHONEDEV(xpd).span.name, alarm_flag);
|
|
span->alarms = alarm_flag;
|
|
dahdi_alarm_notify(span);
|
|
}
|
|
|
|
/*
|
|
* Used to block/pass PCM during onhook-transfers. E.g:
|
|
* - Playing FSK after FXS ONHOOK for MWI (non-neon style)
|
|
* - Playing DTFM/FSK for FXO Caller-ID detection.
|
|
*/
|
|
void oht_pcm(xpd_t *xpd, int pos, bool pass)
|
|
{
|
|
if(pass) {
|
|
LINE_DBG(SIGNAL, xpd, pos, "OHT PCM: pass\n");
|
|
BIT_SET(PHONEDEV(xpd).oht_pcm_pass, pos);
|
|
} else {
|
|
LINE_DBG(SIGNAL, xpd, pos, "OHT PCM: block\n");
|
|
BIT_CLR(PHONEDEV(xpd).oht_pcm_pass, pos);
|
|
}
|
|
CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
|
|
}
|
|
|
|
/*
|
|
* Update our hookstate -- for PCM block/pass
|
|
*/
|
|
void mark_offhook(xpd_t *xpd, int pos, bool to_offhook)
|
|
{
|
|
if(to_offhook) {
|
|
LINE_DBG(SIGNAL, xpd, pos, "OFFHOOK\n");
|
|
BIT_SET(PHONEDEV(xpd).offhook_state, pos);
|
|
} else {
|
|
LINE_DBG(SIGNAL, xpd, pos, "ONHOOK\n");
|
|
BIT_CLR(PHONEDEV(xpd).offhook_state, pos);
|
|
}
|
|
CALL_PHONE_METHOD(card_pcm_recompute, xpd, 0);
|
|
}
|
|
|
|
/*
|
|
* Send a signalling notification to Asterisk
|
|
*/
|
|
void notify_rxsig(xpd_t *xpd, int pos, enum dahdi_rxsig rxsig)
|
|
{
|
|
/*
|
|
* We should not spinlock before calling dahdi_hooksig() as
|
|
* it may call back into our xpp_hooksig() and cause
|
|
* a nested spinlock scenario
|
|
*/
|
|
LINE_DBG(SIGNAL, xpd, pos, "rxsig=%s\n", rxsig2str(rxsig));
|
|
if(SPAN_REGISTERED(xpd))
|
|
dahdi_hooksig(XPD_CHAN(xpd, pos), rxsig);
|
|
}
|
|
|
|
/*
|
|
* Called when hardware state changed:
|
|
* - FXS -- the phone was picked up or hanged-up.
|
|
* - FXO -- we answered the phone or handed-up.
|
|
*/
|
|
void hookstate_changed(xpd_t *xpd, int pos, bool to_offhook)
|
|
{
|
|
BUG_ON(!xpd);
|
|
mark_offhook(xpd, pos, to_offhook);
|
|
if(!to_offhook) {
|
|
oht_pcm(xpd, pos, 0);
|
|
/*
|
|
* To prevent latest PCM to stay in buffers
|
|
* indefinitely, mark this channel for a
|
|
* single silence transmittion.
|
|
*
|
|
* This bit will be cleared on the next tick.
|
|
*/
|
|
BIT_SET(PHONEDEV(xpd).silence_pcm, pos);
|
|
}
|
|
notify_rxsig(xpd, pos, (to_offhook) ? DAHDI_RXSIG_OFFHOOK : DAHDI_RXSIG_ONHOOK);
|
|
}
|
|
|
|
#define XPP_MAX_LEN 512
|
|
|
|
/*------------------------- Dahdi Interfaces -----------------------*/
|
|
|
|
|
|
/*
|
|
* Called from dahdi with spinlock held on chan. Must not call back
|
|
* dahdi functions.
|
|
*/
|
|
int xpp_open(struct dahdi_chan *chan)
|
|
{
|
|
xpd_t *xpd;
|
|
xbus_t *xbus;
|
|
int pos;
|
|
unsigned long flags;
|
|
|
|
if (!chan) {
|
|
NOTICE("open called on a null chan\n");
|
|
return -EINVAL;
|
|
}
|
|
xpd = chan->pvt;
|
|
xpd = get_xpd(__FUNCTION__, xpd); /* Returned in xpp_close() */
|
|
if (!xpd) {
|
|
NOTICE("open called on a chan with no pvt (xpd)\n");
|
|
BUG();
|
|
}
|
|
xbus = xpd->xbus;
|
|
if (!xbus) {
|
|
NOTICE("open called on a chan with no xbus\n");
|
|
BUG();
|
|
}
|
|
pos = chan->chanpos - 1;
|
|
if(!xpd->card_present) {
|
|
LINE_NOTICE(xpd, pos, "Cannot open -- device not ready\n");
|
|
put_xpd(__FUNCTION__, xpd);
|
|
return -ENODEV;
|
|
}
|
|
spin_lock_irqsave(&xbus->lock, flags);
|
|
atomic_inc(&PHONEDEV(xpd).open_counter);
|
|
LINE_DBG(DEVICES, xpd, pos, "%s[%d]: open_counter=%d\n",
|
|
current->comm, current->pid,
|
|
atomic_read(&PHONEDEV(xpd).open_counter));
|
|
spin_unlock_irqrestore(&xbus->lock, flags);
|
|
if(PHONE_METHOD(card_open, xpd))
|
|
CALL_PHONE_METHOD(card_open, xpd, pos);
|
|
return 0;
|
|
}
|
|
|
|
int xpp_close(struct dahdi_chan *chan)
|
|
{
|
|
xpd_t *xpd = chan->pvt;
|
|
xbus_t *xbus = xpd->xbus;
|
|
int pos = chan->chanpos - 1;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&xbus->lock, flags);
|
|
spin_unlock_irqrestore(&xbus->lock, flags);
|
|
if(PHONE_METHOD(card_close, xpd))
|
|
CALL_PHONE_METHOD(card_close, xpd, pos);
|
|
LINE_DBG(DEVICES, xpd, pos, "%s[%d]: open_counter=%d\n",
|
|
current->comm, current->pid,
|
|
atomic_read(&PHONEDEV(xpd).open_counter));
|
|
atomic_dec(&PHONEDEV(xpd).open_counter); /* from xpp_open() */
|
|
put_xpd(__FUNCTION__, xpd); /* from xpp_open() */
|
|
return 0;
|
|
}
|
|
|
|
void report_bad_ioctl(const char *msg, xpd_t *xpd, int pos, unsigned int cmd)
|
|
{
|
|
char *extra_msg = "";
|
|
|
|
if(_IOC_TYPE(cmd) == 'J')
|
|
extra_msg = " (for old ZAPTEL)";
|
|
XPD_NOTICE(xpd, "%s: Bad ioctl%s\n", msg, extra_msg);
|
|
XPD_NOTICE(xpd, "ENOTTY: chan=%d cmd=0x%x\n", pos, cmd);
|
|
XPD_NOTICE(xpd, " IOC_TYPE=0x%02X\n", _IOC_TYPE(cmd));
|
|
XPD_NOTICE(xpd, " IOC_DIR=0x%02X\n", _IOC_DIR(cmd));
|
|
XPD_NOTICE(xpd, " IOC_NR=%d\n", _IOC_NR(cmd));
|
|
XPD_NOTICE(xpd, " IOC_SIZE=0x%02X\n", _IOC_SIZE(cmd));
|
|
}
|
|
|
|
int xpp_ioctl(struct dahdi_chan *chan, unsigned int cmd, unsigned long arg)
|
|
{
|
|
xpd_t *xpd = chan->pvt;
|
|
int pos = chan->chanpos - 1;
|
|
|
|
if(!xpd) {
|
|
ERR("%s: channel in pos %d, was already closed. Ignore.\n",
|
|
__FUNCTION__, pos);
|
|
return -ENODEV;
|
|
}
|
|
switch (cmd) {
|
|
default:
|
|
/* Some span-specific commands before we give up: */
|
|
if (PHONE_METHOD(card_ioctl, xpd)) {
|
|
return CALL_PHONE_METHOD(card_ioctl, xpd, pos, cmd, arg);
|
|
}
|
|
report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
|
|
return -ENOTTY;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int xpp_hooksig(struct dahdi_chan *chan, enum dahdi_txsig txsig)
|
|
{
|
|
xpd_t *xpd = chan->pvt;
|
|
xbus_t *xbus;
|
|
int pos = chan->chanpos - 1;
|
|
|
|
if(!xpd) {
|
|
ERR("%s: channel in pos %d, was already closed. Ignore.\n",
|
|
__FUNCTION__, pos);
|
|
return -ENODEV;
|
|
}
|
|
if(!PHONE_METHOD(card_hooksig, xpd)) {
|
|
LINE_ERR(xpd, pos,
|
|
"%s: No hooksig method for this channel. Ignore.\n",
|
|
__FUNCTION__);
|
|
return -ENODEV;
|
|
}
|
|
xbus = xpd->xbus;
|
|
BUG_ON(!xbus);
|
|
DBG(SIGNAL, "Setting %s to %s (%d)\n", chan->name, txsig2str(txsig), txsig);
|
|
return CALL_PHONE_METHOD(card_hooksig, xpd, pos, txsig);
|
|
}
|
|
EXPORT_SYMBOL(xpp_hooksig);
|
|
|
|
/* Req: Set the requested chunk size. This is the unit in which you must
|
|
report results for conferencing, etc */
|
|
int xpp_setchunksize(struct dahdi_span *span, int chunksize);
|
|
|
|
/* Enable maintenance modes */
|
|
int xpp_maint(struct dahdi_span *span, int cmd)
|
|
{
|
|
struct phonedev *phonedev = container_of(span, struct phonedev, span);
|
|
xpd_t *xpd = container_of(phonedev, struct xpd, phonedev);
|
|
int ret = 0;
|
|
#if 0
|
|
char loopback_data[] = "THE-QUICK-BROWN-FOX-JUMPED-OVER-THE-LAZY-DOG";
|
|
#endif
|
|
|
|
DBG(GENERAL, "span->mainttimer=%d\n", span->mainttimer);
|
|
switch(cmd) {
|
|
case DAHDI_MAINT_NONE:
|
|
INFO("XXX Turn off local and remote loops XXX\n");
|
|
break;
|
|
case DAHDI_MAINT_LOCALLOOP:
|
|
INFO("XXX Turn on local loopback XXX\n");
|
|
break;
|
|
case DAHDI_MAINT_REMOTELOOP:
|
|
INFO("XXX Turn on remote loopback XXX\n");
|
|
break;
|
|
case DAHDI_MAINT_LOOPUP:
|
|
INFO("XXX Send loopup code XXX\n");
|
|
break;
|
|
case DAHDI_MAINT_LOOPDOWN:
|
|
INFO("XXX Send loopdown code XXX\n");
|
|
break;
|
|
default:
|
|
ERR("XPP: Unknown maint command: %d\n", cmd);
|
|
ret = -EINVAL;
|
|
break;
|
|
}
|
|
if (span->mainttimer || span->maintstat)
|
|
update_xpd_status(xpd, DAHDI_ALARM_LOOPBACK);
|
|
return ret;
|
|
}
|
|
|
|
#ifdef CONFIG_DAHDI_WATCHDOG
|
|
/*
|
|
* If the watchdog detects no received data, it will call the
|
|
* watchdog routine
|
|
*/
|
|
static int xpp_watchdog(struct dahdi_span *span, int cause)
|
|
{
|
|
static int rate_limit = 0;
|
|
|
|
if((rate_limit++ % 1000) == 0)
|
|
DBG(GENERAL, "\n");
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Hardware Echo Canceller management
|
|
*/
|
|
static void echocan_free(struct dahdi_chan *chan,
|
|
struct dahdi_echocan_state *ec)
|
|
{
|
|
xpd_t *xpd;
|
|
xbus_t *xbus;
|
|
int pos = chan->chanpos - 1;
|
|
const struct echoops *echoops;
|
|
|
|
xpd = chan->pvt;
|
|
xbus = xpd->xbus;
|
|
echoops = ECHOOPS(xbus);
|
|
if (!echoops)
|
|
return;
|
|
LINE_DBG(GENERAL, xpd, pos, "mode=0x%X\n", ec->status.mode);
|
|
CALL_EC_METHOD(ec_set, xbus, xpd, pos, 0);
|
|
CALL_EC_METHOD(ec_update, xbus, xbus);
|
|
put_xpd(__FUNCTION__, xpd); /* aquired in xpp_echocan_create() */
|
|
}
|
|
|
|
static const struct dahdi_echocan_features xpp_ec_features = {
|
|
};
|
|
|
|
static const struct dahdi_echocan_ops xpp_ec_ops = {
|
|
.echocan_free = echocan_free,
|
|
};
|
|
|
|
const char *xpp_echocan_name(const struct dahdi_chan *chan)
|
|
{
|
|
xpd_t *xpd;
|
|
xbus_t *xbus;
|
|
int pos;
|
|
|
|
if (!chan) {
|
|
NOTICE("%s(NULL)\n", __func__);
|
|
return "XPP";
|
|
}
|
|
xpd = chan->pvt;
|
|
xbus = xpd->xbus;
|
|
pos = chan->chanpos - 1;
|
|
LINE_DBG(GENERAL, xpd, pos, "\n");
|
|
if (!ECHOOPS(xbus))
|
|
return NULL;
|
|
/*
|
|
* quirks and limitations
|
|
*/
|
|
if (xbus->quirks.has_fxo) {
|
|
if (
|
|
xbus->quirks.has_digital_span &&
|
|
xpd->type == XPD_TYPE_FXO) {
|
|
LINE_NOTICE(xpd, pos,
|
|
"quirk: give up HWEC on FXO: "
|
|
"AB has digital span\n");
|
|
return NULL;
|
|
} else if (
|
|
xbus->sync_mode != SYNC_MODE_AB &&
|
|
xpd->type == XPD_TYPE_FXS) {
|
|
LINE_NOTICE(xpd, pos,
|
|
"quirk: give up HWEC on FXS: "
|
|
"AB has FXO and is sync slave\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
return "XPP";
|
|
}
|
|
EXPORT_SYMBOL(xpp_echocan_name);
|
|
|
|
int xpp_echocan_create(struct dahdi_chan *chan,
|
|
struct dahdi_echocanparams *ecp,
|
|
struct dahdi_echocanparam *p,
|
|
struct dahdi_echocan_state **ec)
|
|
{
|
|
xpd_t *xpd;
|
|
xbus_t *xbus;
|
|
int pos;
|
|
struct phonedev *phonedev;
|
|
const struct echoops *echoops;
|
|
int ret;
|
|
|
|
xpd = chan->pvt;
|
|
xbus = xpd->xbus;
|
|
pos = chan->chanpos - 1;
|
|
echoops = ECHOOPS(xbus);
|
|
if (!echoops)
|
|
return -ENODEV;
|
|
phonedev = &PHONEDEV(xpd);
|
|
*ec = phonedev->ec[pos];
|
|
(*ec)->ops = &xpp_ec_ops;
|
|
(*ec)->features = xpp_ec_features;
|
|
xpd = get_xpd(__FUNCTION__, xpd); /* Returned in echocan_free() */
|
|
LINE_DBG(GENERAL, xpd, pos, "(tap=%d, param_count=%d)\n",
|
|
ecp->tap_length, ecp->param_count);
|
|
ret = CALL_EC_METHOD(ec_set, xbus, xpd, pos, 1);
|
|
CALL_EC_METHOD(ec_update, xbus, xbus);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(xpp_echocan_create);
|
|
|
|
|
|
/**
|
|
* Unregister an xpd from dahdi and release related resources
|
|
* @xpd The xpd to be unregistered
|
|
* @returns 0 on success, errno otherwise
|
|
*
|
|
* Checks that nobody holds an open channel.
|
|
*
|
|
* Called by:
|
|
* - User action through /proc
|
|
* - During xpd_remove()
|
|
*/
|
|
int dahdi_unregister_xpd(xpd_t *xpd)
|
|
{
|
|
unsigned long flags;
|
|
|
|
BUG_ON(!xpd);
|
|
spin_lock_irqsave(&xpd->lock, flags);
|
|
|
|
if (!IS_PHONEDEV(xpd)) {
|
|
XPD_ERR(xpd, "Not a telephony device\n");
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return -EBADF;
|
|
}
|
|
if(!SPAN_REGISTERED(xpd)) {
|
|
XPD_NOTICE(xpd, "Already unregistered\n");
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return -EIDRM;
|
|
}
|
|
update_xpd_status(xpd, DAHDI_ALARM_NOTOPEN);
|
|
/* We should now have only a ref from the xbus (from create_xpd()) */
|
|
if(atomic_read(&PHONEDEV(xpd).open_counter)) {
|
|
XPD_NOTICE(xpd, "Busy (open_counter=%d). Skipping.\n", atomic_read(&PHONEDEV(xpd).open_counter));
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
return -EBUSY;
|
|
}
|
|
mdelay(2); // FIXME: This is to give chance for transmit/receiveprep to finish.
|
|
spin_unlock_irqrestore(&xpd->lock, flags);
|
|
if(xpd->card_present)
|
|
CALL_PHONE_METHOD(card_dahdi_preregistration, xpd, 0);
|
|
atomic_dec(&PHONEDEV(xpd).dahdi_registered);
|
|
atomic_dec(&num_registered_spans);
|
|
dahdi_unregister(&PHONEDEV(xpd).span);
|
|
if(xpd->card_present)
|
|
CALL_PHONE_METHOD(card_dahdi_postregistration, xpd, 0);
|
|
return 0;
|
|
}
|
|
|
|
static const struct dahdi_span_ops xpp_span_ops = {
|
|
.owner = THIS_MODULE,
|
|
.open = xpp_open,
|
|
.close = xpp_close,
|
|
.ioctl = xpp_ioctl,
|
|
.maint = xpp_maint,
|
|
.echocan_create = xpp_echocan_create,
|
|
.echocan_name = xpp_echocan_name,
|
|
};
|
|
|
|
static const struct dahdi_span_ops xpp_rbs_span_ops = {
|
|
.owner = THIS_MODULE,
|
|
.hooksig = xpp_hooksig,
|
|
.open = xpp_open,
|
|
.close = xpp_close,
|
|
.ioctl = xpp_ioctl,
|
|
.maint = xpp_maint,
|
|
.echocan_create = xpp_echocan_create,
|
|
.echocan_name = xpp_echocan_name,
|
|
};
|
|
|
|
int dahdi_register_xpd(xpd_t *xpd)
|
|
{
|
|
struct dahdi_span *span;
|
|
xbus_t *xbus;
|
|
int cn;
|
|
int i;
|
|
|
|
BUG_ON(!xpd);
|
|
|
|
xbus = xpd->xbus;
|
|
|
|
if (!IS_PHONEDEV(xpd)) {
|
|
XPD_ERR(xpd, "Not a telephony device\n");
|
|
return -EBADF;
|
|
}
|
|
if (SPAN_REGISTERED(xpd)) {
|
|
XPD_ERR(xpd, "Already registered\n");
|
|
return -EEXIST;
|
|
}
|
|
cn = PHONEDEV(xpd).channels;
|
|
XPD_DBG(DEVICES, xpd, "Initializing span: %d channels.\n", cn);
|
|
memset(&PHONEDEV(xpd).span, 0, sizeof(struct dahdi_span));
|
|
for(i = 0; i < cn; i++) {
|
|
memset(XPD_CHAN(xpd, i), 0, sizeof(struct dahdi_chan));
|
|
}
|
|
|
|
span = &PHONEDEV(xpd).span;
|
|
snprintf(span->name, MAX_SPANNAME, "%s/%s", xbus->busname, xpd->xpdname);
|
|
span->deflaw = DAHDI_LAW_MULAW; /* default, may be overriden by card_* drivers */
|
|
span->channels = cn;
|
|
span->chans = PHONEDEV(xpd).chans;
|
|
|
|
span->flags = DAHDI_FLAG_RBS;
|
|
if(PHONEDEV(xpd).phoneops->card_hooksig)
|
|
span->ops = &xpp_rbs_span_ops; /* Only with RBS bits */
|
|
else
|
|
span->ops = &xpp_span_ops;
|
|
|
|
/*
|
|
* This actually describe the dahdi_spaninfo version 3
|
|
* A bunch of unrelated data exported via a modified ioctl()
|
|
* What a bummer...
|
|
*/
|
|
span->manufacturer = "Xorcom Inc."; /* OK, that's obvious */
|
|
/* span->spantype = "...."; set in card_dahdi_preregistration() */
|
|
/*
|
|
* Yes, this basically duplicates information available
|
|
* from the description field. If some more is needed
|
|
* why not add it there?
|
|
* OK, let's add to the kernel more useless info.
|
|
*/
|
|
snprintf(span->devicetype, sizeof(span->devicetype) - 1,
|
|
"Astribank: Unit %x Subunit %x: %s",
|
|
XBUS_UNIT(xpd->xbus_idx), XBUS_SUBUNIT(xpd->xbus_idx),
|
|
xpd->type_name);
|
|
/*
|
|
* location is the only usefull new data item.
|
|
* For our devices it was available for ages via:
|
|
* - The legacy "/proc/xpp/XBUS-??/summary" (CONNECTOR=...)
|
|
* - The same info in "/proc/xpp/xbuses"
|
|
* - The modern "/sys/bus/astribanks/devices/xbus-??/connector" attribute
|
|
* So let's also export it via the newfangled "location" field.
|
|
*/
|
|
snprintf(span->location, sizeof(span->location) - 1, "%s", xbus->connector);
|
|
/*
|
|
* Who said a span and irq have 1-1 relationship?
|
|
* Also exporting this low-level detail isn't too wise.
|
|
* No irq's for you today!
|
|
*/
|
|
span->irq = 0;
|
|
|
|
snprintf(PHONEDEV(xpd).span.desc, MAX_SPANDESC, "Xorcom XPD #%02d/%1d%1d: %s",
|
|
xbus->num, xpd->addr.unit, xpd->addr.subunit, xpd->type_name);
|
|
XPD_DBG(GENERAL, xpd, "Registering span '%s'\n", PHONEDEV(xpd).span.desc);
|
|
CALL_PHONE_METHOD(card_dahdi_preregistration, xpd, 1);
|
|
if(dahdi_register(&PHONEDEV(xpd).span, prefmaster)) {
|
|
XPD_ERR(xpd, "Failed to dahdi_register span\n");
|
|
return -ENODEV;
|
|
}
|
|
atomic_inc(&num_registered_spans);
|
|
atomic_inc(&PHONEDEV(xpd).dahdi_registered);
|
|
CALL_PHONE_METHOD(card_dahdi_postregistration, xpd, 1);
|
|
/*
|
|
* Update dahdi about our state:
|
|
* - Since asterisk didn't open the channel yet,
|
|
* the report is discarded anyway.
|
|
* - Our FXS driver have another notification mechanism that
|
|
* is triggered (indirectly) by the open() of the channe.
|
|
* - The real fix should be in Asterisk (to get the correct state
|
|
* after open).
|
|
*/
|
|
for_each_line(xpd, cn) {
|
|
if(IS_OFFHOOK(xpd, cn))
|
|
notify_rxsig(xpd, cn, DAHDI_RXSIG_OFFHOOK);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*------------------------- Initialization -------------------------*/
|
|
|
|
static void do_cleanup(void)
|
|
{
|
|
#ifdef CONFIG_PROC_FS
|
|
if(xpp_proc_toplevel) {
|
|
DBG(GENERAL, "Removing '%s' from proc\n", PROC_DIR);
|
|
remove_proc_entry(PROC_DIR, NULL);
|
|
xpp_proc_toplevel = NULL;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
static int __init xpp_dahdi_init(void)
|
|
{
|
|
int ret = 0;
|
|
void *top = NULL;
|
|
|
|
INFO("revision %s MAX_XPDS=%d (%d*%d)\n", XPP_VERSION,
|
|
MAX_XPDS, MAX_UNIT, MAX_SUBUNIT);
|
|
#ifdef CONFIG_PROC_FS
|
|
xpp_proc_toplevel = proc_mkdir(PROC_DIR, NULL);
|
|
if(!xpp_proc_toplevel) {
|
|
ret = -EIO;
|
|
goto err;
|
|
}
|
|
top = xpp_proc_toplevel;
|
|
#endif
|
|
ret = xbus_core_init();
|
|
if(ret) {
|
|
ERR("xbus_core_init failed (%d)\n", ret);
|
|
goto err;
|
|
}
|
|
ret = xbus_pcm_init(top);
|
|
if(ret) {
|
|
ERR("xbus_pcm_init failed (%d)\n", ret);
|
|
xbus_core_shutdown();
|
|
goto err;
|
|
}
|
|
return 0;
|
|
err:
|
|
do_cleanup();
|
|
return ret;
|
|
}
|
|
|
|
static void __exit xpp_dahdi_cleanup(void)
|
|
{
|
|
xbus_pcm_shutdown();
|
|
xbus_core_shutdown();
|
|
do_cleanup();
|
|
}
|
|
|
|
EXPORT_SYMBOL(debug);
|
|
EXPORT_SYMBOL(create_xpd);
|
|
EXPORT_SYMBOL(xpd_post_init);
|
|
EXPORT_SYMBOL(get_xpd);
|
|
EXPORT_SYMBOL(put_xpd);
|
|
EXPORT_SYMBOL(xpd_alloc);
|
|
EXPORT_SYMBOL(xpd_free);
|
|
EXPORT_SYMBOL(xbus_request_removal);
|
|
EXPORT_SYMBOL(update_xpd_status);
|
|
EXPORT_SYMBOL(oht_pcm);
|
|
EXPORT_SYMBOL(mark_offhook);
|
|
EXPORT_SYMBOL(notify_rxsig);
|
|
EXPORT_SYMBOL(hookstate_changed);
|
|
EXPORT_SYMBOL(xpp_open);
|
|
EXPORT_SYMBOL(xpp_close);
|
|
EXPORT_SYMBOL(xpp_ioctl);
|
|
EXPORT_SYMBOL(xpp_maint);
|
|
EXPORT_SYMBOL(report_bad_ioctl);
|
|
|
|
MODULE_DESCRIPTION("XPP Dahdi Driver");
|
|
MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
|
|
MODULE_LICENSE("GPL");
|
|
MODULE_VERSION(XPP_VERSION);
|
|
|
|
module_init(xpp_dahdi_init);
|
|
module_exit(xpp_dahdi_cleanup);
|