/*
 * 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: 1.15 $";

#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, 0, "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: xpp_zap.c,v 1.15 2006/04/11 09:05:16 svn-mirror Exp $");

module_init(xpp_zap_init);
module_exit(xpp_zap_cleanup);
