summaryrefslogtreecommitdiff
path: root/xpp/xpp_zap.c
diff options
context:
space:
mode:
authorkpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-02-15 02:26:14 +0000
committerkpfleming <kpfleming@5390a7c7-147a-4af0-8ec9-7488f05a26cb>2006-02-15 02:26:14 +0000
commita9ababa28c7ccedaf4be1501d009f99d9a7ba580 (patch)
treeb9259934f16b50693c73f9300287a7f92936837c /xpp/xpp_zap.c
parent590e1c07e60d8564388533bb85deee9531df4893 (diff)
Merged revisions 949 via svnmerge from
https://origsvn.digium.com/svn/zaptel/branches/1.2 ........ r949 | kpfleming | 2006-02-14 20:24:18 -0600 (Tue, 14 Feb 2006) | 2 lines initial import of Xorcom Astribank driver (issue #6452, with minor mods) ........ git-svn-id: http://svn.digium.com/svn/zaptel/trunk@950 5390a7c7-147a-4af0-8ec9-7488f05a26cb
Diffstat (limited to 'xpp/xpp_zap.c')
-rw-r--r--xpp/xpp_zap.c2312
1 files changed, 2312 insertions, 0 deletions
diff --git a/xpp/xpp_zap.c b/xpp/xpp_zap.c
new file mode 100644
index 0000000..878bad7
--- /dev/null
+++ b/xpp/xpp_zap.c
@@ -0,0 +1,2312 @@
+/*
+ * 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/init.h>
+#include <linux/delay.h> /* for udelay */
+#include <linux/workqueue.h>
+#include <linux/proc_fs.h>
+#include <zaptel.h>
+#include "xproto.h"
+#include "xpp_zap.h"
+
+static char revision[] = "$Revision$";
+
+#ifdef CONFIG_PROC_FS
+struct proc_dir_entry *xpp_procdir = NULL;
+#define PROC_DIR "xpp"
+#define PROC_XBUSES "xbuses"
+#define PROC_SYNC "sync"
+#define PROC_XBUS_SUMMARY "summary"
+#define PROC_XPD_ZTREGISTER "zt_registration"
+#define PROC_XPD_SUMMARY "summary"
+#endif
+
+#undef WITH_RBS
+//#define WITH_RBS
+
+#define XPP_CTL_MAJOR 42
+#define MAX_BUSES 16
+#define MAX_QUEUE_LEN 10000
+#define LED_BLINK_PERIOD (HZ/8)
+#define SAMPLE_TICKS 10000
+
+static spinlock_t xbuses_lock = SPIN_LOCK_UNLOCKED;
+static xbus_t *xbuses_array[MAX_BUSES] = {};
+static int bus_count = 0;
+static struct timer_list xpp_timer;
+xpd_t *sync_master = NULL; // Start with host based sync
+static unsigned int xpp_timer_count = 0;
+static unsigned int xpp_last_jiffies = 0;
+struct workqueue_struct *xpp_worker = NULL;
+
+static LIST_HEAD(xpd_list);
+
+DEF_PARM(int, print_dbg, 1, "Print DBG statements");
+DEF_PARM(int, max_queue_len, MAX_QUEUE_LEN, "Maximum Queue Length.");
+DEF_PARM(int, xbus_err_disable_bus, 1000, "Number of errors needed to disable bus");
+DEF_PARM(int, ignore_xpds, 0, "a bitmask of xpd numbers to ignore");
+#ifdef SOFT_SIMULATOR
+DEF_PARM(ulong, softloop_xpds, 0, "a bitmask of software xpd numbers");
+#endif
+DEF_PARM(ulong, pcm_gen, 0, "a bitmask of line numbers for hardware tone generator");
+
+DEF_ARRAY(ulong, enabled_channels, MAX_XPDS, ~0, "Enabled channels for each xpd");
+
+#include "zap_debug.h"
+
+
+static int xpd_zaptel_register(xpd_t *xpd);
+static int xpd_zaptel_unregister(xpd_t *xpd);
+static void xbus_remove(xbus_t *xbus);
+static void xpd_blink_leds(xpd_t *xpd);
+static void xpp_ring_generate(xpd_t *xpd);
+static void xpp_transmitprep(xpd_t *xpd);
+static void xpp_receiveprep(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);
+xbus_t *xbus_of(int xbus_num);
+static void xpd_cleanup(xpd_t *xpd);
+static void xpd_card_disable(xpd_t *xpd);
+static void update_xpd_status(xpd_t *xpd, int alarm_flag);
+
+#define SPAN_REGISTERED(xpd) ((xpd)->span.flags & ZT_FLAG_REGISTERED)
+
+/*------------------------- Packet Handling ------------------------*/
+static kmem_cache_t *packet_cache = NULL;
+static atomic_t xpacket_count = ATOMIC_INIT(0);
+
+void card_detected(void *data)
+{
+ struct card_desc_struct *card_desc = (struct card_desc_struct *)data;
+ xbus_t *xbus;
+ xpd_t *xpd;
+ int xpd_num;
+ byte type;
+ byte rev;
+ const xops_t *xops;
+ const xproto_table_t *proto_table;
+
+ BUG_ON(!card_desc);
+ BUG_ON(card_desc->magic != CARD_DESC_MAGIC);
+ xbus = card_desc->xbus;
+ xpd_num = card_desc->xpd_num;
+ type = card_desc->type;
+ rev = card_desc->rev;
+ BUG_ON(!xbus);
+ DBG("%s: xpd_num=%d type=%d rev=%d\n", xbus->busname, xpd_num, type, rev);
+ xpd = xpd_of(xbus, xpd_num);
+ if(xpd) {
+ if(type == XPD_TYPE(NOMODULE)) {
+ NOTICE("%s: xpd #%d: removed\n", __FUNCTION__, xpd_num);
+ xpd_card_disable(xpd);
+ goto out;
+ }
+ NOTICE("%s: xpd #%d: already exists\n", __FUNCTION__, xpd_num);
+ goto out;
+ }
+ if(type == XPD_TYPE(NOMODULE)) {
+ DBG("No module at address=%d\n", xpd_num);
+ goto out;
+ }
+ proto_table = get_xproto_table(type);
+ if(!proto_table) {
+ NOTICE("%s: xpd #%d: missing protocol table for type=%d. Ignored.\n", __FUNCTION__, xpd_num, type);
+ goto out;
+ }
+ xops = &proto_table->xops;
+ BUG_ON(!xops);
+ xpd = xops->card_new(xbus, xpd_num, proto_table, rev);
+ if(!xpd) {
+ NOTICE("card_new(%s,%d,%d,%d) failed. Ignored.\n", xbus->busname, xpd_num, proto_table->type, rev);
+ goto out;
+ }
+#if 0
+ /*
+ * Is it nessessary?
+ */
+ if(xpd->type == XPD_TYPE_FXO) {
+ int i;
+
+ for(i = 0; i < xpd->channels; i++) {
+ zt_hooksig(&xpd->chans[i], ZT_RXSIG_ONHOOK);
+ }
+ }
+#endif
+#ifdef CONFIG_PROC_FS
+ DBG("Creating xpd proc directory for %s/%s\n", xbus->busname, xpd->xpdname);
+ xpd->proc_xpd_dir = proc_mkdir(xpd->xpdname, xbus->proc_xbus_dir);
+ if(!xpd->proc_xpd_dir) {
+ ERR("Failed to create proc directory for %s/%s\n", xbus->busname, xpd->xpdname);
+ 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) {
+ ERR("Failed to create proc '%s' for %s/%s\n", PROC_XPD_SUMMARY, xbus->busname, xpd->xpdname);
+ goto err;
+ }
+ xpd->proc_xpd_ztregister = create_proc_entry(PROC_XPD_ZTREGISTER, 0644, xpd->proc_xpd_dir);
+ if (!xpd->proc_xpd_ztregister) {
+ ERR("Failed to create proc '%s' for %s/%s\n", PROC_XPD_ZTREGISTER, xbus->busname, xpd->xpdname);
+ goto err;
+ }
+ 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;
+#endif
+ list_add(&xpd->xpd_list, &xpd_list);
+ xbus->xpds[xpd->id] = xpd;
+ xbus->num_xpds++;
+ CALL_XMETHOD(card_init, xbus, xpd);
+ // Turn off all channels
+ CALL_XMETHOD(CHAN_ENABLE, xbus, xpd, 0xFF, 0);
+// CALL_XMETHOD(LED, xbus, xpd, 0xFF, LED_RED, 0); // FIXME: Show activated channels
+ // Turn on enabled channels
+ CALL_XMETHOD(CHAN_ENABLE, xbus, xpd, xpd->enabled_chans, 1);
+ atomic_set(&xpd->card_present, 1);
+ xpd_zaptel_register(xpd);
+#if 0
+ // FIXME: not yet initialized...
+ xpp_check_hookstate(xpd, line_status);
+#endif
+
+out:
+ memset(card_desc, 0, sizeof(struct card_desc_struct));
+ kfree(card_desc);
+ return;
+err:
+ xpd_cleanup(xpd);
+ goto out;
+}
+
+/**
+ * Allocates a new XPP packet.
+ * @xbus The XPP bus in which the packet will flow (for counters
+ * maintenance)
+ * @flags Flags for kernel memory allocation.
+ * @returns A pointer to the new packet, or NULL in case of failure.
+ *
+ *
+ * Packet allocation/deallocation:
+ * Sent packets:
+ * - Allocated by protocol commands
+ * - Deallocated by xmus_xmitter
+ * Receive packets:
+ * - Allocated/deallocated by xbus_xmiter
+ */
+xpacket_t *softloop_packet_new(xbus_t *xbus, int flags)
+{
+ xpacket_t *pack;
+
+ /* To avoid races we increament counter in advance and decrement it later
+ * in case of failure */
+ atomic_inc(&xbus->packet_counter);
+ //DBG("Incremented packet_counter of bus %s (new packet) to %d\n",
+ // xbus->busname, atomic_read(&xbus->packet_counter));
+ pack = kmem_cache_alloc(packet_cache, flags);
+ if (pack) {
+ memset(pack, 0, sizeof(xpacket_t));
+ atomic_inc(&xpacket_count);
+ } else {
+ atomic_dec(&xbus->packet_counter);
+ //DBG("Decremented packet_counter of bus %s (failed new packet) to %d\n",
+ // xbus->busname, atomic_read(&xbus->packet_counter));
+ }
+ return pack;
+}
+
+void softloop_packet_free(xbus_t *xbus, xpacket_t *p)
+{
+ kmem_cache_free(packet_cache, p);
+ atomic_dec(&xpacket_count);
+ atomic_dec(&xbus->packet_counter);
+ //DBG("Decremented packet_counter of bus %s (freed packet) to %d\n",
+ // xbus->busname, atomic_read(&xbus->packet_counter));
+}
+
+int call_proto(xbus_t *xbus, xpacket_t *pack)
+{
+ const xproto_entry_t *xe;
+ int toxpd = XPD_NUM(pack->content.addr);
+ xpd_t *xpd = xpd_of(xbus, toxpd);
+
+ xe = find_xproto_entry(xpd, pack->content.opcode);
+ return 0;
+}
+
+static void external_sync(xpd_t *the_xpd)
+{
+ int i, j;
+
+ DBG("SYNC %s\n", (the_xpd) ? "EXTERNAL" : "HOST");
+ for(i = 0; i < MAX_BUSES; i++) {
+ xbus_t *xbus = xbus_of(i);
+ if(!xbus)
+ continue;
+ if (!xbus->hardware_exists)
+ continue;
+ for(j = 0; j < MAX_XPDS; j++) {
+ xpd_t *xpd = xbus->xpds[j];
+ if(xpd)
+ CALL_XMETHOD(SYNC_SOURCE, xbus, xpd, 1, (the_xpd != NULL));
+ }
+ }
+}
+
+void set_sync_master(xpd_t *xpd)
+{
+ DBG("SYNC: %s => %s\n",
+ (sync_master) ? sync_master->xpdname : "HOST",
+ (xpd) ? xpd->xpdname : "HOST"
+ );
+ sync_master = xpd;
+ if(!sync_master) {
+ external_sync(NULL);
+ if(!timer_pending(&xpp_timer)) {
+ xpp_timer.function = xpp_tick;
+ xpp_timer.data = 0;
+ xpp_timer.expires = jiffies + 1; /* Must be 1KHz rate */
+ add_timer(&xpp_timer);
+ }
+ } else {
+ del_timer_sync(&xpp_timer);
+ external_sync(xpd);
+ xpp_tick((unsigned long)xpd);
+ }
+}
+
+void xpp_tick(unsigned long param)
+{
+ xbus_t *xbus;
+ xpd_t *the_xpd = (xpd_t *)param;
+ int i;
+ int j;
+
+ if(!the_xpd) { /* Called from timer */
+#if 0
+ static int rate_limit = 0;
+ if(rate_limit++ % 1000 == 0)
+ DBG("FROM_TIMER\n");
+#endif
+ mod_timer(&xpp_timer, jiffies + 1); /* Must be 1KHz rate */
+ }
+ else if(the_xpd != sync_master)
+ return;
+ /* Statistics */
+ if((xpp_timer_count % SAMPLE_TICKS) == 0) {
+ xpp_last_jiffies = jiffies;
+ }
+ xpp_timer_count++;
+
+ for(i = 0; i < MAX_BUSES; i++) {
+ xbus = xbus_of(i);
+ if(!xbus)
+ continue;
+ if (!xbus->hardware_exists)
+ continue;
+#if 0
+ if(xbus->open_counter == 0)
+ continue; // optimize, but zttool loopback won't function
+#endif
+ for(j = 0; j < MAX_XPDS; j++) {
+ xpd_t *xpd = xbus->xpds[j];
+
+ if(!xpd)
+ continue;
+ if(!atomic_read(&xpd->card_present))
+ continue;
+ xpd->timer_count++;
+ CALL_XMETHOD(card_tick, xbus, xpd);
+ if(!SPAN_REGISTERED(xpd))
+ continue;
+ xpd_blink_leds(xpd);
+ if(xpd->direction == TO_PSTN)
+ xpp_ring_generate(xpd);
+ xpp_transmitprep(xpd);
+ xpp_receiveprep(xpd);
+ }
+ }
+}
+
+#if HZ != 1000
+#warning This module will not be usable since the kernel HZ setting is not 1000 ticks per second.
+#endif
+
+static void xpd_cleanup(xpd_t *xpd)
+{
+ xbus_t *xbus = NULL;
+
+ if(!xpd)
+ return;
+ xbus = xpd->xbus;
+ xpd_card_disable(xpd);
+ DBG("%s/%s\n", xbus->busname, xpd->xpdname);
+#ifdef CONFIG_PROC_FS
+ if(xpd->proc_xpd_dir) {
+ if(xpd->proc_xpd_summary) {
+ DBG("Removing proc '%s' for %s/%s\n", PROC_XPD_SUMMARY, xbus->busname, xpd->xpdname);
+ remove_proc_entry(PROC_XPD_SUMMARY, xpd->proc_xpd_dir);
+ xpd->proc_xpd_summary = NULL;
+ }
+ if(xpd->proc_xpd_ztregister) {
+ DBG("Removing proc '%s' for %s/%s\n", PROC_XPD_ZTREGISTER, xbus->busname, xpd->xpdname);
+ remove_proc_entry(PROC_XPD_ZTREGISTER, xpd->proc_xpd_dir);
+ xpd->proc_xpd_ztregister = NULL;
+ }
+ DBG("Removing proc directory for %s/%s\n", xbus->busname, xpd->xpdname);
+ remove_proc_entry(xpd->xpdname, xbus->proc_xbus_dir);
+ xpd->proc_xpd_dir = NULL;
+ }
+#endif
+}
+
+void init_xbus_packet_queue(packet_queue_t *q, const char name[])
+{
+ INIT_LIST_HEAD(&q->head);
+ spin_lock_init(&q->lock);
+ q->count = 0;
+ q->worst_count = 0;
+ q->overflows = 0;
+ snprintf(q->qname, XPD_NAMELEN, "%s", name);
+}
+
+#if 0
+/*
+ * Assume the queue is locked
+ */
+void __dump_packet_queue(const char *msg, packet_queue_t *q)
+{
+ xpacket_t *tmp;
+
+ list_for_each_entry(tmp, &q->head, list) {
+ dump_packet(msg, tmp);
+ }
+}
+#endif
+
+void drain_xbus_packet_queue(xbus_t *xbus, packet_queue_t *q)
+{
+ unsigned long flags;
+ xpacket_t *pack;
+ xpacket_t *next;
+
+ spin_lock_irqsave(&q->lock, flags);
+ DBG("queue=%s count=%d\n", q->qname, q->count);
+ DBG(" total packets count=%d\n", atomic_read(&xpacket_count));
+ list_for_each_entry_safe(pack, next, &q->head, list) {
+ list_del(&pack->list);
+ q->count--;
+ xbus->ops->packet_free(xbus, pack);
+ }
+ if(q->count != 0)
+ ERR("drain_xbus_packet_queue: queue %s still has %d packets\n",
+ q->qname, q->count);
+ spin_unlock_irqrestore(&q->lock, flags);
+}
+
+void xbus_enqueue_packet(xbus_t *xbus, packet_queue_t *q, xpacket_t *pack)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&q->lock, flags);
+
+ if(q->count >= max_queue_len) {
+ static unsigned long last_notice = 0; // rate limit
+
+ if((jiffies - last_notice) < HZ) {
+ NOTICE("xbus_enqueue_packet: dropping packet (queue len = %d, max=%d)\n",
+ q->count, max_queue_len);
+ last_notice = jiffies;
+ }
+ q->overflows++;
+ xbus->ops->packet_free(xbus, pack);
+ goto out;
+ }
+ list_add_tail(&pack->list, &q->head);
+ q->count++;
+
+ if(q->count > q->worst_count)
+ q->worst_count = q->count;
+
+ if(q->count < max_queue_len/100 && q->worst_count > q->count) // Decay worst_count
+ q->worst_count--;
+
+ // dump_packet("ENQUEUED", pack, print_dbg);
+out:
+ spin_unlock_irqrestore(&q->lock, flags);
+}
+
+xpacket_t *xbus_dequeue_packet(packet_queue_t *q)
+{
+ unsigned long flags;
+ struct list_head *p;
+ xpacket_t *pack = NULL;
+
+ spin_lock_irqsave(&q->lock, flags);
+
+ if(list_empty(&q->head)) {
+ // DBG("LIST EMPTY (count=%d)\n", q->count);
+ goto out;
+ }
+ p = q->head.next;
+ list_del(p);
+ q->count--;
+ pack = list_entry(p, xpacket_t, list);
+ // dump_packet("DEQUEUED", pack, print_dbg);
+out:
+ spin_unlock_irqrestore(&q->lock, flags);
+ return pack;
+}
+
+
+/*------------------------- XPD Management -------------------------*/
+
+#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;
+#if SOFT_SIMULATOR
+ struct xpd_sim *sim;
+#endif
+ int channels;
+ int i;
+
+ if(!xpd)
+ goto out;
+
+ xbus = xpd->xbus;
+#if SOFT_SIMULATOR
+ sim = &xbus->sim[xpd->id];
+#endif
+ channels = xpd->channels;
+ len += sprintf(page + len, "%s (%s ,card %s, span_registered=%s)%s\n"
+ "timer_count: %d span->mainttimer=%d\n"
+ ,
+ xpd->xpdname, xproto_name(xpd->type),
+ (atomic_read(&xpd->card_present))?"present":"missing",
+ (SPAN_REGISTERED(xpd))?"yes":"no",
+ (xpd == sync_master) ? " SYNCER" : "",
+ xpd->timer_count, xpd->span.mainttimer
+ );
+ len += sprintf(page + len, "STATES:");
+ len += sprintf(page + len, "\n\t%-17s: ", "enabled");
+ for(i = 0; i < channels; i++) {
+ len += sprintf(page + len, "%d ", IS_SET(xpd->enabled_chans, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "output_relays");
+ for(i = 0; i < channels; i++) {
+ len += sprintf(page + len, "%d ", IS_SET(xpd->digital_outputs, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "input_relays");
+ for(i = 0; i < channels; i++) {
+ len += sprintf(page + len, "%d ", IS_SET(xpd->digital_inputs, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "hookstate");
+ for(i = 0; i < channels; i++) {
+ len += sprintf(page + len, "%d ", IS_SET(xpd->hookstate, i));
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "ring-state");
+ for(i = 0; i < channels; i++) {
+ len += sprintf(page + len, "%d ", xpd->lasttxhook[i]);
+ }
+ len += sprintf(page + len, "\n\t%-17s: ", "ringing");
+ for(i = 0; i < channels; i++) {
+ len += sprintf(page + len, "%d ", xpd->ringing[i]);
+ }
+#if 1
+ if(SPAN_REGISTERED(xpd)) {
+ len += sprintf(page + len, "\nreadchunk: ");
+ for(i = 0; i < channels; i++) {
+ struct zt_chan *chans = xpd->span.chans;
+ byte chunk[ZT_CHUNKSIZE];
+ int j;
+
+ memcpy(chunk, chans[i].readchunk, ZT_CHUNKSIZE);
+ len += sprintf(page + len, "\n\tport %2d> ", i);
+ for(j = 0; j < ZT_CHUNKSIZE; j++) {
+ len += sprintf(page + len, "%02X ", chunk[j]);
+ }
+ }
+ }
+#endif
+#if SOFT_SIMULATOR
+ if(sim->simulated) {
+ len += sprintf(page + len, "\nSIMULATED (xpd_type=%d, loopto=%d):", sim->xpd_type, sim->loopto);
+ len += sprintf(page + len, "\n\t%-17s: ", "hookstate");
+ for(i = 0; i < channels; i++) {
+ len += sprintf(page + len, "%d ", IS_SET(sim->hookstate, i));
+ }
+ }
+#endif
+#if 0
+ if(SPAN_REGISTERED(xpd)) {
+ len += sprintf(page + len, "\nSignalling:\n");
+ for(i = 0; i < channels; i++) {
+ struct zt_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, xbus_t *xbus, int xpd_num, const xproto_table_t *proto_table, int channels, byte revision)
+{
+ xpd_t *xpd = NULL;
+ size_t alloc_size = sizeof(xpd_t) + privsize;
+
+ INFO("New XPD #%d (Revision %d.%d) detected on xbus %s\n",
+ xpd_num, revision / 10, revision % 10, xbus->busname);
+ if(!VALID_XPD_NUM(xpd_num)) {
+ ERR("%s: illegal xpd id = %d\n", __FUNCTION__, xpd_num);
+ goto err;
+ }
+ if(channels > CHANNELS_PERXPD) {
+ ERR("%s: too many channels %d for xpd #%d\n", __FUNCTION__, channels, xpd_num);
+ goto err;
+ }
+
+ if((xpd = kmalloc(alloc_size, GFP_KERNEL)) == NULL) {
+ ERR("%s: Unable to allocate memory for xpd #%d\n", __FUNCTION__, xpd_num);
+ goto err;
+ }
+ memset(xpd, 0, alloc_size);
+ xpd->priv = (byte *)xpd + sizeof(xpd_t);
+
+ spin_lock_init(&xpd->lock);
+ xpd->xbus = xbus;
+ xpd->id = xpd_num;
+ xpd->channels = channels;
+ xpd->chans = NULL;
+ atomic_set(&xpd->card_present, 0);
+ snprintf(xpd->xpdname, XPD_NAMELEN, "XPD-%d", xpd_num);
+ xpd->hookstate = 0x0; /* ONHOOK */
+ xpd->type = proto_table->type;
+ xpd->xops = &proto_table->xops;
+ xpd->enabled_chans = enabled_channels[xpd_num];
+ xpd->digital_outputs = 0;
+ xpd->digital_inputs = 0;
+ atomic_set(&xpd->open_counter, 0);
+
+ xpd->chans = kmalloc(sizeof(struct zt_chan)*xpd->channels, GFP_KERNEL);
+ if (xpd->chans == NULL) {
+ ERR("%s: Unable to allocate channels\n", __FUNCTION__);
+ goto err;
+ }
+ /* 8 channels, double buffer, Read/Write */
+ int need = ZT_MAX_CHUNKSIZE * CHANNELS_PERXPD * 2 * 2;
+ if((xpd->writechunk = kmalloc(need, GFP_KERNEL)) == NULL) {
+ ERR("%s: Unable to allocate memory for writechunks\n", __FUNCTION__);
+ goto err;
+ }
+ /* Initialize Write/Buffers to all blank data */
+ memset((void *)xpd->writechunk, 0x00, need);
+ xpd->readchunk = xpd->writechunk + ZT_CHUNKSIZE * CHANNELS_PERXPD * 2;
+
+ return xpd;
+err:
+ if(xpd->chans)
+ kfree((void *)xpd->chans);
+ if(xpd->writechunk)
+ kfree((void *)xpd->writechunk);
+ if(xpd)
+ kfree(xpd);
+ return NULL;
+}
+
+static void xpd_card_disable(xpd_t *xpd)
+{
+ BUG_ON(!xpd);
+ atomic_set(&xpd->card_present, 0);
+ if(SPAN_REGISTERED(xpd))
+ update_xpd_status(xpd, ZT_ALARM_NOTOPEN);
+}
+
+void xpd_remove(xpd_t *xpd)
+{
+ xbus_t *xbus;
+
+ BUG_ON(!xpd);
+ xbus = xpd->xbus;
+ INFO("Remove XPD #%d from xbus=%s\n", xpd->id, xbus->busname);
+#if 0
+ // TODO: elect a new sync master
+ if(sync_master == xpd)
+ set_sync_master(NULL);
+#endif
+ xpd_zaptel_unregister(xpd);
+ xbus->xpds[xpd->id] = NULL;
+ list_del(&xpd->xpd_list);
+ xbus->num_xpds--;
+ CALL_XMETHOD(card_remove, xbus, xpd);
+ xpd_cleanup(xpd);
+ kfree((void *)xpd->writechunk);
+ kfree(xpd);
+}
+
+static void update_xpd_status(xpd_t *xpd, int alarm_flag)
+{
+ struct zt_span *span = &xpd->span;
+
+ if(!SPAN_REGISTERED(xpd)) {
+ NOTICE("%s: %s is not registered. Skipping.\n", __FUNCTION__, xpd->xpdname);
+ return;
+ }
+ switch (alarm_flag) {
+ case ZT_ALARM_NONE:
+ xpd->last_response = jiffies;
+ break;
+ default:
+ // Nothing
+ break;
+ }
+ if(span->alarms == alarm_flag)
+ return;
+ span->alarms = alarm_flag;
+ zt_alarm_notify(span);
+ DBG("Update XPD alarms: %s -> %02X\n", xpd->span.name, alarm_flag);
+}
+
+void phone_hook(xpd_t *xpd, int channo, bool offhook)
+{
+ struct zt_chan *chan = &xpd->span.chans[channo];
+
+ if(offhook && !IS_SET(xpd->hookstate, channo)) { // OFFHOOK
+ DBG("OFFHOOK: channo=%d\n", chan->channo);
+ xpd->ringing[channo] = 0;
+ BIT_SET(xpd->hookstate, channo);
+ zt_hooksig(chan, ZT_RXSIG_OFFHOOK);
+ if(!IS_SET(xpd->digital_outputs, channo) && !IS_SET(xpd->digital_inputs, channo)) {
+ CALL_XMETHOD(CHAN_POWER, xpd->xbus, xpd, BIT(channo), 0); // Power down (prevent overheating!!!)
+ CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(channo), LED_GREEN, 1);
+ }
+ } else if(!offhook && IS_SET(xpd->hookstate, channo)) { // ONHOOK
+ DBG("ONHOOK channo=%d\n", chan->channo);
+ xpd->ringing[channo] = 0;
+ BIT_CLR(xpd->hookstate, channo);
+ zt_hooksig(chan, ZT_RXSIG_ONHOOK);
+ if(!IS_SET(xpd->digital_outputs, channo) && !IS_SET(xpd->digital_inputs, channo)) {
+ CALL_XMETHOD(CHAN_POWER, xpd->xbus, xpd, BIT(channo), 0); // Power down (prevent overheating!!!)
+ CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(channo), LED_GREEN, 0);
+ }
+ }
+}
+
+void xpp_check_hookstate(xpd_t *xpd, xpp_line_t fxs_off_hook)
+{
+ int i;
+ unsigned long flags;
+
+ spin_lock_irqsave(&xpd->lock, flags);
+ if(xpd->direction != TO_PHONE) {
+ ERR("%s: %s: Only PHONE can report hookstate changes\n", __FUNCTION__, xpd->xpdname);
+ goto out;
+ }
+ if(!SPAN_REGISTERED(xpd)) {
+ NOTICE("%s: %s is not registered. Skipping.\n", __FUNCTION__, xpd->xpdname);
+ goto out;
+ }
+ DBG("%s: hookstate=0x%04X fxs_off_hook=0x%04X\n", xpd->xpdname, xpd->hookstate, fxs_off_hook);
+ for(i = 0; i < xpd->channels; i++) {
+ phone_hook(xpd, i, IS_SET(fxs_off_hook, i));
+ }
+out:
+ spin_unlock_irqrestore(&xpd->lock, flags);
+}
+
+static void xpd_blink_leds(xpd_t *xpd)
+{
+ int i;
+ unsigned long flags;
+
+ BUG_ON(!xpd);
+
+ spin_lock_irqsave(&xpd->lock, flags);
+ for(i = 0; i < xpd->channels; i++) {
+ if(IS_SET(xpd->digital_outputs, i) || IS_SET(xpd->digital_inputs, i))
+ continue;
+ if(xpd->ringing[i]) {
+ // led state is toggled
+ if((xpd->timer_count % LED_BLINK_PERIOD) == 0) {
+ DBG("%s pos=%d ringing=%d led_on=%d\n", xpd->xpdname, i, xpd->ringing[i], xpd->led_on[i]);
+ if(xpd->ringing[i] && xpd->led_on[i]) {
+ CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 1);
+ } else {
+ CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 0);
+ }
+ xpd->led_on[i] = !xpd->led_on[i];
+ }
+ }
+ }
+ spin_unlock_irqrestore(&xpd->lock, flags);
+}
+
+static void xpp_ring_generate(xpd_t *xpd)
+{
+ int i;
+ static int bug_counter = 0;
+ unsigned long flags;
+
+ BUG_ON(!xpd);
+
+ spin_lock_irqsave(&xpd->lock, flags);
+ if(xpd->direction != TO_PSTN && ((bug_counter++ % 1000) == 0)) {
+ ERR("%s: %s: Only FXO can report ring changes\n", __FUNCTION__, xpd->xpdname);
+ goto out;
+ }
+ if(!SPAN_REGISTERED(xpd)) {
+ NOTICE("%s: %s is not registered. Skipping.\n", __FUNCTION__, xpd->xpdname);
+ goto out;
+ }
+ /*
+ * Ring detect logic:
+ * fxo_power is toggled
+ */
+ for(i = 0; i < xpd->channels; i++) {
+ if(xpd->ringing[i] || xpd->ringer_on[i]) {
+ // ring state is only changed once per second:
+ if((xpd->timer_count % 1000) == 0) {
+ DBG("pos=%d ringing=%d ringer_on=%d\n", i, xpd->ringing[i], xpd->ringer_on[i]);
+ if(xpd->ringer_on[i]) {
+ zt_hooksig(&xpd->chans[i], ZT_RXSIG_OFFHOOK);
+ } else {
+ zt_hooksig(&xpd->chans[i], ZT_RXSIG_RING);
+ }
+ xpd->ringing[i]--;
+ xpd->ringer_on[i] = !xpd->ringer_on[i];
+ if (xpd->ringing[i] < 0)
+ xpd->ringing[i]=0;
+ }
+ }
+ }
+out:
+ spin_unlock_irqrestore(&xpd->lock, flags);
+}
+
+/*------------------------- Bus Management -------------------------*/
+
+xbus_t *xbus_of(int xbus_num)
+{
+ if(xbus_num < 0 || xbus_num >= MAX_BUSES)
+ return NULL;
+ return xbuses_array[xbus_num];
+}
+
+xpd_t *xpd_of(xbus_t *xbus, int xpd_num)
+{
+ if(!VALID_XPD_NUM(xpd_num))
+ return NULL;
+ return xbus->xpds[xpd_num];
+}
+
+void xbus_reset_counters(xbus_t *xbus)
+{
+ int i;
+
+ DBG("Reseting counters of %s\n", xbus->busname);
+ for(i = 0; i < XBUS_COUNTER_MAX; i++) {
+ xbus->counters[i] = 0;
+ }
+// xbus->xmit_queue.worst_count = 0;
+// xbus->xmit_queue.overflows = 0;
+}
+
+#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 xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long flags;
+ xbus_t *xbus = data;
+ int i;
+
+ if(!xbus)
+ goto out;
+ spin_lock_irqsave(&xbus->lock, flags);
+
+ len += sprintf(page + len, "%s: CONNECTOR=%s STATUS=%s bus_type=%d\n",
+ xbus->busname,
+ xbus->busdesc,
+ (xbus->hardware_exists) ? "connected" : "missing",
+ xbus->bus_type
+ );
+ len += sprintf(page + len, "open_counter=%d packet_count=%d\n",
+ xbus->open_counter,
+ atomic_read(&xbus->packet_counter)
+ );
+#if SOFT_SIMULATOR
+ len += sprintf(page + len, "XPDS SIM\n");
+ for(i = 0; i < MAX_XPDS; i++) {
+ struct xpd_sim *sim = &xbus->sim[i];
+ xpd_t *xpd = xpd_of(xbus, i);
+ len += sprintf(page + len, "\t%d> ignored=%d simulated=%d softloop_xpd=%d loopto=%d instanciated=%s\n",
+ i, IS_SET(ignore_xpds, i), sim->simulated, sim->softloop_xpd, sim->loopto, (xpd) ? "yes" : "no");
+ }
+#endif
+ len += sprintf(page + len, "COUNTERS:\n");
+ for(i = 0; i < XBUS_COUNTER_MAX; i++) {
+ len += sprintf(page + len, "\t%-15s = %d\n",
+ xbus_counters[i].name, xbus->counters[i]);
+ }
+ len += sprintf(page + len, "<-- len=%d\n", len);
+ spin_unlock_irqrestore(&xbus->lock, flags);
+out:
+ if (len <= off+count)
+ *eof = 1;
+ *start = page + off;
+ len -= off;
+ if (len > count)
+ len = count;
+ if (len < 0)
+ len = 0;
+ return len;
+
+}
+
+int proc_sync_read(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+
+ len += sprintf(page + len, "# To modify sync source write into this file:\n");
+ len += sprintf(page + len, "# HOST - For host based sync\n");
+ len += sprintf(page + len, "# 0 0 - XBUS-0/XPD-0 provide sync\n");
+ len += sprintf(page + len, "# m n - XBUS-m/XPD-n provide sync\n");
+ if(!sync_master)
+ len += sprintf(page + len, "HOST\n");
+ else
+ len += sprintf(page + len, "%s/%s\n", sync_master->xbus->busname, sync_master->xpdname);
+ len += sprintf(page + len, "tick: #%d\n", xpp_timer_count);
+ unsigned int xpp_timer_rate = 0;
+ unsigned int now = jiffies;
+ if(now - xpp_last_jiffies > 0) {
+ xpp_timer_rate = ((xpp_timer_count % SAMPLE_TICKS) * 1000) / (now - xpp_last_jiffies);
+ len += sprintf(page + len, "tick rate: %4d/second (average over %d seconds)\n", xpp_timer_rate, SAMPLE_TICKS/HZ);
+ }
+ 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_sync_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
+{
+ DBG("%s: count=%ld\n", __FUNCTION__, count);
+ const int NUM_SIZE = 100;
+ char buf[NUM_SIZE];
+ int xbus_num;
+ int xpd_num;
+ xbus_t *xbus;
+ xpd_t *xpd;
+ int ret;
+
+ if(count >= NUM_SIZE)
+ return -EINVAL;
+ if(copy_from_user(buf, buffer, count))
+ return -EFAULT;
+ buf[count] = '\0';
+ if(strncmp("HOST", buf, 4) == 0) {
+ set_sync_master(NULL);
+ goto out;
+ }
+ ret = sscanf(buf, "%d %d", &xbus_num, &xpd_num);
+ if(ret != 2)
+ return -EINVAL;
+ DBG("%s: %d/%d\n", __FUNCTION__, xbus_num, xpd_num);
+ if(xbus_num >= MAX_BUSES)
+ return -EINVAL;
+ xbus = xbus_of(xbus_num);
+ if(!xbus)
+ return -EINVAL;
+ xpd = xpd_of(xbus, xpd_num);
+ if(!xpd) {
+ ERR("%s: XPD number %d does not exist\n", __FUNCTION__, xpd_num);
+ return -ENXIO;
+ }
+ set_sync_master(xpd);
+out:
+ return count;
+}
+
+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));
+ 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;
+ const int NUM_SIZE = 100;
+ char buf[NUM_SIZE];
+ bool zt_reg;
+ int ret;
+
+ BUG_ON(!xpd);
+ if(count >= NUM_SIZE)
+ return -EINVAL;
+ if(copy_from_user(buf, buffer, count))
+ return -EFAULT;
+ buf[count] = '\0';
+ ret = sscanf(buf, "%d", &zt_reg);
+ if(ret != 1)
+ return -EINVAL;
+ DBG("%s: %s/%s %s\n", __FUNCTION__,
+ xpd->xbus->busname, xpd->xpdname, (zt_reg) ? "register" : "unregister");
+ if(zt_reg)
+ ret = xpd_zaptel_register(xpd);
+ else
+ ret = xpd_zaptel_unregister(xpd);
+ return (ret < 0) ? ret : count;
+}
+
+#endif
+
+/**
+ *
+ * Packet is freed:
+ * - In case of error, by this function.
+ * - Otherwise, by the underlying sending mechanism
+ */
+int packet_send(xbus_t *xbus, xpacket_t *pack_tx)
+{
+ int ret = -ENODEV;
+ int toxpd;
+
+ if(!pack_tx) {
+ DBG("null pack\n");
+ return -EINVAL;
+ }
+ toxpd = XPD_NUM(pack_tx->content.addr);
+ if(!xbus) {
+ DBG("null xbus\n");
+ ret = -EINVAL;
+ goto error;
+ }
+ if (!xbus->hardware_exists) {
+ DBG("xbus %s Dropped a packet -- NO HARDWARE.", xbus->busname);
+ ret = -ENODEV;
+ goto error;
+ }
+ if(!VALID_XPD_NUM(toxpd)) {
+ ERR("%s: toxpd=%d > MAX_XPDS\n", __FUNCTION__, toxpd);
+ ret = -EINVAL;
+ goto error;
+ }
+#if 0
+ // DEBUG: For Dima
+ if(pack_tx->content.opcode == XPP_PCM_WRITE) {
+ static int rate_limit;
+ static int count;
+
+ if(sync_master == NULL)
+ count = 0;
+ if(count++ > 5) {
+ ret = 0;
+ goto error;
+ }
+ if(rate_limit++ % 1000 == 0)
+ INFO("DEBUG: TRANSMIT (PCM_WRITE)\n");
+ }
+#endif
+ if(down_read_trylock(&xbus->in_use)) {
+ ret = xbus->ops->packet_send(xbus, pack_tx);
+ XBUS_COUNTER(xbus, TX_BYTES) += pack_tx->datalen;
+ up_read(&xbus->in_use);
+ } else {
+ DBG("Dropped packet. %s is in_use\n", xbus->busname);
+ }
+ return ret;
+
+error:
+ xbus->ops->packet_free(xbus, pack_tx);
+ return ret;
+}
+
+static void xbus_poll(xbus_t *xbus, int probe_all)
+{
+ int id;
+ int ret;
+ xpd_t **xpds;
+ xpd_t *xpd;
+
+ DBG("%s (probe_all=%d)\n", xbus->busname, probe_all);
+ xpds = xbus->xpds;
+ for(id = 0; id < MAX_XPDS; id++) {
+ if(!xbus->hardware_exists)
+ break;
+ xpd = xpd_of(xbus, id);
+ if(!probe_all) {
+ if(!xpd) {
+ DBG(" Skipping XPD #%d is MISSING\n", id);
+ continue;
+ }
+ if(!atomic_read(&xpd->card_present)) {
+ DBG(" Skipping XPD #%d not present\n", id);
+ continue;
+ }
+ if(time_after(xpd->last_response+20, jiffies)) {
+ DBG(" SKIP DESC_REQ\n");
+ continue;
+ }
+ }
+ if(IS_SET(ignore_xpds, id)) { /* skip xpds */
+ DBG(" Ignoring XPD #%d\n", id);
+ continue;
+ }
+ DBG(" Polling slot %d %s\n", id, xbus->busname);
+ ret = CALL_PROTO(GLOBAL, DESC_REQ, xbus, NULL, id);
+ if(ret < 0) {
+ NOTICE("xpp: %s: Failed sending DESC_REQ to XPD #%d\n", __FUNCTION__, id);
+ }
+ }
+}
+
+void process_xbus_poll(void *data) {
+ xbus_t *xbus = (xbus_t*) data;
+
+ ERR("%s: issuing xbus_poll\n", __FUNCTION__);
+ xbus_poll(xbus, 1);
+}
+
+
+#if SOFT_SIMULATOR
+/*
+ * Assume xbus->lock is held
+ */
+static void simulator_setup(xbus_t *xbus, ulong sim_xpds)
+{
+ int i;
+ int last_fxo = 0;
+ bool first = 1;
+
+ DBG("%s: sim_xpds=0x%lX\n", xbus->busname, sim_xpds);
+ for(i = 0; i < MAX_XPDS; i++) {
+ if (!IS_SET(sim_xpds, i) || xbus->sim[i].simulated)
+ continue;
+ if (first) {
+ last_fxo=i;
+ } else {
+ // setting simulated twice here: in case of odd number of simulated XPDs
+ xbus->sim[i].xpd_type = XPD_TYPE_FXO;
+ xbus->sim[i].loopto = last_fxo;
+ xbus->sim[i].simulated = 1;
+ xbus->sim[last_fxo].xpd_type = XPD_TYPE_FXS;
+ xbus->sim[last_fxo].loopto = i;
+ xbus->sim[last_fxo].simulated = 1;
+
+ }
+ if(IS_SET(softloop_xpds, i))
+ xbus->sim[i].softloop_xpd = 1;
+ xbus->sim[i].hookstate = 0;
+
+ first = !first;
+ }
+}
+#endif
+
+void xbus_activate(xbus_t *xbus)
+{
+ xbus_ops_t *ops;
+
+ BUG_ON(!xbus);
+ ops = xbus->ops;
+ BUG_ON(!ops);
+ BUG_ON(!xbus->priv);
+ /* Sanity checks */
+ if(!ops->packet_send) {
+ ERR("%s: missing mandatory handler: packet_send=\n", __FUNCTION__);
+ return;
+ }
+ if(!ops->packet_new || !ops->packet_free) {
+ ops->packet_new = softloop_packet_new;
+ ops->packet_free = softloop_packet_free;
+ }
+
+ xbus->hardware_exists = 1;
+ DBG("Activating: %s\n", xbus->busname);
+ /* Poll it */
+ xbus_poll(xbus, 1);
+}
+
+void xbus_deactivate(xbus_t *xbus)
+{
+ int i;
+
+ BUG_ON(!xbus);
+ DBG("%s\n", xbus->busname);
+ xbus->hardware_exists = 0;
+ for(i = 0; i < MAX_XPDS; i++) {
+ xpd_t *xpd = xpd_of(xbus, i);
+ if(!xpd)
+ continue;
+ if(xpd->id != i) {
+ ERR("%s: BUG: xpd->id=%d != i=%d\n", __FUNCTION__, xpd->id, i);
+ continue;
+ }
+ xpd_card_disable(xpd);
+ }
+ down_write(&xbus->in_use);
+ DBG("%s (deactivated)\n", xbus->busname);
+ if(xbus->open_counter == 0) {
+ xbus_remove(xbus);
+ }
+}
+
+
+static void xbus_cleanup(xbus_t *xbus)
+{
+ BUG_ON(!xbus);
+#ifdef CONFIG_PROC_FS
+ if(xbus->proc_xbus_dir) {
+ if(xbus->proc_xbus_summary) {
+ DBG("Removing proc '%s' for %s\n", PROC_XBUS_SUMMARY, xbus->busname);
+ remove_proc_entry(PROC_XBUS_SUMMARY, xbus->proc_xbus_dir);
+ xbus->proc_xbus_summary = NULL;
+ }
+ DBG("Removing proc directory %s\n", xbus->busname);
+ remove_proc_entry(xbus->busname, xpp_procdir);
+ xbus->proc_xbus_dir = NULL;
+ }
+#endif
+ kfree(xbus);
+}
+
+xbus_t *xbus_new(ulong loopback_xpds)
+{
+ unsigned long flags;
+ int xbus_num;
+ int err;
+ xbus_t *xbus;
+
+ xbus = kmalloc(sizeof(xbus_t), GFP_KERNEL);
+ if(!xbus)
+ return NULL;
+ memset(xbus, 0, sizeof(xbus_t));
+
+ spin_lock_irqsave(&xbuses_lock, flags);
+ for(xbus_num = 0; xbus_num < MAX_BUSES; xbus_num++)
+ if(xbuses_array[xbus_num] == NULL)
+ break;
+ if(xbus_num >= MAX_BUSES) {
+ spin_unlock_irqrestore(&xbuses_lock, flags);
+ err = -ENOMEM;
+ goto nobus;
+ }
+ /* Found empty slot */
+ xbuses_array[xbus_num] = xbus;
+ bus_count++;
+ spin_unlock_irqrestore(&xbuses_lock, flags);
+
+ /* Init data structures */
+ spin_lock_init(&xbus->lock);
+ snprintf(xbus->busname, XBUS_NAMELEN, "XBUS-%d", xbus_num);
+ INFO("New xbus: %s\n", xbus->busname);
+ init_waitqueue_head(&xbus->packet_cache_empty);
+ atomic_set(&xbus->packet_counter, 0);
+ init_rwsem(&xbus->in_use);
+ xbus->num = xbus_num;
+ xbus->num_xpds = 0;
+#if SOFT_SIMULATOR
+ xbus->sim_workqueue = create_singlethread_workqueue(xbus->busname);
+ if(!xbus->sim_workqueue) {
+ ERR("Failed to create workqueue for xbus %s\n", xbus->busname);
+ err = -ENOMEM;
+ goto nobus;
+ }
+ init_xbus_packet_queue(&xbus->sim_packet_queue, "SIM_PACKET_QUEUE");
+ INIT_WORK(&xbus->sim_work, process_sim_queue, xbus);
+ simulator_setup(xbus, loopback_xpds); // Hardware loopback must use simulator
+ simulator_setup(xbus, softloop_xpds); // Add the soft loopback to the simulated set
+#endif
+ xbus_reset_counters(xbus);
+#ifdef CONFIG_PROC_FS
+ DBG("Creating xbus proc directory %s.\n",xbus->busname);
+ xbus->proc_xbus_dir = proc_mkdir(xbus->busname, xpp_procdir);
+ if(!xbus->proc_xbus_dir) {
+ ERR("Failed to create proc directory for xbus %s\n", xbus->busname);
+ err = -EIO;
+ goto nobus;
+ }
+ xbus->proc_xbus_summary = create_proc_read_entry(PROC_XBUS_SUMMARY, 0444, xbus->proc_xbus_dir,
+ xbus_read_proc, xbus);
+ if (!xbus->proc_xbus_summary) {
+ ERR("Failed to create '%s' proc file for xbus %s\n", PROC_XBUS_SUMMARY, xbus->busname);
+ err = -EIO;
+ goto nobus;
+ }
+#endif
+ return xbus;
+nobus:
+ spin_lock_irqsave(&xbuses_lock, flags);
+ xbuses_array[xbus_num] = NULL;
+ bus_count--;
+ spin_unlock_irqrestore(&xbuses_lock, flags);
+ xbus_cleanup(xbus);
+ return NULL;
+}
+
+static void xbus_remove(xbus_t *xbus)
+{
+ int i;
+ unsigned long flags;
+
+ BUG_ON(!xbus);
+ DBG("%s\n", xbus->busname);
+ spin_lock_irqsave(&xbuses_lock, flags);
+ BUG_ON(xbus != xbus_of(xbus->num));
+ xbuses_array[xbus->num] = NULL;
+ bus_count--;
+ spin_unlock_irqrestore(&xbuses_lock, flags);
+#if SOFT_SIMULATOR
+ if(xbus->sim_workqueue) {
+ cancel_delayed_work(&xbus->sim_work);
+ }
+#endif
+ INFO("Removing xbus(%d) %s\n", xbus->num, xbus->busname);
+ for(i = 0; i < MAX_XPDS; i++) {
+ xpd_t *xpd = xpd_of(xbus, i);
+
+ if(xpd) {
+ const xops_t *xops = xpd->xops;
+
+ if(xpd->id != i) {
+ ERR("%s: BUG: xpd->id=%d != i=%d\n", __FUNCTION__, xpd->id, i);
+ continue;
+ }
+ BUG_ON(!xops);
+ DBG(" Removing xpd id=%d\n", xpd->id);
+ xpd_remove(xpd);
+ }
+ xbus->xpds[i] = NULL;
+ }
+#if SOFT_SIMULATOR
+ if(xbus->sim_workqueue) {
+ flush_workqueue(xbus->sim_workqueue);
+ destroy_workqueue(xbus->sim_workqueue);
+ xbus->sim_workqueue = NULL;
+ }
+ drain_xbus_packet_queue(xbus, &xbus->sim_packet_queue);
+#endif
+ int ret = wait_event_interruptible(xbus->packet_cache_empty,
+ atomic_read(&xbus->packet_counter) == 0);
+ if(ret) {
+ ERR("waiting for packet_cache_empty interrupted!!!\n");
+ }
+ xbus_cleanup(xbus);
+}
+
+
+#define XPP_MAX_LEN 512
+
+/*------------------------- Zaptel Interfaces ----------------------*/
+
+#define PREP_REPORT_RATE 1000
+
+static void xpp_transmitprep(xpd_t *xpd)
+{
+ volatile u_char *writechunk;
+ volatile u_char *w;
+ int ret;
+ int i;
+ int channels = xpd->channels;
+ struct zt_chan *chans = xpd->span.chans;
+
+// if((xpd->timer_count % PREP_REPORT_RATE) < 10)
+// DBG("%d\n", xpd->timer_count);
+
+// if(xpd->hookstate == 0)
+// return;
+ if (xpd->timer_count & 1) {
+ /* First part */
+ w = writechunk = xpd->writechunk /* + 1 */;
+ } else {
+ w = writechunk = xpd->writechunk + ZT_CHUNKSIZE * CHANNELS_PERXPD /* + 1 */;
+ }
+ zt_transmit(&xpd->span);
+
+ for (i = 0; i < channels; i++) {
+ if(IS_SET(xpd->hookstate, i)) {
+ memcpy((u_char *)w, chans[i].writechunk, ZT_CHUNKSIZE);
+ // fill_beep((u_char *)w, 5);
+ }
+ w += ZT_CHUNKSIZE;
+ }
+ if(xpd->hookstate != 0 || sync_master == xpd || !sync_master) {
+ ret = CALL_XMETHOD(PCM_WRITE, xpd->xbus, xpd, xpd->hookstate, writechunk);
+ if(ret < 0) {
+ DBG("failed to write PCM %d\n", ret);
+ }
+ }
+}
+
+void fill_beep(u_char *buf, int duration)
+{
+ int which = (jiffies/(duration*HZ)) & 0x3;
+
+ /*
+ * debug tones
+ */
+ static u_char beep[] = {
+// 0x7F, 0xBE, 0xD8, 0xBE, 0x80, 0x41, 0x24, 0x41, /* Dima */
+// 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, 0x67, /* silence */
+ 0x67, 0x90, 0x89, 0x90, 0xFF, 0x10, 0x09, 0x10, /* Izzy */
+// 0x67, 0xCD, 0xC5, 0xCD, 0xFF, 0x49, 0x41, 0x49, /* Dima 2 */
+// 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, /* silence */
+ };
+ memcpy(buf, &beep[(which*8) % ARRAY_SIZE(beep)], ZT_CHUNKSIZE);
+}
+
+static void xpp_receiveprep(xpd_t *xpd)
+{
+ volatile u_char *readchunk;
+ int i;
+ int channels = xpd->channels;
+ struct zt_chan *chans = xpd->span.chans;
+
+// if((xpd->timer_count % PREP_REPORT_RATE) == 0)
+// DBG("%d\n", xpd->timer_count);
+
+ if (xpd->timer_count & 1) {
+ /* First part */
+ readchunk = xpd->readchunk;
+ } else {
+ readchunk = xpd->readchunk + ZT_CHUNKSIZE * CHANNELS_PERXPD;
+ }
+
+ for (i = 0; i < channels; i++) {
+ if(IS_SET(xpd->hookstate, i)) {
+ memcpy(chans[i].readchunk, (u_char *)readchunk, ZT_CHUNKSIZE);
+ }
+ readchunk += ZT_CHUNKSIZE;
+ }
+
+#if 0
+ /* FIXME: need to Echo cancel double buffered data */
+ for (i = 0;i < xpd->span.channels; i++) {
+ zt_ec_chunk(&chans[i], chans[i].readchunk, xpd->ec_chunk2[i]);
+ memcpy(xpd->ec_chunk2[i], xpd->ec_chunk1[i], ZT_CHUNKSIZE);
+ memcpy(xpd->ec_chunk1[i], chans[i].writechunk, ZT_CHUNKSIZE);
+ }
+#endif
+ zt_receive(&xpd->span);
+}
+
+static int xpp_startup(struct zt_span *span)
+{
+ DBG("\n");
+ return 0;
+}
+
+/*
+ * Called only for 'span' keyword in /etc/zaptel.conf
+ */
+static int xpp_spanconfig(struct zt_span *span, struct zt_lineconfig *lc)
+{
+ xpd_t *xpd = span->pvt;
+
+ DBG("%s\n", xpd->xpdname);
+ return 0;
+}
+
+/*
+ * Called only for 'span' keyword in /etc/zaptel.conf
+ */
+static int xpp_shutdown(struct zt_span *span)
+{
+ xpd_t *xpd = span->pvt;
+
+ DBG("%s\n", xpd->xpdname);
+ return 0;
+}
+
+int xpp_open(struct zt_chan *chan)
+{
+ xpd_t *xpd = chan->pvt;
+ xbus_t *xbus = xpd->xbus;
+ unsigned long flags;
+
+ spin_lock_irqsave(&xbus->lock, flags);
+ xbus->open_counter++;
+ atomic_inc(&xpd->open_counter);
+ DBG("chan=%d (open_counter=%d)\n", chan->chanpos, xbus->open_counter);
+ spin_unlock_irqrestore(&xbus->lock, flags);
+ return 0;
+}
+
+int xpp_close(struct zt_chan *chan)
+{
+ xpd_t *xpd = chan->pvt;
+ xbus_t *xbus = xpd->xbus;
+ unsigned long flags;
+ bool should_remove = 0;
+
+ spin_lock_irqsave(&xbus->lock, flags);
+ xbus->open_counter--;
+ atomic_dec(&xpd->open_counter);
+ if (!xbus->hardware_exists && xbus->open_counter == 0)
+ should_remove = 1;
+ spin_unlock_irqrestore(&xbus->lock, flags);
+
+ DBG("chan=%d (open_counter=%d, should_remove=%d)\n", chan->chanpos, xbus->open_counter, should_remove);
+ if(should_remove)
+ xbus_remove(xbus);
+ return 0;
+}
+
+int xpp_ioctl(struct zt_chan *chan, unsigned int cmd, unsigned long arg)
+{
+ xpd_t *xpd = chan->pvt;
+ int pos = chan->chanpos - 1;
+ int x;
+
+ switch (cmd) {
+ case ZT_ONHOOKTRANSFER:
+ if (get_user(x, (int *)arg))
+ return -EFAULT;
+ if (xpd->lasttxhook[pos] == 0x1) {
+ /* Apply the change if appropriate */
+ xpd->lasttxhook[pos] = 0x2;
+ // CALL_XMETHOD(CHAN_CID, xpd->xbus, xpd, BIT(pos)); // CALLER ID
+ }
+ DBG("xpd=%d: ZT_ONHOOKTRANSFER (%d millis) chan=%d\n", xpd->id, x, pos);
+ return -ENOTTY;
+ case ZT_TONEDETECT:
+ if (get_user(x, (int *)arg))
+ return -EFAULT;
+ DBG("xpd=%d: ZT_TONEDETECT chan=%d: TONEDETECT_ON=%d TONEDETECT_MUTE=%d\n",
+ xpd->id, pos, (x & ZT_TONEDETECT_ON), (x & ZT_TONEDETECT_MUTE));
+ return -ENOTTY;
+ default:
+ DBG("ENOTTY: chan=%d cmd=0x%x\n", pos, cmd);
+ DBG(" IOC_TYPE=0x%02X\n", _IOC_TYPE(cmd));
+ DBG(" IOC_DIR=0x%02X\n", _IOC_DIR(cmd));
+ DBG(" IOC_NR=0x%02X\n", _IOC_NR(cmd));
+ DBG(" IOC_SIZE=0x%02X\n", _IOC_SIZE(cmd));
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+#ifdef WITH_RBS
+static int xpp_hooksig(struct zt_chan *chan, zt_txsig_t txsig)
+{
+ xpd_t *xpd = chan->pvt;
+ xbus_t *xbus;
+ int pos = chan->chanpos - 1;
+ int ret = 0;
+
+ if(!xpd) {
+ ERR("%s: channel=%d without an XPD!\n", __FUNCTION__, pos);
+ return -EINVAL;
+ }
+ xbus = xpd->xbus;
+
+ if (txsig == ZT_TXSIG_START) {
+ if(xpd->direction == TO_PHONE) {
+ // A PHONE line: ZT_START will be treated as ZT_RING
+ DBG("Got ZT_START for PHONE channel %d, treated as ZT_RING\n", pos);
+ //hookstate = ZT_TXSIG_RING;
+
+ if(IS_SET(xpd->digital_inputs, pos)) {
+ NOTICE("%s: Trying to RING a digital input channel %d. Ignoring\n", __FUNCTION__, pos);
+ return -EINVAL;
+ }
+ if(IS_SET(xpd->digital_outputs, pos)) {
+ DBG("ZT_RING %s digital output ON\n", chan->name);
+ ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 1);
+ return ret;
+ }
+ xpd->ringing[pos] = RINGS_NUM*2;
+ DBG("ZT_RING %s ringing=%d\n", chan->name, xpd->ringing[pos]);
+ ret = CALL_XMETHOD(RING, xbus, xpd, pos, 1); // RING on
+ if(ret) {
+ DBG("ZT_RING Failed: ret=0x%02X\n", ret);
+ return ret;
+ }
+ return ret;
+ } else { /* TO_PSTN */
+ // An FXO line: ZT_START will be treated as ZT_OFFHOOK
+ DBG("Got ZT_START for FXO channel %d, treated as ZT_OFFHOOK\n", pos);
+ txsig = ZT_TXSIG_OFFHOOK;
+ }
+ }
+ switch(txsig) {
+ case ZT_TXSIG_START:
+ DBG("ZT_TXSIG_START: (doing OFFHOOK) %s (chan->dialing=%d)\n", chan->name, chan->dialing);
+ break;
+ /* Fall through */
+ case ZT_TXSIG_OFFHOOK:
+ DBG("ZT_TXSIG_OFFHOOK: %s hookstate=0x%04X\n", chan->name, xpd->hookstate);
+ BIT_SET(xpd->hookstate, pos);
+ xpd->ringing[pos] = 0;
+ if(IS_SET(xpd->digital_inputs, pos)) {
+ NOTICE("%s: Trying to OFFHOOK a digital input channel %d. Ignoring\n", __FUNCTION__, pos);
+ return -EINVAL;
+ }
+ if(IS_SET(xpd->digital_outputs, pos)) {
+ DBG("ZT_TXSIG_OFFHOOK %s digital output OFF\n", chan->name);
+ ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0);
+ return ret;
+ }
+ ret = CALL_XMETHOD(SETHOOK, xbus, xpd->id, xpd->hookstate);
+ if(ret) {
+ DBG("ZT_TXSIG_OFFHOOK Failed: ret=0x%02X\n", ret);
+ break;
+ }
+ CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 0);
+ break;
+ //DBG("ZT_TXSIG_OFFHOOK %d\n", pos);
+ //BIT_SET(xpd->hookstate, pos);
+ //break;
+ case ZT_TXSIG_KEWL:
+ DBG("ZT_TXSIG_KEWL (doing ONHOOK): %d.\n", pos);
+ break;
+ /* Fall through */
+ case ZT_TXSIG_ONHOOK:
+ DBG("ZT_TXSIG_ONHOOK: %s hookstate=0x%04X\n", chan->name, xpd->hookstate);
+ xpd->ringing[pos] = 0;
+ if(IS_SET(xpd->digital_inputs, pos)) {
+ NOTICE("%s: Trying to ONHOOK a digital input channel %d. Ignoring\n", __FUNCTION__, pos);
+ return -EINVAL;
+ }
+ if(IS_SET(xpd->digital_outputs, pos)) {
+ DBG("ZT_TXSIG_ONHOOK %s digital output OFF\n", chan->name);
+ ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0);
+ return ret;
+ }
+ BIT_CLR(xpd->hookstate, pos);
+ ret = CALL_XMETHOD(SETHOOK, xbus, xpd->id, xpd->hookstate);
+ if(ret) {
+ DBG("ZT_ONHOOK Failed: ret=0x%02X\n", ret);
+ break;
+ }
+ CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(i), LED_GREEN, 0);
+ break;
+ //DBG("ZT_TXSIG_ONHOOK: %d\n", pos);
+ //BIT_CLR(xpd->hookstate, pos);
+ //break;
+ default:
+ DBG("hooksig: unkown txsig=%d on channel %d\n", txsig, pos);
+ return -EINVAL;
+ }
+ //ret = CALL_XMETHOD(SETHOOK, xbus, xpd->id, xpd->hookstate);
+ //if(ret) {
+ // DBG("ZT_TXSIG_START Failed: ret=0x%02X\n", ret);
+ //}
+ return ret;
+}
+#endif
+
+static int xpp_sethook(struct zt_chan *chan, int hookstate)
+{
+ int pos = chan->chanpos - 1;
+ xpd_t *xpd = chan->pvt;
+ xbus_t *xbus;
+ int ret = 0;
+
+ if(!xpd) {
+ ERR("%s: channel=%d without an XPD!\n", __FUNCTION__, pos);
+ return -EINVAL;
+ }
+ xbus = xpd->xbus;
+ DBG("%s (%d) (old=0x%04X, hook-command=%d)\n", chan->name, pos, xpd->hookstate, hookstate);
+ switch(hookstate) {
+ /* On-hook, off-hook: The PBX is playing a phone on an FXO line.
+ * Can be ignored for an FXS line
+ */
+ case ZT_ONHOOK:
+ if(IS_SET(xpd->digital_inputs, pos)) {
+ NOTICE("%s: Trying to ONHOOK a digital input channel %d. Ignoring\n", __FUNCTION__, pos);
+ return -EINVAL;
+ }
+ if(IS_SET(xpd->digital_outputs, pos)) {
+ DBG("ZT_ONHOOK %s digital output OFF\n", chan->name);
+ ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 0);
+ return ret;
+ }
+ if(xpd->direction == TO_PHONE) { /* Stop ring */
+ DBG("ZT_ONHOOK: %s hookstate=0x%04X (stop ringing pos=%d)\n", chan->name, xpd->hookstate, pos);
+ xpd->ringing[pos] = 0;
+#if 1 // FIXME: Not needed -- verify
+ ret = CALL_XMETHOD(RING, xbus, xpd, pos, 0); // RING off
+#endif
+ xpd->lasttxhook[pos] = 1;
+ ret = CALL_XMETHOD(LED, xpd->xbus, xpd, BIT(pos), LED_GREEN, 0);
+ ret = CALL_XMETHOD(CHAN_POWER, xbus, xpd, BIT(pos), 0); // Power down (prevent overheating!!!)
+ if(ret) {
+ DBG("ZT_ONHOOK(stop ring) Failed: ret=0x%02X\n", ret);
+ break;
+ }
+ } else {
+ DBG("ZT_ONHOOK: %s hookstate=0x%04X (pos=%d)\n", chan->name, xpd->hookstate, pos);
+ xpd->ringing[pos] = 0;
+ BIT_CLR(xpd->hookstate, pos);
+ ret = CALL_XMETHOD(SETHOOK, xbus, xpd, xpd->hookstate);
+ if(ret) {
+ DBG("ZT_ONHOOK Failed: ret=0x%02X\n", ret);
+ break;
+ }
+ }
+ break;
+ case ZT_START:
+ DBG("ZT_START: %s hookstate=0x%04X (fall through ZT_OFFHOOK)\n", chan->name, xpd->hookstate);
+ // Fall through
+ case ZT_OFFHOOK:
+ if(xpd->direction == TO_PHONE) {
+ DBG("ZT_OFFHOOK: %s hookstate=0x%04X -- ignoring (PHONE)\n", chan->name, xpd->hookstate);
+ break;
+ }
+ DBG("ZT_OFFHOOK: %s hookstate=0x%04X (pos=%d)\n", chan->name, xpd->hookstate, pos);
+ BIT_SET(xpd->hookstate, pos);
+ xpd->ringing[pos] = 0;
+ ret = CALL_XMETHOD(SETHOOK, xbus, xpd, xpd->hookstate);
+ if(ret) {
+ DBG("ZT_OFFHOOK Failed: ret=0x%02X\n", ret);
+ break;
+ }
+ break;
+ case ZT_WINK:
+ DBG("ZT_WINK %s\n", chan->name);
+ break;
+ case ZT_FLASH:
+ DBG("ZT_FLASH %s\n", chan->name);
+ break;
+ case ZT_RING:
+ DBG("ZT_RING %s pos=%d (ringing[pos]=%d)\n", chan->name, pos, xpd->ringing[pos]);
+ if(xpd->direction == TO_PHONE) {
+ if(IS_SET(xpd->digital_inputs, pos)) {
+ NOTICE("%s: Trying to RING a digital input channel %d. Ignoring\n", __FUNCTION__, pos);
+ return -EINVAL;
+ }
+ if(IS_SET(xpd->digital_outputs, pos)) {
+ DBG("ZT_ONHOOK %s digital output ON\n", chan->name);
+ ret = CALL_XMETHOD(RELAY_OUT, xpd->xbus, xpd, pos-8, 1);
+ return ret;
+ }
+ xpd->ringing[pos] = RINGS_NUM*2;
+ ret = CALL_XMETHOD(CHAN_POWER, xbus, xpd, BIT(pos), 1); // Power up (for ring)
+ ret = CALL_XMETHOD(RING, xbus, xpd, pos, 1); // RING on
+ if(ret) {
+ DBG("ZT_RING Failed: ret=0x%02X\n", ret);
+ }
+ }
+ break;
+ case ZT_RINGOFF:
+ DBG("ZT_RINGOFF %s\n", chan->name);
+ break;
+ default:
+ DBG("UNKNOWN hookstate=0x%X\n", hookstate);
+ }
+ return ret;
+}
+
+/* Req: Set the requested chunk size. This is the unit in which you must
+ report results for conferencing, etc */
+int xpp_setchunksize(struct zt_span *span, int chunksize);
+
+/* Enable maintenance modes */
+int xpp_maint(struct zt_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
+
+ BUG_ON(!xpd);
+ DBG("%s: span->mainttimer=%d\n", __FUNCTION__, span->mainttimer);
+ switch(cmd) {
+ case ZT_MAINT_NONE:
+ printk("XXX Turn off local and remote loops XXX\n");
+ CALL_XMETHOD(LED, xpd->xbus, xpd, xpd->enabled_chans, LED_RED, 0); // FIXME: Find usage for extra LED
+ break;
+ case ZT_MAINT_LOCALLOOP:
+ printk("XXX Turn on local loopback XXX\n");
+ break;
+ case ZT_MAINT_REMOTELOOP:
+ printk("XXX Turn on remote loopback XXX\n");
+ break;
+ case ZT_MAINT_LOOPUP:
+ printk("XXX Send loopup code XXX\n");
+ CALL_XMETHOD(LED, xpd->xbus, xpd, xpd->enabled_chans, LED_RED, 1); // FIXME: Find usage for extra LED
+ // CALL_XMETHOD(LOOPBACK_AX, xpd->xbus, xpd, loopback_data, ARRAY_SIZE(loopback_data));
+ break;
+ case ZT_MAINT_LOOPDOWN:
+ printk("XXX Send loopdown code XXX\n");
+ CALL_XMETHOD(LED, xpd->xbus, xpd, xpd->enabled_chans, LED_RED, 0); // FIXME: Find usage for extra LED
+ break;
+ case ZT_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, ZT_ALARM_LOOPBACK);
+ return ret;
+}
+
+/* Set signalling type (if appropriate) */
+static int xpp_chanconfig(struct zt_chan *chan, int sigtype)
+{
+ DBG("channel %d (%s), sigtype %d.\n", chan->channo, chan->name, sigtype);
+ dump_sigtype(print_dbg, " ", sigtype);
+ // FIXME: sanity checks:
+ // - should be supported (within the sigcap)
+ // - should not replace fxs <->fxo ??? (covered by previous?)
+ return 0;
+}
+
+#if 0
+/* Okay, now we get to the signalling. You have several options: */
+
+/* Option 1: If you're a T1 like interface, you can just provide a
+ rbsbits function and we'll assert robbed bits for you. Be sure to
+ set the ZT_FLAG_RBS in this case. */
+
+/* Opt: If the span uses A/B bits, set them here */
+int (*rbsbits)(struct zt_chan *chan, int bits);
+
+/* Option 2: If you don't know about sig bits, but do have their
+ equivalents (i.e. you can disconnect battery, detect off hook,
+ generate ring, etc directly) then you can just specify a
+ sethook function, and we'll call you with appropriate hook states
+ to set. Still set the ZT_FLAG_RBS in this case as well */
+int (*hooksig)(struct zt_chan *chan, zt_txsig_t hookstate);
+
+/* Option 3: If you can't use sig bits, you can write a function
+ which handles the individual hook states */
+int (*sethook)(struct zt_chan *chan, int hookstate);
+#endif
+
+#ifdef CONFIG_ZAPTEL_WATCHDOG
+/*
+ * If the watchdog detects no received data, it will call the
+ * watchdog routine
+ */
+static int xpp_watchdog(struct zt_span *span, int cause)
+{
+ static int rate_limit = 0;
+
+ if((rate_limit++ % 1000) == 0)
+ DBG("\n");
+ return 0;
+}
+#endif
+
+/**
+ * Unregister an xpd from zaptel 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 xpd_zaptel_unregister(xpd_t *xpd)
+{
+ BUG_ON(!xpd);
+
+ if(!SPAN_REGISTERED(xpd)) {
+ NOTICE("%s: %s is already unregistered\n", __FUNCTION__, xpd->xpdname);
+ return -EIDRM;
+ }
+ if(sync_master == xpd)
+ set_sync_master(NULL); // FIXME: it's better to elect a new prince
+ update_xpd_status(xpd, ZT_ALARM_NOTOPEN);
+ if(atomic_read(&xpd->open_counter)) {
+ NOTICE("%s: %s is busy (open_counter=%d). Skipping.\n", __FUNCTION__, xpd->xpdname, atomic_read(&xpd->open_counter));
+ return -EBUSY;
+ }
+ mdelay(2); // FIXME: This is to give chance for transmit/receiveprep to finish.
+ zt_unregister(&xpd->span);
+ return 0;
+}
+
+static int xpd_zaptel_register(xpd_t *xpd)
+{
+ struct zt_chan *cur_chan;
+ struct zt_span *span;
+ xbus_t *xbus;
+ int sigfxs;
+ int i;
+ int cn;
+ const xops_t *xops;
+
+ BUG_ON(!xpd);
+ xops = xpd->xops;
+
+ if (SPAN_REGISTERED(xpd)) {
+ ERR("xpd %s already registered\n", xpd->xpdname);
+ return -EEXIST;
+ }
+ sigfxs = ! (xpd->direction == TO_PHONE); /* signaling is opposite */
+ cn = xpd->channels;
+ DBG("Initializing span: xpd %d have %d channels.\n", xpd->id, cn);
+
+ memset(xpd->chans, 0, sizeof(struct zt_chan)*cn);
+ memset(&xpd->span, 0, sizeof(struct zt_span));
+
+ span = &xpd->span;
+ xbus = xpd->xbus;
+ snprintf(span->name, MAX_SPANNAME, "%s/%s",
+ xbus->busname, xpd->xpdname);
+ {
+ char tmp[MAX_SPANNAME];
+#if SOFT_SIMULATOR
+ struct xpd_sim *sim = &xbus->sim[xpd->id];
+
+ if(sim->simulated)
+ snprintf(tmp, MAX_SPANNAME, " (sim to=%d)", sim->loopto);
+ else
+#endif
+ tmp[0] = '\0';
+
+ snprintf(span->desc, MAX_SPANDESC, "Xorcom XPD #%d/%d: %s%s",
+ xbus->num, xpd->id,
+ (xpd->direction == TO_PHONE) ? "FXS" : "FXO",
+ tmp
+ );
+ }
+ for(i = 0; i < cn; i++) {
+
+ cur_chan = &xpd->chans[i];
+ DBG("setting channel %d (sigfxs=%d)\n", i, sigfxs);
+ if(IS_SET(xpd->digital_outputs, i)) {
+ snprintf(cur_chan->name, MAX_CHANNAME, "XPP_OUT/%d-%d", xpd->id, i);
+ } else if(IS_SET(xpd->digital_inputs, i)) {
+ snprintf(cur_chan->name, MAX_CHANNAME, "XPP_IN/%d-%d", xpd->id, i);
+ } else {
+ snprintf(cur_chan->name, MAX_CHANNAME, "XPP_%s/%d-%d", (sigfxs) ? "FXO" : "FXS", xpd->id, i);
+ }
+ cur_chan->chanpos = i + 1;
+ cur_chan->pvt = xpd;
+ if (sigfxs)
+ cur_chan->sigcap =
+#if 1
+ ZT_SIG_FXSKS |
+ ZT_SIG_FXSLS |
+#else
+ ZT_SIG_SF |
+#endif
+ 0;
+ else
+ cur_chan->sigcap =
+#if 1
+ ZT_SIG_FXOKS |
+ ZT_SIG_FXOLS |
+ ZT_SIG_FXOGS |
+#else
+ ZT_SIG_SF |
+ ZT_SIG_EM |
+#endif
+ 0;
+ }
+ span->deflaw = ZT_LAW_MULAW;
+ init_waitqueue_head(&span->maintq);
+ span->pvt = xpd;
+ span->channels = cn;
+ span->chans = xpd->chans;
+
+ span->startup = xpp_startup;
+ span->shutdown = xpp_shutdown;
+ span->spanconfig = xpp_spanconfig;
+ span->chanconfig = xpp_chanconfig;
+ span->open = xpp_open;
+ span->close = xpp_close;
+#ifdef WITH_RBS
+ span->flags = ZT_FLAG_RBS;
+ span->hooksig = xpp_hooksig; /* Only with RBS bits */
+#else
+ span->sethook = xpp_sethook;
+#endif
+ span->ioctl = xpp_ioctl;
+ span->maint = xpp_maint;
+#ifdef CONFIG_ZAPTEL_WATCHDOG
+ span->watchdog = xpp_watchdog;
+#endif
+
+ DBG("Finished span_load: ZT_FLAG_RUNNING=%d\n", span->flags & ZT_FLAG_RUNNING);
+
+ DBG("Registering span of %s.\n", xpd->xpdname);
+ if(zt_register(&xpd->span, 1)) {
+ xbus_t *xbus = xpd->xbus;
+ ERR("Failed to zt_register of span of xpd %s.\n", xpd->xpdname);
+ xbus->xpds[xpd->id] = NULL;
+ list_del(&xpd->xpd_list);
+ xbus->num_xpds--;
+ return -ENODEV;
+ }
+// if(xpd->id == 0)
+// set_sync_master(xpd);
+
+ return 0;
+}
+
+
+/*------------------------- Proc debugging interface ---------------*/
+
+#ifdef CONFIG_PROC_FS
+
+static int xpp_zap_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+ int len = 0;
+ unsigned long flags;
+ int i;
+
+ spin_lock_irqsave(&xbuses_lock, flags);
+ for(i = 0; i < MAX_BUSES; i++) {
+ xbus_t *xbus = xbus_of(i);
+
+ if(xbus) {
+ len += sprintf(page + len, "%s: CONNECTOR=%s STATUS=%s bus_type=%d\n",
+ xbus->busname,
+ xbus->busdesc,
+ (xbus->hardware_exists) ? "connected" : "missing",
+ xbus->bus_type
+ );
+ }
+ }
+#if 0
+ len += sprintf(page + len, "<-- len=%d\n", len);
+#endif
+ spin_unlock_irqrestore(&xbuses_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;
+
+}
+
+#if 0
+static int xpp_zap_write_proc(struct file *file, const char __user *buffer, unsigned long count, void *data)
+{
+}
+#endif
+
+#endif
+
+/*------------------------- File Operations ------------------------*/
+
+#define MINOR_XBUS_NUM(m) ((m) >> 4)
+#define MINOR_XPD_NUM(m) ((m) & 0xF);
+
+static int xpp_sys_open (struct inode * inode, struct file * file)
+{
+ xbus_t *xbus;
+ unsigned int minor = iminor(inode);
+ unsigned int busnum = MINOR_XBUS_NUM(minor);
+ unsigned long flags;
+
+ spin_lock_irqsave(&xbuses_lock, flags);
+ xbus = xbus_of(busnum);
+ spin_unlock_irqrestore(&xbuses_lock, flags);
+ if(xbus == NULL)
+ return -ENODEV;
+ file->private_data = xbus;
+ DBG("unit %d xbus %s\n", busnum, xbus->busname);
+ return 0;
+}
+
+static int xpp_sys_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+ switch(cmd) {
+ default:
+ return -ENOTTY;
+ }
+ return 0;
+}
+
+static int xpp_sys_release (struct inode * inode, struct file * file)
+{
+ unsigned int minor = iminor(inode);
+ unsigned int busnum = MINOR_XBUS_NUM(minor);
+ xbus_t *xbus = file->private_data;
+ DBG("unit %d xbus %s\n", busnum, xbus->busname);
+ if(xbus == NULL) {
+ ERR("xpp_sys_release: xbus has dissappeared\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+#if 0
+static ssize_t xpp_sys_write (struct file * file, const char __user * buf,
+ size_t count, loff_t * ppos)
+{
+ unsigned int minor = iminor(file->f_dentry->d_inode);
+ unsigned int busnum = MINOR_XBUS_NUM(minor);
+ int xpdnum = MINOR_XPD_NUM(minor)
+ xbus_t *xbus = file->private_data;
+ xpacket_t *pack_tx;
+
+ DBG("count=%d from %d/%d xbus %s\n", count, busnum, xpdnum, xbus->busname);
+ if(xbus == NULL) {
+ ERR("xpp_sys_write: xbus has dissappeared\n");
+ return -EINVAL;
+ }
+ if(count == 0)
+ return 0;
+ if(count >= sizeof(xpacket_raw_t)) {
+ DBG("count=%d, partial write...\n", count);
+ count = sizeof(xpacket_raw_t);
+ }
+ pack_tx = xbus->ops->packet_new(xbus, GFP_KERNEL);
+ if (!pack_tx) {
+ return -ENOMEM;
+ }
+ if (copy_from_user (pack_tx->content.raw, buf, count)) {
+ return -EFAULT;
+ }
+ XPD_ADDR_SET(pack_tx->content.addr, xpdnum);
+ pack_tx->datalen = count;
+ // pack_tx->flags |= XPP_PACKET_FIREANDFORGET;
+ DBG("sending op=%d to %d\n", pack_tx->content.opcode, xpdnum);
+ xbus_reset_counters(xbus);
+ pcm_write_enable = 1;
+ packet_send(xbus, pack_tx);
+ return count;
+}
+#endif
+
+static struct file_operations xpp_fops = {
+ .owner = THIS_MODULE,
+ .llseek = no_llseek,
+#if 0
+ .write = xpp_sys_write,
+#endif
+ .ioctl = xpp_sys_ioctl,
+ .open = xpp_sys_open,
+ .release = xpp_sys_release,
+};
+
+
+/*------------------------- Initialization -------------------------*/
+
+static void do_cleanup(void)
+{
+ if(timer_pending(&xpp_timer))
+ del_timer_sync(&xpp_timer);
+ unregister_chrdev(XPP_CTL_MAJOR, THIS_MODULE->name);
+#ifdef CONFIG_PROC_FS
+ remove_proc_entry(PROC_SYNC, xpp_procdir);
+ remove_proc_entry(PROC_XBUSES, xpp_procdir);
+ if(xpp_procdir) {
+ remove_proc_entry(PROC_DIR, NULL);
+ }
+#endif
+ if (xpp_worker) {
+ flush_workqueue(xpp_worker);
+ destroy_workqueue(xpp_worker);
+ xpp_worker = NULL;
+ }
+ kmem_cache_destroy(packet_cache);
+}
+
+int __init xpp_zap_init(void)
+{
+ INFO("%s revision %s\n", THIS_MODULE->name, revision);
+
+ packet_cache = kmem_cache_create("xpp_packets",
+ sizeof(xpacket_t),
+ 0, 0,
+ NULL, NULL);
+ if(!packet_cache) {
+ return -ENOMEM;
+ }
+#ifdef CONFIG_PROC_FS
+ xpp_procdir = proc_mkdir(PROC_DIR, NULL);
+ if(!xpp_procdir) {
+ do_cleanup();
+ return -EIO;
+ }
+ struct proc_dir_entry *ent;
+
+ ent = create_proc_entry(PROC_SYNC, 0644, xpp_procdir);
+ if(!ent) {
+ do_cleanup();
+ return -EFAULT;
+ }
+ ent->read_proc = proc_sync_read;
+ ent->write_proc = proc_sync_write;
+ ent->data = NULL;
+ ent = create_proc_read_entry(PROC_XBUSES, 0444, xpp_procdir, xpp_zap_read_proc, 0);
+ if (!ent) {
+ do_cleanup();
+ return -EFAULT;
+ }
+#endif
+ xpp_worker = create_singlethread_workqueue("xppworker");
+ if(!xpp_worker) {
+ ERR("Failed to create card detector workqueue.\n");
+ do_cleanup();
+ return -ENOMEM;
+ }
+
+ if (register_chrdev(XPP_CTL_MAJOR, THIS_MODULE->name, &xpp_fops)) {
+ printk (KERN_WARNING "%s: unable to get major %d\n", THIS_MODULE->name, XPP_CTL_MAJOR);
+ do_cleanup();
+ return -EIO;
+ }
+
+ /* Only timer init. We add it only *after* zt_register */
+ init_timer(&xpp_timer);
+ set_sync_master(NULL); /* Internal ticking */
+ return 0;
+}
+
+void __exit xpp_zap_cleanup(void)
+{
+// unsigned long flags;
+ int i;
+
+ for(i = 0; i < MAX_BUSES; i++) {
+ xbus_t *xbus = xbus_of(i);
+ if(!xbus)
+ continue;
+ xbus_remove(xbus);
+ }
+// spin_lock_irqsave(&xbuses_lock, flags);
+ if(bus_count) {
+ ERR("%s: bus_count=%d!\n", __FUNCTION__, bus_count);
+ }
+// spin_unlock_irqrestore(&xbuses_lock, flags);
+ do_cleanup();
+}
+
+EXPORT_SYMBOL(print_dbg);
+EXPORT_SYMBOL(card_detected);
+EXPORT_SYMBOL(xpd_alloc);
+EXPORT_SYMBOL(xbus_activate);
+EXPORT_SYMBOL(xbus_deactivate);
+EXPORT_SYMBOL(xpd_of);
+EXPORT_SYMBOL(xbus_new);
+EXPORT_SYMBOL(xbus_remove);
+EXPORT_SYMBOL(xbus_reset_counters);
+EXPORT_SYMBOL(packet_send);
+EXPORT_SYMBOL(fill_beep);
+EXPORT_SYMBOL(xbus_enqueue_packet);
+EXPORT_SYMBOL(xbus_dequeue_packet);
+EXPORT_SYMBOL(init_xbus_packet_queue);
+EXPORT_SYMBOL(drain_xbus_packet_queue);
+EXPORT_SYMBOL(phone_hook);
+EXPORT_SYMBOL(xpp_check_hookstate);
+EXPORT_SYMBOL(xpp_tick);
+EXPORT_SYMBOL(xpp_open);
+EXPORT_SYMBOL(xpp_close);
+EXPORT_SYMBOL(xpp_ioctl);
+EXPORT_SYMBOL(xpp_maint);
+
+MODULE_DESCRIPTION("XPP Zaptel Driver");
+MODULE_AUTHOR("Oron Peled <oron@actcom.co.il>");
+MODULE_LICENSE("GPL");
+MODULE_VERSION("$Id$");
+
+module_init(xpp_zap_init);
+module_exit(xpp_zap_cleanup);