From a9ababa28c7ccedaf4be1501d009f99d9a7ba580 Mon Sep 17 00:00:00 2001 From: kpfleming Date: Wed, 15 Feb 2006 02:26:14 +0000 Subject: 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 --- xpp/xpp_zap.c | 2312 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2312 insertions(+) create mode 100644 xpp/xpp_zap.c (limited to 'xpp/xpp_zap.c') 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 + * 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 + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,0) +# warning "This module is tested only with 2.6 kernels" +#endif + +#include +#include +#include +#include +#include /* for udelay */ +#include +#include +#include +#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 "); +MODULE_LICENSE("GPL"); +MODULE_VERSION("$Id$"); + +module_init(xpp_zap_init); +module_exit(xpp_zap_cleanup); -- cgit v1.2.3