summaryrefslogtreecommitdiff
path: root/drivers/dahdi/xpp/xpp_dahdi.c
diff options
context:
space:
mode:
authorTzafrir Cohen <tzafrir.cohen@xorcom.com>2008-06-19 18:13:11 +0000
committerTzafrir Cohen <tzafrir.cohen@xorcom.com>2008-06-19 18:13:11 +0000
commit74856ecf5237acc6f5ca08ce0cefb354316084be (patch)
treeec81fa4dc155bf0aac74e631d51d558f8e0b74f7 /drivers/dahdi/xpp/xpp_dahdi.c
parent9d886a8e34490fabc3a1e3a0ebba5f8a43a6fd49 (diff)
Yet another zaptel->dahdi rename. XPP file names this time.
git-svn-id: http://svn.asterisk.org/svn/dahdi/linux/trunk@4418 a0bf4364-ded3-4de4-8d8a-66a801d63aff
Diffstat (limited to 'drivers/dahdi/xpp/xpp_dahdi.c')
-rw-r--r--drivers/dahdi/xpp/xpp_dahdi.c1075
1 files changed, 1075 insertions, 0 deletions
diff --git a/drivers/dahdi/xpp/xpp_dahdi.c b/drivers/dahdi/xpp/xpp_dahdi.c
new file mode 100644
index 0000000..8ee23f7
--- /dev/null
+++ b/drivers/dahdi/xpp/xpp_dahdi.c
@@ -0,0 +1,1075 @@
+/*
+ * 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/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_ZTREGISTER "dahdi_registration"
+#define PROC_XPD_BLINK "blink"
+#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"
+
+#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);
+}
+
+static int dahdi_register_xpd(xpd_t *xpd);
+static int dahdi_unregister_xpd(xpd_t *xpd);
+static int xpd_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int proc_xpd_ztregister_read(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int proc_xpd_ztregister_write(struct file *file, const char __user *buffer, unsigned long count, void *data);
+static int proc_xpd_blink_read(char *page, char **start, off_t off, int count, int *eof, void *data);
+static int proc_xpd_blink_write(struct file *file, const char __user *buffer, unsigned long count, void *data);
+
+/*------------------------- XPD Management -------------------------*/
+
+static void xpd_proc_remove(xbus_t *xbus, xpd_t *xpd)
+{
+#ifdef CONFIG_PROC_FS
+ if(xpd->proc_xpd_dir) {
+ chip_proc_remove(xbus, xpd);
+ 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;
+ }
+ if(xpd->proc_xpd_ztregister) {
+ XPD_DBG(PROC, xpd, "Removing proc '%s'\n", PROC_XPD_ZTREGISTER);
+ remove_proc_entry(PROC_XPD_ZTREGISTER, xpd->proc_xpd_dir);
+ xpd->proc_xpd_ztregister = NULL;
+ }
+ if(xpd->proc_xpd_blink) {
+ XPD_DBG(PROC, xpd, "Removing proc '%s'\n", PROC_XPD_BLINK);
+ remove_proc_entry(PROC_XPD_BLINK, xpd->proc_xpd_dir);
+ xpd->proc_xpd_blink = 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;
+ }
+ xpd->proc_xpd_summary->owner = THIS_MODULE;
+ xpd->proc_xpd_ztregister = create_proc_entry(PROC_XPD_ZTREGISTER, 0644, xpd->proc_xpd_dir);
+ if (!xpd->proc_xpd_ztregister) {
+ XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_XPD_ZTREGISTER);
+ goto err;
+ }
+ xpd->proc_xpd_ztregister->owner = THIS_MODULE;
+ xpd->proc_xpd_ztregister->data = xpd;
+ xpd->proc_xpd_ztregister->read_proc = proc_xpd_ztregister_read;
+ xpd->proc_xpd_ztregister->write_proc = proc_xpd_ztregister_write;
+ xpd->proc_xpd_blink = create_proc_entry(PROC_XPD_BLINK, 0644, xpd->proc_xpd_dir);
+ if (!xpd->proc_xpd_blink) {
+ XPD_ERR(xpd, "Failed to create proc file '%s'\n", PROC_XPD_BLINK);
+ goto err;
+ }
+ xpd->proc_xpd_blink->owner = THIS_MODULE;
+ xpd->proc_xpd_blink->data = xpd;
+ xpd->proc_xpd_blink->read_proc = proc_xpd_blink_read;
+ xpd->proc_xpd_blink->write_proc = proc_xpd_blink_write;
+ if(chip_proc_create(xbus, xpd) < 0)
+ goto err;
+#endif
+ return 0;
+err:
+ xpd_proc_remove(xbus, xpd);
+ return -EFAULT;
+}
+
+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_unregister_xpd(xbus, xpd);
+ KZFREE(xpd);
+}
+
+
+__must_check int xpd_common_init(xbus_t *xbus, xpd_t *xpd, int unit, int subunit, int subtype, int subunits)
+{
+ int ret;
+
+ MKADDR(&xpd->addr, unit, subunit);
+ xpd->xbus_idx = XPD_IDX(unit,subunit);
+ snprintf(xpd->xpdname, XPD_NAMELEN, "XPD-%1d%1d", unit, subunit);
+ xpd->subtype = subtype;
+ xpd->subunits = subunits;
+ xpd->offhook = 0;
+
+ /* For USB-1 disable some channels */
+ if(MAX_SEND_SIZE(xbus) < RPACKET_SIZE(GLOBAL, PCM_WRITE)) {
+ xpp_line_t no_pcm;
+
+ no_pcm = 0x7F | xpd->digital_outputs | xpd->digital_inputs;
+ xpd->no_pcm = no_pcm;
+ XBUS_NOTICE(xbus, "max xframe size = %d, disabling some PCM channels. no_pcm=0x%04X\n",
+ MAX_SEND_SIZE(xbus), xpd->no_pcm);
+ }
+ if((ret = xpd_proc_create(xbus, xpd)) < 0)
+ return ret;
+ xbus_register_xpd(xbus, xpd);
+ return 0;
+}
+
+/*
+ * Synchronous part of XPD detection.
+ * Called from xbus_poll()
+ */
+int create_xpd(xbus_t *xbus, const xproto_table_t *proto_table,
+ int unit,
+ int subunit,
+ byte type,
+ byte subtype,
+ int subunits,
+ byte port_dir)
+{
+ xpd_t *xpd = NULL;
+ bool to_phone;
+ int ret = -EINVAL;
+
+ 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);
+ goto out;
+ }
+ xpd = proto_table->xops.card_new(xbus, unit, subunit, proto_table, subtype, subunits, 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);
+ goto err;
+ }
+out:
+ return 0;
+err:
+ if(xpd)
+ xpd_free(xpd);
+ return ret;
+}
+
+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;
+ xbus_t *xbus;
+ int i;
+
+ if(!xpd)
+ goto out;
+
+ xbus = xpd->xbus;
+ 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)) ? xpd->span.spanno : 0,
+ xpd->timing_priority,
+ xpd->timer_count, xpd->span.mainttimer
+ );
+ 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", xpd->pcm_len);
+ len += sprintf(page + len, "wanted_pcm_mask=0x%04X\n\n", xpd->wanted_pcm_mask);
+ len += sprintf(page + len, "mute_dtmf=0x%04X\n\n", 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(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(xpd->digital_inputs, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "offhook");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%d ", IS_SET(xpd->offhook, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "cid_on");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%d ", IS_SET(xpd->cid_on, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "msg_waiting");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%d ", IS_SET(xpd->msg_waiting, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "ringing");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%d ", xpd->ringing[i]);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "no_pcm");
+ for_each_line(xpd, i) {
+ len += sprintf(page + len, "%d ", IS_SET(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 *chans = xpd->span.chans;
+ byte rchunk[DAHDI_CHUNKSIZE];
+ byte wchunk[DAHDI_CHUNKSIZE];
+ byte *rp;
+ byte *wp;
+ int j;
+
+ if(IS_SET(xpd->digital_outputs, i))
+ continue;
+ if(IS_SET(xpd->digital_inputs, i))
+ continue;
+ if(IS_SET(xpd->digital_signalling, i))
+ continue;
+ rp = chans[i].readchunk;
+ wp = chans[i].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(xpd->wanted_pcm_mask, i))?'+':' ');
+ len += sprintf(page + len, " %c",
+ (IS_SET(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->span.chans[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
+
+/*
+ * xpd_alloc - Allocator for new XPD's
+ *
+ */
+xpd_t *xpd_alloc(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;
+
+ BUG_ON(!proto_table);
+ DBG(DEVICES, "type=%d channels=%d (alloc_size=%zd)\n",
+ type, channels, alloc_size);
+ if(channels > CHANNELS_PERXPD) {
+ ERR("%s: type=%d: too many channels %d\n",
+ __FUNCTION__, type, channels);
+ goto err;
+ }
+
+ if((xpd = KZALLOC(alloc_size, GFP_KERNEL)) == NULL) {
+ ERR("%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->xbus = NULL;
+ xpd->xbus_idx = -1;
+ xpd->channels = channels;
+ xpd->chans = NULL;
+ xpd->card_present = 0;
+ xpd->offhook = 0x0; /* ONHOOK */
+ xpd->type = proto_table->type;
+ xpd->xproto = proto_table;
+ xpd->xops = &proto_table->xops;
+ xpd->digital_outputs = 0;
+ xpd->digital_inputs = 0;
+
+ atomic_set(&xpd->dahdi_registered, 0);
+ atomic_set(&xpd->open_counter, 0);
+
+ xpd->chans = kmalloc(sizeof(struct dahdi_chan)*xpd->channels, GFP_KERNEL);
+ if (xpd->chans == NULL) {
+ ERR("%s: Unable to allocate channels\n", __FUNCTION__);
+ goto err;
+ }
+ xproto_get(type); /* will be returned in xpd_free() */
+ return xpd;
+err:
+ if(xpd) {
+ if(xpd->chans)
+ kfree((void *)xpd->chans);
+ kfree(xpd);
+ }
+ return NULL;
+}
+
+/* FIXME: this should be removed once digium patch their dahdi.h
+ * I simply wish to avoid changing dahdi.h in the xpp patches.
+ */
+#ifndef DAHDI_EVENT_REMOVED
+#define DAHDI_EVENT_REMOVED (20)
+#endif
+
+void xpd_disconnect(xpd_t *xpd)
+{
+ unsigned long flags;
+
+ BUG_ON(!xpd);
+
+ spin_lock_irqsave(&xpd->lock, flags);
+ XPD_DBG(DEVICES, xpd, "(%p)\n", xpd->xproto);
+ if(!xpd->card_present) /* Multiple reports */
+ goto out;
+ xpd->card_present = 0;
+ if(SPAN_REGISTERED(xpd)) {
+ int i;
+
+ update_xpd_status(xpd, DAHDI_ALARM_NOTOPEN);
+ /* TODO: Should this be done before releasing the spinlock? */
+ XPD_DBG(DEVICES, xpd, "Queuing DAHDI_EVENT_REMOVED on all channels to ask user to release them\n");
+ for (i=0; i<xpd->span.channels; i++)
+ dahdi_qevent_lock(&xpd->chans[i],DAHDI_EVENT_REMOVED);
+ }
+out:
+ spin_unlock_irqrestore(&xpd->lock, flags);
+}
+
+void xpd_remove(xpd_t *xpd)
+{
+ xbus_t *xbus;
+
+ BUG_ON(!xpd);
+ xbus = xpd->xbus;
+ XPD_INFO(xpd, "Remove\n");
+ dahdi_unregister_xpd(xpd);
+ CALL_XMETHOD(card_remove, xbus, xpd);
+ xpd_free(xpd);
+}
+
+void update_xpd_status(xpd_t *xpd, int alarm_flag)
+{
+ struct dahdi_span *span = &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;
+ span->alarms = alarm_flag;
+ dahdi_alarm_notify(span);
+ XPD_DBG(GENERAL, xpd, "Update XPD alarms: %s -> %02X\n", xpd->span.name, alarm_flag);
+}
+
+void update_line_status(xpd_t *xpd, int pos, bool to_offhook)
+{
+ dahdi_rxsig_t rxsig;
+
+ BUG_ON(!xpd);
+ if(to_offhook) {
+ BIT_SET(xpd->offhook, pos);
+ rxsig = DAHDI_RXSIG_OFFHOOK;
+ } else {
+ BIT_CLR(xpd->offhook, pos);
+ BIT_CLR(xpd->cid_on, pos);
+ rxsig = DAHDI_RXSIG_ONHOOK;
+ /*
+ * 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(xpd->silence_pcm, pos);
+ }
+ /*
+ * 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", (rxsig == DAHDI_RXSIG_ONHOOK) ? "ONHOOK" : "OFFHOOK");
+ if(SPAN_REGISTERED(xpd))
+ dahdi_hooksig(&xpd->chans[pos], rxsig);
+}
+
+#ifdef CONFIG_PROC_FS
+static int proc_xpd_ztregister_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long flags;
+ xpd_t *xpd = data;
+
+ BUG_ON(!xpd);
+ spin_lock_irqsave(&xpd->lock, flags);
+
+ len += sprintf(page + len, "%d\n", SPAN_REGISTERED(xpd) ? xpd->span.spanno : 0);
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ if (len <= off+count)
+ *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+static int proc_xpd_ztregister_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
+{
+ xpd_t *xpd = data;
+ char buf[MAX_PROC_WRITE];
+ int dahdi_reg;
+ int ret;
+
+ BUG_ON(!xpd);
+ if(count >= MAX_PROC_WRITE)
+ return -EINVAL;
+ if(copy_from_user(buf, buffer, count))
+ return -EFAULT;
+ buf[count] = '\0';
+ ret = sscanf(buf, "%d", &dahdi_reg);
+ if(ret != 1)
+ return -EINVAL;
+ XPD_DBG(GENERAL, xpd, "%s\n", (dahdi_reg) ? "register" : "unregister");
+ if(dahdi_reg)
+ ret = dahdi_register_xpd(xpd);
+ else
+ ret = dahdi_unregister_xpd(xpd);
+ return (ret < 0) ? ret : count;
+}
+
+static int proc_xpd_blink_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long flags;
+ xpd_t *xpd = data;
+
+ BUG_ON(!xpd);
+ spin_lock_irqsave(&xpd->lock, flags);
+
+ len += sprintf(page + len, "%d\n", xpd->blink_mode);
+ spin_unlock_irqrestore(&xpd->lock, flags);
+ if (len <= off+count)
+ *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+ return len;
+}
+
+static int proc_xpd_blink_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
+{
+ xpd_t *xpd = data;
+ char buf[MAX_PROC_WRITE];
+ char *endp;
+ unsigned blink;
+
+
+ BUG_ON(!xpd);
+ if(count >= MAX_PROC_WRITE)
+ return -EINVAL;
+ if(copy_from_user(buf, buffer, count))
+ return -EFAULT;
+ buf[count] = '\0';
+ if(count > 0 && buf[count-1] == '\n') /* chomp */
+ buf[count-1] = '\0';
+ blink = simple_strtoul(buf, &endp, 0);
+ if(*endp != '\0' || blink > 0xFFFF)
+ return -EINVAL;
+ XPD_DBG(GENERAL, xpd, "BLINK channels: 0x%X\n", blink);
+ xpd->blink_mode = blink;
+ return count;
+}
+
+#endif
+
+
+#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)
+{
+#if 0
+ xpd_t *xpd = chan->pvt;
+ xbus_t *xbus = xpd->xbus;
+ int pos = chan->chanpos - 1;
+ unsigned long flags;
+#else
+ 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;
+ if (!xpd) {
+ NOTICE("open called on a chan with no pvt (xpd)\n");
+ return -EINVAL;
+ }
+ xbus = xpd->xbus;
+ if (!xbus) {
+ NOTICE("open called on a chan with no xbus\n");
+ return -EINVAL;
+ }
+ pos = chan->chanpos - 1;
+#endif
+
+ spin_lock_irqsave(&xbus->lock, flags);
+ atomic_inc(&xbus->xbus_ref_count);
+ atomic_inc(&xpd->open_counter);
+ if(IS_SET(xpd->digital_signalling, pos)) /* D-chan offhook */
+ BIT_SET(xpd->offhook, pos);
+ DBG(DEVICES, "chan=%d (xbus_ref_count=%d)\n",
+ pos, atomic_read(&xbus->xbus_ref_count));
+ spin_unlock_irqrestore(&xbus->lock, flags);
+ if(xpd->xops->card_open)
+ xpd->xops->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);
+ atomic_dec(&xpd->open_counter);
+ if(IS_SET(xpd->digital_signalling, pos)) /* D-chan onhook */
+ BIT_CLR(xpd->offhook, pos);
+ spin_unlock_irqrestore(&xbus->lock, flags);
+ if(xpd->xops->card_close)
+ xpd->xops->card_close(xpd, pos);
+ XPD_DBG(GENERAL, xpd, "pid=%d: chan=%d (xbus_ref_count=%d)\n",
+ current->pid, pos, atomic_read(&xbus->xbus_ref_count));
+ if(atomic_dec_and_test(&xbus->xbus_ref_count))
+ xbus_remove(xbus);
+ return 0;
+}
+
+void report_bad_ioctl(const char *msg, xpd_t *xpd, int pos, unsigned int cmd)
+{
+ XPD_NOTICE(xpd, "%s: Bad ioctl\n", 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 (xpd->xops->card_ioctl != NULL) {
+ return xpd->xops->card_ioctl(xpd, pos, cmd, arg);
+ }
+ report_bad_ioctl(THIS_MODULE->name, xpd, pos, cmd);
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int xpp_hooksig(struct dahdi_chan *chan, dahdi_txsig_t 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;
+ }
+ xbus = xpd->xbus;
+ BUG_ON(!xbus);
+ DBG(SIGNAL, "Setting %s to %s (%d)\n", chan->name, txsig2str(txsig), txsig);
+ return CALL_XMETHOD(card_hooksig, xbus, xpd, pos, txsig);
+}
+
+/* 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)
+{
+ xpd_t *xpd = span->pvt;
+ 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:
+ printk("XXX Turn off local and remote loops XXX\n");
+ break;
+ case DAHDI_MAINT_LOCALLOOP:
+ printk("XXX Turn on local loopback XXX\n");
+ break;
+ case DAHDI_MAINT_REMOTELOOP:
+ printk("XXX Turn on remote loopback XXX\n");
+ break;
+ case DAHDI_MAINT_LOOPUP:
+ printk("XXX Send loopup code XXX\n");
+ // CALL_XMETHOD(LOOPBACK_AX, xpd->xbus, xpd, loopback_data, ARRAY_SIZE(loopback_data));
+ break;
+ case DAHDI_MAINT_LOOPDOWN:
+ printk("XXX Send loopdown code XXX\n");
+ break;
+ case DAHDI_MAINT_LOOPSTOP:
+ printk("XXX Stop sending loop codes 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
+
+/**
+ * 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()
+ */
+static int dahdi_unregister_xpd(xpd_t *xpd)
+{
+ unsigned long flags;
+
+ BUG_ON(!xpd);
+ spin_lock_irqsave(&xpd->lock, flags);
+
+ 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);
+ if(atomic_read(&xpd->open_counter)) {
+ XPD_NOTICE(xpd, "Busy (open_counter=%d). Skipping.\n", atomic_read(&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)
+ xpd->xops->card_dahdi_preregistration(xpd, 0);
+ atomic_dec(&xpd->dahdi_registered);
+ atomic_dec(&num_registered_spans);
+ dahdi_unregister(&xpd->span);
+ if(xpd->card_present)
+ xpd->xops->card_dahdi_postregistration(xpd, 0);
+ return 0;
+}
+
+static int dahdi_register_xpd(xpd_t *xpd)
+{
+ struct dahdi_span *span;
+ xbus_t *xbus;
+ int cn;
+ const xops_t *xops;
+
+ BUG_ON(!xpd);
+ xops = xpd->xops;
+ xbus = xpd->xbus;
+
+ if (SPAN_REGISTERED(xpd)) {
+ XPD_ERR(xpd, "Already registered\n");
+ return -EEXIST;
+ }
+ cn = xpd->channels;
+ XPD_DBG(DEVICES, xpd, "Initializing span: %d channels.\n", cn);
+ memset(xpd->chans, 0, sizeof(struct dahdi_chan)*cn);
+ memset(&xpd->span, 0, sizeof(struct dahdi_span));
+
+ span = &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 */
+ init_waitqueue_head(&span->maintq);
+ span->pvt = xpd;
+ span->channels = cn;
+ span->chans = xpd->chans;
+
+ span->open = xpp_open;
+ span->close = xpp_close;
+ span->flags = DAHDI_FLAG_RBS;
+ span->hooksig = xpp_hooksig; /* Only with RBS bits */
+ span->ioctl = xpp_ioctl;
+ span->maint = xpp_maint;
+#ifdef DAHDI_SPANSTAT_V2
+ /*
+ * 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->location);
+ /*
+ * 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;
+#endif
+#ifdef DAHDI_SYNC_TICK
+ span->sync_tick = dahdi_sync_tick;
+#endif
+ if (xpp_ec)
+ span->echocan = xpp_echocan;
+#ifdef CONFIG_DAHDI_WATCHDOG
+ span->watchdog = xpp_watchdog;
+#endif
+
+ snprintf(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", xpd->span.desc);
+ xpd->xops->card_dahdi_preregistration(xpd, 1);
+ if(dahdi_register(&xpd->span, prefmaster)) {
+ XPD_ERR(xpd, "Failed to dahdi_register span\n");
+ return -ENODEV;
+ }
+ atomic_inc(&num_registered_spans);
+ atomic_inc(&xpd->dahdi_registered);
+ xpd->xops->card_dahdi_postregistration(xpd, 1);
+ /*
+ * Update dahdi about our state
+ */
+#if 0
+ /*
+ * FIXME: since asterisk didn't open the channel yet, the report
+ * is discarded anyway. OTOH, we cannot report in xpp_open or
+ * xpp_chanconfig since dahdi call them with a spinlock on the channel
+ * and dahdi_hooksig tries to acquire the same spinlock, resulting in
+ * double spinlock deadlock (we are lucky that RH/Fedora kernel are
+ * compiled with spinlock debugging).... tough.
+ */
+ for_each_line(xpd, cn) {
+ struct dahdi_chan *chans = xpd->span.chans;
+
+ if(IS_SET(xpd->offhook, cn)) {
+ LINE_NOTICE(xpd, cn, "Report OFFHOOK to dahdi\n");
+ dahdi_hooksig(&chans[cn], DAHDI_RXSIG_OFFHOOK);
+ }
+ }
+#endif
+ 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;
+
+ INFO("revision %s MAX_XPDS=%d (%d*%d)\n", XPP_VERSION,
+ MAX_XPDS, MAX_UNIT, MAX_SUBUNIT);
+#ifdef CONFIG_ZAPATA_BRI_DCHANS
+ INFO("FEATURE: with BRISTUFF support\n");
+#else
+ INFO("FEATURE: without BRISTUFF support\n");
+#endif
+#ifdef CONFIG_PROC_FS
+ xpp_proc_toplevel = proc_mkdir(PROC_DIR, NULL);
+ if(!xpp_proc_toplevel) {
+ ret = -EIO;
+ goto err;
+ }
+#endif
+ ret = xbus_core_init();
+ if(ret) {
+ ERR("xbus_core_init failed (%d)\n", ret);
+ goto err;
+ }
+ ret = xbus_pcm_init(xpp_proc_toplevel);
+ 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(xpd_common_init);
+EXPORT_SYMBOL(create_xpd);
+EXPORT_SYMBOL(xpd_post_init);
+EXPORT_SYMBOL(xpd_alloc);
+EXPORT_SYMBOL(xpd_free);
+EXPORT_SYMBOL(xpd_disconnect);
+EXPORT_SYMBOL(update_xpd_status);
+EXPORT_SYMBOL(update_line_status);
+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);