/*
 * Written by Oron Peled <oron@actcom.co.il>
 * Copyright (C) 2004-2006, Xorcom
 *
 * 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/module.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#ifdef	PROTOCOL_DEBUG
#include <linux/ctype.h>
#endif
#include <linux/workqueue.h>
#include <linux/device.h>
#include <linux/delay.h>	/* for msleep() to debug */
#include "xpd.h"
#include "xpp_dahdi.h"
#include "xbus-core.h"
#include "card_global.h"
#ifdef	XPP_DEBUGFS
#include "xpp_log.h"
#endif
#include "dahdi_debug.h"

static const char rcsid[] = "$Id: xbus-core.c 5167 2008-10-29 15:09:11Z tzafrir $";

/* Defines */
#define	INITIALIZATION_TIMEOUT	(90*HZ)		/* in jiffies */
#define	PROC_XBUSES		"xbuses"
#define	PROC_XBUS_SUMMARY	"summary"
#ifdef	OLD_PROC
#define	PROC_XBUS_WAITFOR_XPDS	"waitfor_xpds"
#endif

#ifdef	PROTOCOL_DEBUG
#define	PROC_XBUS_COMMAND	"command"
static int proc_xbus_command_write(struct file *file, const char __user *buffer, unsigned long count, void *data);
#endif

/* Command line parameters */
extern int debug;
static DEF_PARM(uint, poll_timeout, 1000, 0644, "Timeout (in jiffies) waiting for units to reply");
static DEF_PARM_BOOL(rx_tasklet, 0, 0644, "Use receive tasklets");

static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data);
#ifdef	OLD_PROC
static int xbus_read_waitfor_xpds(char *page, char **start, off_t off, int count, int *eof, void *data);
#endif
static void transport_init(xbus_t *xbus, struct xbus_ops *ops, ushort max_send_size, void *priv);
static void transport_destroy(xbus_t *xbus);

/* Data structures */
static spinlock_t		xbuses_lock = SPIN_LOCK_UNLOCKED;
static struct proc_dir_entry	*proc_xbuses = NULL;

static struct xbus_desc {
	xbus_t			*xbus;
} xbuses_array[MAX_BUSES];

const char *xbus_statename(enum xbus_state st)
{
	switch(st) {
		case XBUS_STATE_START:		return "START";
		case XBUS_STATE_IDLE:		return "IDLE";
		case XBUS_STATE_SENT_REQUEST:	return "SENT_REQUEST";
		case XBUS_STATE_RECVD_DESC:	return "RECVD_DESC";
		case XBUS_STATE_READY:		return "READY";
		case XBUS_STATE_DEACTIVATING:	return "DEACTIVATING";
		case XBUS_STATE_DEACTIVATED:	return "DEACTIVATED";
		case XBUS_STATE_DISCONNECTED:	return "DISCONNECTED";
		case XBUS_STATE_FAIL:		return "FAIL";
	}
	return NULL;
}

static void init_xbus(uint num, xbus_t *xbus)
{
	struct xbus_desc	*desc;

	BUG_ON(num >= ARRAY_SIZE(xbuses_array));
	desc = &xbuses_array[num];
	desc->xbus = xbus;
}

xbus_t *xbus_num(uint num)
{
	struct xbus_desc	*desc;

	if(num >= ARRAY_SIZE(xbuses_array))
		return NULL;
	desc = &xbuses_array[num];
	return desc->xbus;
}

static void initialize_xbuses_array(void)
{
	int	i;

	for(i = 0; i < ARRAY_SIZE(xbuses_array); i++)
		init_xbus(i, NULL);
}

static void finalize_xbuses_array(void)
{
	int	i;

	for(i = 0; i < ARRAY_SIZE(xbuses_array); i++) {
		if(xbuses_array[i].xbus != NULL) {
			ERR("%s: xbus #%d is not NULL\n", __FUNCTION__, i);
			BUG();
		}
	}
}

xbus_t *get_xbus(const char *msg, xbus_t *xbus)
{
	struct device	*dev;

	XBUS_DBG(DEVICES, xbus, "%s: refcount_xbus=%d\n",
		msg, refcount_xbus(xbus));
	dev = get_device(&xbus->astribank);
	return dev_to_xbus(dev);
}

void put_xbus(const char *msg, xbus_t *xbus)
{
	XBUS_DBG(DEVICES, xbus, "%s: refcount_xbus=%d\n",
		msg, refcount_xbus(xbus));
	put_device(&xbus->astribank);
}

int refcount_xbus(xbus_t *xbus)
{
	struct kref *kref = &xbus->astribank.kobj.kref;

	return atomic_read(&kref->refcount);
}

/*------------------------- Debugfs Handling -----------------------*/
#ifdef	XPP_DEBUGFS

#define DEBUGFS_BUFSIZ		4096	/* must be power of two, otherwise POS_IN_BUF will have to use '%' instead of '&' */
#define POS_IN_BUF(x)		((x) & (DEBUGFS_BUFSIZ-1))

struct debugfs_data {
	spinlock_t lock;
	xbus_t *xbus;
	char buffer[DEBUGFS_BUFSIZ];
	unsigned long head, tail;	/* reading and writing are performed at position (head % BUF_SIZ) and (tail % BUF_SIZ) */
	wait_queue_head_t queue;
};

static unsigned long add_to_buf(struct debugfs_data *d, unsigned long tail, const void *buf, unsigned long len)
{
	unsigned long count = min(len, (unsigned long)(DEBUGFS_BUFSIZ - POS_IN_BUF(tail)));
	memcpy(d->buffer + POS_IN_BUF(tail), buf, count);		/* fill starting at position tail */
	memcpy(d->buffer, (u_char *)buf + count, len - count);		/* fill leftover */
	return len;
}

int xbus_log(xbus_t *xbus, xpd_t *xpd, int direction, const void *buf, unsigned long len)
{
	unsigned long tail;
	unsigned long flags;
	struct debugfs_data *d;
	struct log_header header;
	int ret = 0;
	
	BUG_ON(!xbus);
	BUG_ON(!xpd);
	BUG_ON(sizeof(struct log_header) + len > DEBUGFS_BUFSIZ);
	d = xbus->debugfs_data;
	if (!d)			/* no consumer process */
		return ret;
	spin_lock_irqsave(&d->lock, flags);
	if (sizeof(struct log_header) + len > DEBUGFS_BUFSIZ - (d->tail - d->head)) {
		ret = -ENOSPC;
		XPD_DBG(GENERAL, xpd, "Dropping debugfs data of len %lu, free space is %lu\n", sizeof(struct log_header) + len,
				DEBUGFS_BUFSIZ - (d->tail - d->head));
		goto out;
	}
	header.len = sizeof(struct log_header) + len;
	header.time = jiffies_to_msecs(jiffies);
	header.xpd_num = xpd->xbus_idx;
	header.direction = (char)direction;
	tail = d->tail;
	tail += add_to_buf(d, tail, &header, sizeof(header));
	tail += add_to_buf(d, tail, buf, len);
	d->tail = tail;
	wake_up_interruptible(&d->queue);
out:
	spin_unlock_irqrestore(&d->lock, flags);
	return ret;
}

static struct dentry	*debugfs_root = NULL;
static int debugfs_open(struct inode *inode, struct file *file);
static ssize_t debugfs_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos);
static int debugfs_release(struct inode *inode, struct file *file);

static struct file_operations debugfs_operations = {
	.open		= debugfs_open,
	.read		= debugfs_read,
	.release	= debugfs_release,
};

/*
 * As part of the "inode diet" the private data member of struct inode
 * has changed in 2.6.19. However, Fedore Core 6 adopted this change
 * a bit earlier (2.6.18). If you use such a kernel, Change the 
 * following test from 2,6,19 to 2,6,18.
 */
#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,19)
#define	I_PRIVATE(inode)	((inode)->u.generic_ip)
#else
#define	I_PRIVATE(inode)	((inode)->i_private)
#endif

static int debugfs_open(struct inode *inode, struct file *file)
{
	xbus_t	*xbus = I_PRIVATE(inode);
	struct debugfs_data *d;
	struct log_global_header gheader;

	BUG_ON(!xbus);
	XBUS_DBG(GENERAL, xbus, "\n");
	if (xbus->debugfs_data)
		return -EBUSY;
	d = KZALLOC(sizeof(struct debugfs_data), GFP_KERNEL);
	if (!d)
		return -ENOMEM;
	try_module_get(THIS_MODULE);
	spin_lock_init(&d->lock);
	d->xbus = xbus;
	d->head = d->tail = 0;
	init_waitqueue_head(&d->queue);
	file->private_data = d;

	gheader.magic = XPP_LOG_MAGIC;
	gheader.version = 1;
	d->tail += add_to_buf(d, d->tail, &gheader, sizeof(gheader));

	xbus->debugfs_data = d;
	return 0;
}

static ssize_t debugfs_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos)
{
	struct debugfs_data *d = file->private_data;
	size_t len;

	BUG_ON(!d);
	BUG_ON(!d->xbus);
	XBUS_DBG(GENERAL, d->xbus, "\n");
	while (d->head == d->tail) {
		if (wait_event_interruptible(d->queue, d->head != d->tail))
			return -EAGAIN;
	}
	len = min(nbytes, (size_t)(d->tail - d->head));
	if (copy_to_user(buf, d->buffer + POS_IN_BUF(d->head), len))
		return -EFAULT;
	d->head += len;
	/* optimization to avoid future buffer wraparound */
	if (d->head == d->tail) {
		unsigned long flags;
		spin_lock_irqsave(&d->lock, flags);
		if (d->head == d->tail)
			d->head = d->tail = 0;
		spin_unlock_irqrestore(&d->lock, flags);
	}
	return len;
}

static int debugfs_release(struct inode *inode, struct file *file)
{
	struct debugfs_data *d = file->private_data;

	BUG_ON(!d);
	BUG_ON(!d->xbus);
	XBUS_DBG(GENERAL, d->xbus, "\n");
	d->xbus->debugfs_data = NULL;
	KZFREE(d);
	module_put(THIS_MODULE);
	return 0;
}
#endif

/*------------------------- Frame  Handling ------------------------*/

void xframe_init(xbus_t *xbus, xframe_t *xframe, void *buf, size_t maxsize, void *priv)
{
	memset(xframe, 0, sizeof(*xframe));
	INIT_LIST_HEAD(&xframe->frame_list);
	xframe->priv = priv;
	xframe->xbus = xbus;
	xframe->packets = xframe->first_free = buf;
	xframe->frame_maxlen = maxsize;
	atomic_set(&xframe->frame_len, 0);
	do_gettimeofday(&xframe->tv_created);
	xframe->xframe_magic = XFRAME_MAGIC;
}

/*
 * Return pointer to next packet slot in the frame
 * or NULL if the frame is full.
 *
 * FIXME: we do not use atomic_add_return() because kernel-2.6.8
 *        does not have it. This make this code a little racy,
 *        but we currently call xframe_next_packet() only in the
 *        PCM loop (xbus_tick() etc.)
 */
xpacket_t *xframe_next_packet(xframe_t *frm, int len)
{
	int newlen = XFRAME_LEN(frm);

	newlen += len;
//	DBG(GENERAL, "len=%d, newlen=%d, frm->frame_len=%d\n", len, newlen, XFRAME_LEN(frm));
	if (newlen > XFRAME_DATASIZE) {
		return NULL;
	}
	atomic_add(len, &frm->frame_len);
	return (xpacket_t *)(frm->packets + newlen - len);
}

static spinlock_t serialize_dump_xframe = SPIN_LOCK_UNLOCKED;

static void do_hexdump(const char msg[], byte *data, uint16_t len)
{
	int	i;
	int	debug = DBG_ANY;	/* mask global debug */

	for(i = 0; i < len; i++)
		DBG(ANY, "%s: %3d> %02X\n", msg, i, data[i]);
}

void dump_xframe(const char msg[], const xbus_t *xbus, const xframe_t *xframe, int debug)
{
	const uint16_t	frm_len = XFRAME_LEN(xframe);
	xpacket_t	*pack;
	uint16_t	pos = 0;
	uint16_t	nextpos;
	int 		num = 1;
	bool		do_print;
	unsigned long	flags;
	
	if(xframe->xframe_magic != XFRAME_MAGIC) {
		XBUS_ERR(xbus, "%s: bad xframe_magic %lX\n",
			__FUNCTION__, xframe->xframe_magic);
		return;
	}
	spin_lock_irqsave(&serialize_dump_xframe, flags);
	do {
		if(pos >= xbus->transport.max_send_size) {
			if(printk_ratelimit()) {
				XBUS_NOTICE(xbus, "%s: xframe overflow (%d bytes)\n",
				    msg, frm_len);
				do_hexdump(msg, xframe->packets, frm_len);
			}
			break;
		}
		if(pos > frm_len) {
			if(printk_ratelimit()) {
				XBUS_NOTICE(xbus, "%s: packet overflow pos=%d frame_len=%d\n",
				    msg, pos, frm_len);
				do_hexdump(msg, xframe->packets, frm_len);
			}
			break;
		}
		pack = (xpacket_t *)&xframe->packets[pos];
		if(XPACKET_LEN(pack) <= 0) {
			if(printk_ratelimit()) {
				XBUS_NOTICE(xbus, "%s: xframe -- bad packet_len=%d pos=%d frame_len=%d\n",
				    msg, XPACKET_LEN(pack), pos, frm_len);
				do_hexdump(msg, xframe->packets, frm_len);
			}
			break;
		}
		nextpos = pos + XPACKET_LEN(pack);
		if(nextpos > frm_len) {
			if(printk_ratelimit()) {
				XBUS_NOTICE(xbus, "%s: packet overflow nextpos=%d frame_len=%d\n",
				    msg, nextpos, frm_len);
				do_hexdump(msg, xframe->packets, frm_len);
			}
			break;
		}
		do_print = 0;
		if(debug == DBG_ANY)
			do_print = 1;
		else if(XPACKET_OP(pack) != XPROTO_NAME(GLOBAL,PCM_READ) &&
			XPACKET_OP(pack) != XPROTO_NAME(GLOBAL,PCM_WRITE))
			do_print = 1;
		else if(debug & DBG_PCM) {
			static int	rate_limit;

			if((rate_limit++ % 1003) == 0)
				do_print = 1;
		}
		if(do_print) {
			if(num == 1) {
				XBUS_DBG(ANY, xbus, "%s: frame_len=%d. %s\n",
						msg, frm_len,
						(XPACKET_IS_PCM(pack))
							? "(IS_PCM)"
							: "");
			}
			XBUS_DBG(ANY, xbus, "  %3d. DATALEN=%d pcm=%d slot=%d OP=0x%02X XPD-%d%d (pos=%d)\n",
				num, XPACKET_LEN(pack),
				XPACKET_IS_PCM(pack), XPACKET_PCMSLOT(pack),
				XPACKET_OP(pack),
				XPACKET_ADDR_UNIT(pack), XPACKET_ADDR_SUBUNIT(pack),
				pos);
			dump_packet("     ", pack, debug);
		}
		num++;
		pos = nextpos;
		if(pos >= frm_len)
			break;
	} while(1);
	spin_unlock_irqrestore(&serialize_dump_xframe, flags);
}

/**
 *
 * Frame is freed:
 * 	- In case of error, by this function.
 * 	- Otherwise, by the underlying sending mechanism
 */
int send_pcm_frame(xbus_t *xbus, xframe_t *xframe)
{
	struct xbus_ops	*ops;
	int		ret = -ENODEV;

	BUG_ON(!xframe);
	if(!XBUS_IS(xbus, READY)) {
		XBUS_ERR(xbus, "Dropped a pcm frame -- hardware is not ready.\n");
		ret = -ENODEV;
		goto error;
	}
	ops = transportops_get(xbus);
	BUG_ON(!ops);
	ret = ops->xframe_send_pcm(xbus, xframe);
	transportops_put(xbus);
	if(ret)
		XBUS_COUNTER(xbus, TX_BYTES) += XFRAME_LEN(xframe);
	return ret;

error:	
	FREE_SEND_XFRAME(xbus, xframe);
	return ret;
}

static int really_send_cmd_frame(xbus_t *xbus, xframe_t *xframe)
{
	struct xbus_ops	*ops;
	int		ret;

	BUG_ON(!xbus);
	BUG_ON(!xframe);
	BUG_ON(xframe->xframe_magic != XFRAME_MAGIC);
	if(XBUS_IS(xbus, DISCONNECTED)) {
		XBUS_ERR(xbus, "Dropped command before sending -- hardware deactivated.\n");
		dump_xframe("Dropped", xbus, xframe, DBG_ANY);
		FREE_SEND_XFRAME(xbus, xframe);
		return -ENODEV;
	}
	ops = transportops_get(xbus);
	BUG_ON(!ops);
	if(debug & DBG_COMMANDS)
		dump_xframe("TX-CMD", xbus, xframe, DBG_ANY);
	ret = ops->xframe_send_cmd(xbus, xframe);
	transportops_put(xbus);
	if(ret == 0) {
		XBUS_COUNTER(xbus, TX_CMD)++;
		XBUS_COUNTER(xbus, TX_BYTES) += XFRAME_LEN(xframe);
	}
	return ret;
}

int xbus_command_queue_tick(xbus_t *xbus)
{
	xframe_t	*frm;
	int		ret = 0;

	xbus->command_tick_counter++;
	frm = xframe_dequeue(&xbus->command_queue);
	if(frm) {
		BUG_ON(frm->xframe_magic != XFRAME_MAGIC);
		ret = really_send_cmd_frame(xbus, frm);
		if(ret < 0) {
			XBUS_ERR(xbus,
				"Failed to send from command_queue (ret=%d)\n",
				ret);
			xbus_setstate(xbus, XBUS_STATE_FAIL);
		}
	} else
		wake_up(&xbus->command_queue_empty);
	return ret;
}

static void xbus_command_queue_clean(xbus_t *xbus)
{
	xframe_t	*frm;

	XBUS_DBG(DEVICES, xbus, "count=%d\n", xbus->command_queue.count);
	xframe_queue_disable(&xbus->command_queue, 1);
	while((frm = xframe_dequeue(&xbus->command_queue)) != NULL) {
		FREE_SEND_XFRAME(xbus, frm);
	}
}

static int xbus_command_queue_waitempty(xbus_t *xbus)
{
	int		ret;

	XBUS_DBG(DEVICES, xbus, "Waiting for command_queue to empty\n");
	ret = wait_event_interruptible(xbus->command_queue_empty,
				xframe_queue_count(&xbus->command_queue) == 0);
	if(ret) {
		XBUS_ERR(xbus, "waiting for command_queue interrupted!!!\n");
	}
	return ret;
}

int send_cmd_frame(xbus_t *xbus, xframe_t *xframe)
{
	static int	rate_limit;
	int		ret = 0;


	BUG_ON(xframe->xframe_magic != XFRAME_MAGIC);
	if(XBUS_IS(xbus, DISCONNECTED)) {
		XBUS_ERR(xbus, "Dropped command before queueing -- hardware deactivated.\n");
		ret = -ENODEV;
		goto err;
	}
	if(debug & DBG_COMMANDS)
		dump_xframe(__FUNCTION__, xbus, xframe, DBG_ANY);
	if(!xframe_enqueue(&xbus->command_queue, xframe)) {
		if((rate_limit++ % 1003) == 0) {
			XBUS_ERR(xbus,
				"Dropped command xframe. Cannot enqueue (%d)\n",
				rate_limit);
			dump_xframe(__FUNCTION__, xbus, xframe, DBG_ANY);
		}
		xbus_setstate(xbus, XBUS_STATE_FAIL);
		ret = -E2BIG;
		goto err;
	}
	return 0;
err:
	FREE_SEND_XFRAME(xbus, xframe);
	return ret;
}

/*------------------------- Receive Tasklet Handling ---------------*/

static void xframe_enqueue_recv(xbus_t *xbus, xframe_t *xframe)
{
	int	cpu = smp_processor_id();

	BUG_ON(!xbus);
	xbus->cpu_rcv_intr[cpu]++;
	if(!xframe_enqueue(&xbus->receive_queue, xframe)) {
		static int	rate_limit;

		if((rate_limit++ % 1003) == 0)
			XBUS_ERR(xbus, "Failed to enqueue for receive_tasklet (%d)\n", rate_limit);
		FREE_RECV_XFRAME(xbus, xframe);	/* return to receive_pool */
		return;
	}
	tasklet_schedule(&xbus->receive_tasklet);
}

/*
 * process frames in the receive_queue in a tasklet
 */
static void receive_tasklet_func(unsigned long data)
{
	xbus_t		*xbus = (xbus_t *)data;
	xframe_t	*xframe = NULL;
	int		cpu = smp_processor_id();

	BUG_ON(!xbus);
	xbus->cpu_rcv_tasklet[cpu]++;
	while((xframe = xframe_dequeue(&xbus->receive_queue)) != NULL) {
		xframe_receive(xbus, xframe);
	}
}

void xbus_receive_xframe(xbus_t *xbus, xframe_t *xframe)
{
	BUG_ON(!xbus);
	if(rx_tasklet) {
		xframe_enqueue_recv(xbus, xframe);
	} else {
		if(likely(!XBUS_IS(xbus, DISCONNECTED)))
			xframe_receive(xbus, xframe);
		else
			FREE_RECV_XFRAME(xbus, xframe);	/* return to receive_pool */
	}
}

/*------------------------- Bus Management -------------------------*/
xpd_t	*xpd_of(const xbus_t *xbus, int xpd_num)
{
	if(!VALID_XPD_NUM(xpd_num))
		return NULL;
	return xbus->xpds[xpd_num];
}

xpd_t	*xpd_byaddr(const xbus_t *xbus, uint unit, uint subunit)
{
	if(unit > MAX_UNIT || subunit > MAX_SUBUNIT)
		return NULL;
	return xbus->xpds[XPD_IDX(unit,subunit)];
}

int xbus_xpd_bind(xbus_t *xbus, xpd_t *xpd, int unit, int subunit)
{
	unsigned int	xpd_num;
	unsigned long	flags;

	BUG_ON(!xbus);
	xpd_num = XPD_IDX(unit,subunit);
	XBUS_DBG(DEVICES, xbus, "XPD #%d\n", xpd_num);
	spin_lock_irqsave(&xbus->lock, flags);
	if(!VALID_XPD_NUM(xpd_num)) {
		XBUS_ERR(xbus, "Bad xpd_num = %d\n", xpd_num);
		BUG();
	}
	if(xbus->xpds[xpd_num] != NULL) {
		xpd_t	*other = xbus->xpds[xpd_num];

		XBUS_ERR(xbus, "xpd_num=%d is occupied by %p (%s)\n",
				xpd_num, other, other->xpdname);
		BUG();
	}
	snprintf(xpd->xpdname, XPD_NAMELEN, "XPD-%1d%1d", unit, subunit);
	MKADDR(&xpd->addr, unit, subunit);
	xpd->xbus_idx = xpd_num;
	xbus->xpds[xpd_num] = xpd;
	xpd->xbus = xbus;
	atomic_inc(&xbus->num_xpds);
	spin_unlock_irqrestore(&xbus->lock, flags);
	/* Must be done out of atomic context */
	if(xpd_device_register(xbus, xpd) < 0) {
		XPD_ERR(xpd, "%s: xpd_device_register() failed\n", __FUNCTION__);
		/* FIXME: What to do? */
	}
	return 0;
}

int xbus_xpd_unbind(xbus_t *xbus, xpd_t *xpd)
{
	unsigned int	xpd_num = xpd->xbus_idx;
	unsigned long	flags;

	XBUS_DBG(DEVICES, xbus, "XPD #%d\n", xpd_num);
	if(!VALID_XPD_NUM(xpd_num)) {
		XBUS_ERR(xbus, "%s: Bad xpd_num = %d\n", __FUNCTION__, xpd_num);
		BUG();
	}
	if(xbus->xpds[xpd_num] == NULL) {
		XBUS_ERR(xbus, "%s: slot xpd_num=%d is empty\n", __FUNCTION__, xpd_num);
		BUG();
	}
	if(xbus->xpds[xpd_num] != xpd) {
		xpd_t	*other = xbus->xpds[xpd_num];

		XBUS_ERR(xbus, "%s: slot xpd_num=%d is occupied by %p (%s)\n",
				__FUNCTION__, xpd_num, other, other->xpdname);
		BUG();
	}
	spin_lock_irqsave(&xbus->lock, flags);
	xpd->xbus = NULL;
	xbus->xpds[xpd_num] = NULL;
	if(atomic_dec_and_test(&xbus->num_xpds))
		xbus_setstate(xbus, XBUS_STATE_IDLE);
	spin_unlock_irqrestore(&xbus->lock, flags);
	return 0;
}

static int new_card(xbus_t *xbus,
		int unit,
		byte type,
		byte subtype,
		byte numchips,
		byte ports_per_chip,
		byte ports,
		byte port_dir)
{
	const xproto_table_t	*proto_table;
	const xops_t		*xops;
	int			i;
	int			subunits;
	int			ret = 0;

	proto_table = xproto_get(type);
	if(!proto_table) {
		XBUS_NOTICE(xbus,
			"CARD %d: missing protocol table for type %d. Ignored.\n",
			unit, type);
		return -EINVAL;
	}
	subunits = (ports + proto_table->ports_per_subunit - 1) /
			proto_table->ports_per_subunit;
	XBUS_DBG(DEVICES, xbus, "CARD %d type=%d.%d ports=%d (%dx%d), %d subunits, port-dir=0x%02X\n",
			unit,
			type,
			subtype,
			ports,
			numchips,
			ports_per_chip,
			subunits,
			port_dir
		);
	xops = &proto_table->xops;
	BUG_ON(!xops);
	xbus->worker->num_units += subunits - 1;
	for(i = 0; i < subunits; i++) {
		if(!XBUS_IS(xbus, RECVD_DESC)) {
			XBUS_NOTICE(xbus,
				"Cannot create XPD=%d%d in state %s\n",
				unit,
				i,
				xbus_statename(XBUS_STATE(xbus)));
			ret = -ENODEV;
			goto out;
		}
		XBUS_DBG(DEVICES, xbus, "Creating XPD=%d%d type=%d.%d\n",
				unit,
				i,
				type,
				subtype);
		if(!XBUS_IS(xbus, RECVD_DESC)) {
			XBUS_ERR(xbus, "Aborting creation -- In bad state %s\n",
				xbus_statename(XBUS_STATE(xbus)));
			ret = -ENODEV;
			goto out;
		}
		ret = create_xpd(xbus, proto_table, unit, i, type, subtype, subunits, port_dir);
		if(ret < 0) {
			XBUS_ERR(xbus, "Creation of XPD=%d%d failed %d\n",
				unit, i, ret);
			goto out;
		}
		xbus->worker->num_units_initialized++;
	}
out:
	xproto_put(proto_table);	/* ref count is inside the xpds now */
	return ret;
}

static void xbus_release_xpds(xbus_t *xbus)
{
	int			i;

	XBUS_INFO(xbus, "[%s] Release XPDS\n", xbus->label);
	for(i = 0; i < MAX_XPDS; i++) {
		xpd_t *xpd = xpd_of(xbus, i);

		if(xpd)
			put_xpd(__FUNCTION__, xpd);	/* taken in xbus_xpd_bind() */
	}
}

static int xpd_initialize(xpd_t *xpd)
{
	int	ret = -ENODEV;

	if(CALL_XMETHOD(card_init, xpd->xbus, xpd) < 0) {
		XPD_ERR(xpd, "Card Initialization failed\n");
		goto out;
	}
	//CALL_XMETHOD(XPD_STATE, xpd->xbus, xpd, 0);	/* Turn off all channels */
	xpd->card_present = 1;
	CALL_XMETHOD(XPD_STATE, xpd->xbus, xpd, 1);		/* Turn on all channels */
	if(!xpd_setstate(xpd, XPD_STATE_READY)) {
		goto out;
	}
	XPD_INFO(xpd, "Initialized: %s\n", xpd->type_name);
	xpd_post_init(xpd);
	ret = 0;
out:
	return ret;
}

static int xbus_initialize(xbus_t *xbus)
{
	int	unit;
	int	subunit;
	xpd_t	*xpd;

	XBUS_DBG(DEVICES, xbus, "refcount_xbus=%d\n",
			refcount_xbus(xbus));
	for(unit = 0; unit < MAX_UNIT; unit++) {
		xpd = xpd_byaddr(xbus, unit, 0);
		if(!xpd)
			continue;
		if(run_initialize_registers(xpd) < 0) {
			XBUS_ERR(xbus, "Register Initialization of card #%d failed\n", unit);
			goto err;
		}
		for(subunit = 0; subunit < MAX_SUBUNIT; subunit++) {
			int	ret;

			xpd = xpd_byaddr(xbus, unit, subunit);
			if(!xpd)
				continue;
			xpd = get_xpd(__FUNCTION__, xpd);
			if(!xpd) {
				XBUS_ERR(xbus,
						"Aborting initialization. XPD-%d%d is gone.\n",
						unit, subunit);
				goto err;
			}
			ret = xpd_initialize(xpd);
			put_xpd(__FUNCTION__, xpd);
			if(ret < 0)
				goto err;
		}
	}
	return 0;
err:
	return -EINVAL;
}

/*
 * This must be called from synchronous (non-interrupt) context
 * it returns only when all XPD's on the bus are detected and
 * initialized.
 */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
static void xbus_populate(struct work_struct *work)
{
	struct xbus_workqueue	*worker = container_of(work, struct xbus_workqueue, xpds_init_work);
#else
void xbus_populate(void *data)
{
	struct xbus_workqueue	*worker = data;
#endif
	xbus_t			*xbus;
	struct list_head	*card;
	struct list_head	*next_card;
	unsigned long		flags;
	int			ret = 0;

	xbus = worker->xbus;
	XBUS_DBG(DEVICES, xbus, "Entering %s\n", __FUNCTION__);
	spin_lock_irqsave(&worker->worker_lock, flags);
	list_for_each_safe(card, next_card, &worker->card_list) {
		struct card_desc_struct	*card_desc = list_entry(card, struct card_desc_struct, card_list);

		list_del(card);
		BUG_ON(card_desc->magic != CARD_DESC_MAGIC);
		/* Release/Reacquire locks around blocking calls */
		spin_unlock_irqrestore(&xbus->worker->worker_lock, flags);
		ret = new_card(xbus,
			card_desc->xpd_addr.unit,
			card_desc->type,
			card_desc->subtype,
			card_desc->numchips,
			card_desc->ports_per_chip,
			card_desc->ports,
			card_desc->port_dir);
		spin_lock_irqsave(&xbus->worker->worker_lock, flags);
		KZFREE(card_desc);
		if(ret)
			break;
	}
	spin_unlock_irqrestore(&worker->worker_lock, flags);
	if(xbus_initialize(xbus) < 0) {
		XBUS_NOTICE(xbus, "Initialization failed. Leave unused. refcount_xbus=%d\n",
			refcount_xbus(xbus));
		goto failed;
	}
	if(!xbus_setstate(xbus, XBUS_STATE_READY)) {
		XBUS_NOTICE(xbus, "Illegal transition. Leave unused. refcount_xbus=%d\n",
			refcount_xbus(xbus));
		goto failed;
	}
	worker->xpds_init_done = 1;
	/*
	 * Now request Astribank to start self_ticking.
	 * This is the last initialization command. So
	 * all others will reach the device before it.
	 */
	xbus_request_sync(xbus, SYNC_MODE_PLL);
	elect_syncer("xbus_poll(end)");	/* FIXME: try to do it later */
out:
	XBUS_DBG(DEVICES, xbus, "Leaving\n");
	wake_up(&worker->wait_for_xpd_initialization);
	XBUS_DBG(DEVICES, xbus, "populate release\n");
	up(&xbus->in_worker);
	return;
failed:
	goto out;
}

int xbus_process_worker(xbus_t *xbus)
{
	struct xbus_workqueue	*worker = xbus->worker;

	if(!xbus) {
		ERR("%s: xbus gone -- skip initialization\n", __FUNCTION__);
		return 0;
	}
	if(down_trylock(&xbus->in_worker)) {
		ERR("%s: xbus is disconnected -- skip initialization\n", __FUNCTION__);
		return 0;
	}
	XBUS_DBG(DEVICES, xbus, "\n");
	/* Initialize the work. (adapt to kernel API changes). */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)
	INIT_WORK(&worker->xpds_init_work, xbus_populate);
#else
	INIT_WORK(&worker->xpds_init_work, xbus_populate, worker);
#endif
	BUG_ON(!xbus);
	/* Now send it */
	if(!queue_work(worker->wq, &worker->xpds_init_work)) {
		XBUS_ERR(xbus, "Failed to queue xpd initialization work\n");
		up(&xbus->in_worker);
		return 0;
	}
	return 1;
}

static void worker_reset(struct xbus_workqueue *worker)
{
	
	struct list_head	*card;
	struct list_head	*next_card;
	unsigned long		flags;
	xbus_t			*xbus = worker->xbus;
	char			*name = (xbus) ? xbus->busname : "detached";

	BUG_ON(!worker);
	DBG(DEVICES, "%s\n", name);
	if(!worker->xpds_init_done) {
		NOTICE("%s: worker(%s)->xpds_init_done=%d\n",
			__FUNCTION__, name, worker->xpds_init_done);
	}
	spin_lock_irqsave(&worker->worker_lock, flags);
	list_for_each_safe(card, next_card, &worker->card_list) {
		struct card_desc_struct	*card_desc = list_entry(card, struct card_desc_struct, card_list);

		BUG_ON(card_desc->magic != CARD_DESC_MAGIC);
		list_del(card);
		KZFREE(card_desc);
	}
	worker->xpds_init_done = 0;
	worker->num_units = 0;
	worker->num_units_initialized = 0;
	spin_unlock_irqrestore(&worker->worker_lock, flags);
	wake_up(&worker->wait_for_xpd_initialization);
}

static void worker_destroy(struct xbus_workqueue *worker)
{
	xbus_t			*xbus;

	if(!worker)
		return;
	worker_reset(worker);
	xbus = worker->xbus;
	if(xbus) {
		XBUS_DBG(DEVICES, xbus, "Waiting for worker to finish...\n");
		down(&xbus->in_worker);
		XBUS_DBG(DEVICES, xbus, "Waiting for worker to finish -- done\n");
		if (worker->wq) {
			XBUS_DBG(DEVICES, xbus, "destroying workqueue...\n");
			flush_workqueue(worker->wq);
			destroy_workqueue(worker->wq);
			worker->wq = NULL;
			XBUS_DBG(DEVICES, xbus, "destroying workqueue -- done\n");
		}
#ifdef CONFIG_PROC_FS
#ifdef	OLD_PROC
		if(xbus->proc_xbus_dir && worker->proc_xbus_waitfor_xpds) {
			XBUS_DBG(PROC, xbus, "Removing proc '%s'\n", PROC_XBUS_WAITFOR_XPDS);
			remove_proc_entry(PROC_XBUS_WAITFOR_XPDS, xbus->proc_xbus_dir);
			worker->proc_xbus_waitfor_xpds = NULL;
		}
#endif
#endif
		XBUS_DBG(DEVICES, xbus, "detach worker\n");
		xbus->worker = NULL;
		KZFREE(worker);
		put_xbus(__FUNCTION__, xbus);	/* got from worker_new() */
	}
}

/*
 * Allocate a worker for the xbus including the nessessary workqueue.
 * May call blocking operations, but only briefly (as we are called
 * from xbus_new() which is called from khubd.
 */
static struct xbus_workqueue *worker_new(int xbusno)
{
	struct xbus_workqueue	*worker;
	xbus_t			*xbus;

	if((xbus = xbus_num(xbusno)) == NULL)
		return NULL;
	get_xbus(__FUNCTION__, xbus);		/* return in worker_destroy() */
	BUG_ON(xbus->busname[0] == '\0');	/* No name? */
	BUG_ON(xbus->worker != NULL);		/* Hmmm... nested workers? */
	XBUS_DBG(DEVICES, xbus, "\n");
	worker = KZALLOC(sizeof(*worker), GFP_KERNEL);
	if(!worker)
		goto err;
	/* poll related variables */
	spin_lock_init(&worker->worker_lock);
	INIT_LIST_HEAD(&worker->card_list);
	init_waitqueue_head(&worker->wait_for_xpd_initialization);
	worker->xbus = xbus;
#ifdef CONFIG_PROC_FS
#ifdef	OLD_PROC
	if(xbus->proc_xbus_dir) {
		worker->proc_xbus_waitfor_xpds = create_proc_read_entry(
				PROC_XBUS_WAITFOR_XPDS, 0444,
				xbus->proc_xbus_dir,
				xbus_read_waitfor_xpds,
				xbus);
		if (!worker->proc_xbus_waitfor_xpds) {
			XBUS_ERR(xbus, "Failed to create proc file '%s'\n", PROC_XBUS_WAITFOR_XPDS);
			goto err;
		}
		worker->proc_xbus_waitfor_xpds->owner = THIS_MODULE;
	}
#endif
#endif
	worker->wq = create_singlethread_workqueue(xbus->busname);
	if(!worker->wq) {
		XBUS_ERR(xbus, "Failed to create worker workqueue.\n");
		goto err;
	}
	return worker;
err:
	worker_destroy(worker);
	return NULL;
}

bool xbus_setstate(xbus_t *xbus, enum xbus_state newstate)
{
	unsigned long	flags;
	bool		ret = 0;
	int		state_flip = 0;

	spin_lock_irqsave(&xbus->transport.state_lock, flags);
	if(newstate == XBUS_STATE(xbus)) {
		XBUS_DBG(DEVICES, xbus, "stay at %s\n",
			xbus_statename(newstate));
		goto out;
	}
	/* Sanity tests */
	switch(newstate) {
		case XBUS_STATE_START:
			goto bad_state;
		case XBUS_STATE_IDLE:
			if(!XBUS_IS(xbus, START) &&
					!XBUS_IS(xbus, DEACTIVATED))
				goto bad_state;
			break;
		case XBUS_STATE_SENT_REQUEST:
			if(!XBUS_IS(xbus, IDLE) &&
					!XBUS_IS(xbus, SENT_REQUEST))
				goto bad_state;
			break;
		case XBUS_STATE_RECVD_DESC:
			if(!XBUS_IS(xbus, SENT_REQUEST))
				goto bad_state;
			break;
		case XBUS_STATE_READY:
			if(!XBUS_IS(xbus, RECVD_DESC))
				goto bad_state;
			state_flip = 1;	/* We are good */
			break;
		case XBUS_STATE_DEACTIVATING:
#if 0
			if(XBUS_IS(xbus, DEACTIVATED) || XBUS_IS(xbus, DISCONNECTED))
				goto bad_state;
#endif
			break;
		case XBUS_STATE_DEACTIVATED:
			if(!XBUS_IS(xbus, DEACTIVATING))
				goto bad_state;
			break;
		case XBUS_STATE_DISCONNECTED:
			break;
		case XBUS_STATE_FAIL:
			if(XBUS_IS(xbus, DEACTIVATING) || XBUS_IS(xbus, DEACTIVATED) || XBUS_IS(xbus, DISCONNECTED))
				goto bad_state;
			break;
		default:
			XBUS_NOTICE(xbus, "%s: unknown state %d\n", __FUNCTION__, newstate);
			goto out;
	}
	/* All good */
	XBUS_DBG(DEVICES, xbus, "%s -> %s\n",
		xbus_statename(XBUS_STATE(xbus)),
		xbus_statename(newstate));
	if(xbus->transport.xbus_state == XBUS_STATE_READY && newstate != XBUS_STATE_READY)
		state_flip = -1;	/* We became bad */
	xbus->transport.xbus_state = newstate;
	ret = 1;
out:
	spin_unlock_irqrestore(&xbus->transport.state_lock, flags);
	/* Should be sent out of spinlocks */
	if(state_flip > 0)
		astribank_uevent_send(xbus, KOBJ_ONLINE);
	else if(state_flip < 0)
		astribank_uevent_send(xbus, KOBJ_OFFLINE);
	return ret;
bad_state:
	XBUS_NOTICE(xbus, "Bad state transition %s -> %s ignored.\n",
		xbus_statename(XBUS_STATE(xbus)),
		xbus_statename(newstate));
	goto out;
}

int xbus_activate(xbus_t *xbus)
{
	XBUS_INFO(xbus, "[%s] Activating\n", xbus->label);
	xpp_drift_init(xbus);
	xbus_set_command_timer(xbus, 1);
	xframe_queue_disable(&xbus->command_queue, 0);
	xbus_setstate(xbus, XBUS_STATE_IDLE);	/* must be done after transport is valid */
	CALL_PROTO(GLOBAL, AB_REQUEST, xbus, NULL);
	/*
	 * Make sure Astribank knows not to send us ticks.
	 */
	xbus_request_sync(xbus, SYNC_MODE_NONE);
	return 0;
}

int xbus_connect(xbus_t *xbus)
{
	struct xbus_ops		*ops;
	struct xbus_workqueue	*worker;

	BUG_ON(!xbus);
	XBUS_DBG(DEVICES, xbus, "\n");
	ops = transportops_get(xbus);
	BUG_ON(!ops);
	worker = xbus->worker;
	BUG_ON(!worker);
	/* Sanity checks */
	BUG_ON(!ops->xframe_send_pcm);
	BUG_ON(!ops->xframe_send_cmd);
	BUG_ON(!ops->alloc_xframe);
	BUG_ON(!ops->free_xframe);
	xbus_activate(xbus);
	return 0;
}

void xbus_deactivate(xbus_t *xbus, bool is_disconnected)
{
	int	i;

	BUG_ON(!xbus);
	XBUS_INFO(xbus, "[%s] Deactivating\n", xbus->label);
	if(!xbus_setstate(xbus, XBUS_STATE_DEACTIVATING))
		return;
	xbus_request_sync(xbus, SYNC_MODE_NONE);	/* no more ticks */
	for(i = 0; i < MAX_XPDS; i++) {
		xpd_t *xpd = xpd_of(xbus, i);
		if(xpd) {
			xpd_unreg_request(xpd);
		}
	}
	elect_syncer("deactivate");
	XBUS_DBG(DEVICES, xbus, "[%s] Waiting for queues\n", xbus->label);
	xbus_command_queue_clean(xbus);
	xbus_command_queue_waitempty(xbus);
	xbus_setstate(xbus, XBUS_STATE_DEACTIVATED);
	worker_reset(xbus->worker);
	xbus_release_xpds(xbus);
	if(!is_disconnected)
		xbus_setstate(xbus, XBUS_STATE_IDLE);
}

void xbus_disconnect(xbus_t *xbus)
{
	XBUS_INFO(xbus, "[%s] Disconnecting\n", xbus->label);
	BUG_ON(!xbus);
	xbus_deactivate(xbus, 1);
	xbus_command_queue_clean(xbus);
	xbus_command_queue_waitempty(xbus);
	tasklet_kill(&xbus->receive_tasklet);
	xframe_queue_clear(&xbus->receive_queue);
	xframe_queue_clear(&xbus->send_pool);
	xframe_queue_clear(&xbus->receive_pool);
	xframe_queue_clear(&xbus->pcm_tospan);
	xbus_setstate(xbus, XBUS_STATE_DISCONNECTED);
	del_timer_sync(&xbus->command_timer);
	transportops_put(xbus);
	transport_destroy(xbus);
	worker_destroy(xbus->worker);
	XBUS_DBG(DEVICES, xbus, "Deactivated refcount_xbus=%d\n",
		refcount_xbus(xbus));
	xbus_sysfs_remove(xbus);	/* Device-Model */
}

static xbus_t *xbus_alloc(void)
{
	unsigned long	flags;
	xbus_t		*xbus;
	int		i;

	xbus = KZALLOC(sizeof(xbus_t), GFP_KERNEL);
	if(!xbus) {
		ERR("%s: out of memory\n", __FUNCTION__);
		return NULL;
	}
	spin_lock_irqsave(&xbuses_lock, flags);
	for(i = 0; i < MAX_BUSES; i++)
		if(xbuses_array[i].xbus == NULL)
			break;
	if(i >= MAX_BUSES) {
		ERR("%s: No free slot for new bus. i=%d\n", __FUNCTION__, i);
		KZFREE(xbus);
		xbus = NULL;
		goto out;
	}
	/* Found empty slot */
	xbus->num = i;
	init_xbus(i, xbus);
out:
	spin_unlock_irqrestore(&xbuses_lock, flags);
	return xbus;
}


void xbus_free(xbus_t *xbus)
{
	unsigned long	flags;
	uint		num;

	if(!xbus)
		return;
	XBUS_DBG(DEVICES, xbus, "Free\n");
	spin_lock_irqsave(&xbuses_lock, flags);
	num = xbus->num;
	BUG_ON(!xbuses_array[num].xbus);
	BUG_ON(xbus != xbuses_array[num].xbus);
	spin_unlock_irqrestore(&xbuses_lock, flags);
#ifdef	XPP_DEBUGFS
	if(xbus->debugfs_dir) {
		if(xbus->debugfs_file) {
			XBUS_DBG(GENERAL, xbus, "Removing debugfs file\n");
			debugfs_remove(xbus->debugfs_file);
		}
		XBUS_DBG(GENERAL, xbus, "Removing debugfs directory\n");
		debugfs_remove(xbus->debugfs_dir);
	}
#endif
#ifdef CONFIG_PROC_FS
	if(xbus->proc_xbus_dir) {
		if(xbus->proc_xbus_summary) {
			XBUS_DBG(PROC, xbus, "Removing proc '%s'\n", PROC_XBUS_SUMMARY);
			remove_proc_entry(PROC_XBUS_SUMMARY, xbus->proc_xbus_dir);
			xbus->proc_xbus_summary = NULL;
		}
#ifdef	PROTOCOL_DEBUG
		if(xbus->proc_xbus_command) {
			XBUS_DBG(PROC, xbus, "Removing proc '%s'\n", PROC_XBUS_COMMAND);
			remove_proc_entry(PROC_XBUS_COMMAND, xbus->proc_xbus_dir);
			xbus->proc_xbus_command = NULL;
		}
#endif
		XBUS_DBG(PROC, xbus, "Removing proc directory\n");
		remove_proc_entry(xbus->busname, xpp_proc_toplevel);
		xbus->proc_xbus_dir = NULL;
	}
#endif
	spin_lock_irqsave(&xbuses_lock, flags);
	XBUS_DBG(DEVICES, xbus, "Going to free...\n");
	init_xbus(num, NULL);
	spin_unlock_irqrestore(&xbuses_lock, flags);
	KZFREE(xbus);
}

xbus_t *xbus_new(struct xbus_ops *ops, ushort max_send_size, void *priv)
{
	int			err;
	xbus_t			*xbus = NULL;

	BUG_ON(!ops);
	xbus = xbus_alloc();
	if(!xbus) {
		ERR("%s: Failed allocating new xbus\n", __FUNCTION__);
		module_put(THIS_MODULE);
		return NULL;
	}
	snprintf(xbus->busname, XBUS_NAMELEN, "XBUS-%02d", xbus->num);
	XBUS_DBG(DEVICES, xbus, "\n");
	transport_init(xbus, ops, max_send_size, priv);
	spin_lock_init(&xbus->lock);
	init_waitqueue_head(&xbus->command_queue_empty);
	init_timer(&xbus->command_timer);
	init_MUTEX(&xbus->in_worker);
	atomic_set(&xbus->pcm_rx_counter, 0);
	xbus->min_tx_sync = INT_MAX;
	xbus->min_rx_sync = INT_MAX;
	
	atomic_set(&xbus->num_xpds, 0);
	xbus->sync_mode = SYNC_MODE_NONE;
	err = xbus_sysfs_create(xbus);
	if(err) {
		XBUS_ERR(xbus, "SYSFS creation failed: %d\n", err);
		goto nobus;
	}
	xbus_reset_counters(xbus);
#ifdef CONFIG_PROC_FS
	XBUS_DBG(PROC, xbus, "Creating xbus proc directory\n");
	xbus->proc_xbus_dir = proc_mkdir(xbus->busname, xpp_proc_toplevel);
	if(!xbus->proc_xbus_dir) {
		XBUS_ERR(xbus, "Failed to create proc directory\n");
		err = -EIO;
		goto nobus;
	}
	xbus->proc_xbus_summary = create_proc_read_entry(PROC_XBUS_SUMMARY,
			0444, xbus->proc_xbus_dir,
			xbus_read_proc,
			(void *)((unsigned long)(xbus->num)));
	if (!xbus->proc_xbus_summary) {
		XBUS_ERR(xbus, "Failed to create proc file '%s'\n", PROC_XBUS_SUMMARY);
		err = -EIO;
		goto nobus;
	}
	xbus->proc_xbus_summary->owner = THIS_MODULE;
#ifdef	PROTOCOL_DEBUG
	xbus->proc_xbus_command = create_proc_entry(PROC_XBUS_COMMAND, 0200, xbus->proc_xbus_dir);
	if (!xbus->proc_xbus_command) {
		XBUS_ERR(xbus, "Failed to create proc file '%s'\n", PROC_XBUS_COMMAND);
		err = -EIO;
		goto nobus;
	}
	xbus->proc_xbus_command->write_proc = proc_xbus_command_write;
	xbus->proc_xbus_command->data = xbus;
	xbus->proc_xbus_command->owner = THIS_MODULE;
#endif
#endif
#ifdef	XPP_DEBUGFS
	xbus->debugfs_dir = debugfs_create_dir(xbus->busname, debugfs_root);
	if(!xbus->debugfs_dir) {
		XBUS_ERR(xbus, "Failed to create debugfs directory\n");
		goto nobus;
	}
	xbus->debugfs_file = debugfs_create_file("dchannel", S_IFREG|S_IRUGO|S_IWUSR, xbus->debugfs_dir, xbus, &debugfs_operations);
	if(!xbus->debugfs_file) {
		XBUS_ERR(xbus, "Failed to create dchannel file\n");
		goto nobus;
	}
#endif
	xframe_queue_init(&xbus->command_queue, 10, 500, "command_queue", xbus);
	xframe_queue_init(&xbus->receive_queue, 10, 50, "receive_queue", xbus);
	xframe_queue_init(&xbus->send_pool, 10, 200, "send_pool", xbus);
	xframe_queue_init(&xbus->receive_pool, 10, 50, "receive_pool", xbus);
	xframe_queue_init(&xbus->pcm_tospan, 5, 10, "pcm_tospan", xbus);
	tasklet_init(&xbus->receive_tasklet, receive_tasklet_func, (unsigned long)xbus);
	/*
	 * Create worker after /proc/XBUS-?? so the directory exists
	 * before /proc/XBUS-??/waitfor_xpds tries to get created.
	 */
	xbus->worker = worker_new(xbus->num);
	if(!xbus->worker) {
		ERR("Failed to allocate worker\n");
		goto nobus;
	}
	return xbus;
nobus:
	xbus_free(xbus);
	return NULL;
}

/*------------------------- Proc handling --------------------------*/

void xbus_reset_counters(xbus_t *xbus)
{
	int	i;

	XBUS_DBG(GENERAL, xbus, "Reseting counters\n");
	for(i = 0; i < XBUS_COUNTER_MAX; i++) {
		xbus->counters[i] = 0;
	}
}

#if CONFIG_PROC_FS

static int xbus_fill_proc_queue(char *p, struct xframe_queue *q)
{
	int	len;

	len = sprintf(p,
			"%-15s: counts %3d, %3d, %3d worst %3d, overflows %3d worst_lag %02ld.%ld ms\n",
				q->name,
				q->steady_state_count,
				q->count,
				q->max_count,
				q->worst_count,
				q->overflows,
				q->worst_lag_usec / 1000,
				q->worst_lag_usec % 1000);
	xframe_queue_clearstats(q);
	return len;
}

static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	xbus_t			*xbus;
	struct xbus_workqueue	*worker;
	unsigned long		flags;
	int			len = 0;
	int			i = (int)((unsigned long)data);

	xbus = xbus_num(i);
	if(!xbus)
		goto out;
	xbus = get_xbus(__FUNCTION__, xbus);	/* until end of xbus_read_proc */
	spin_lock_irqsave(&xbus->lock, flags);
	worker = xbus->worker;

	len += sprintf(page + len, "%s: CONNECTOR=%s LABEL=[%s] STATUS=%s\n",
			xbus->busname,
			xbus->connector,
			xbus->label,
			(!XBUS_IS(xbus, DISCONNECTED)) ? "connected" : "missing"
		      );
	len += xbus_fill_proc_queue(page + len, &xbus->send_pool);
	len += xbus_fill_proc_queue(page + len, &xbus->receive_pool);
	len += xbus_fill_proc_queue(page + len, &xbus->command_queue);
	len += xbus_fill_proc_queue(page + len, &xbus->receive_queue);
	len += xbus_fill_proc_queue(page + len, &xbus->pcm_tospan);
	if(rx_tasklet) {
		len += sprintf(page + len, "\ncpu_rcv_intr:    ");
		for_each_online_cpu(i)
			len += sprintf(page + len, "%5d ", xbus->cpu_rcv_intr[i]);
		len += sprintf(page + len, "\ncpu_rcv_tasklet: ");
		for_each_online_cpu(i)
			len += sprintf(page + len, "%5d ", xbus->cpu_rcv_tasklet[i]);
		len += sprintf(page + len, "\n");
	}
	len += sprintf(page + len, "self_ticking: %d (last_tick at %ld)\n",
			xbus->self_ticking, xbus->ticker.last_sample.tv.tv_sec);
	len += sprintf(page + len, "command_tick: %d\n", xbus->command_tick_counter);
	len += sprintf(page + len, "xbus: pcm_rx_counter = %d, frag = %d\n",
		atomic_read(&xbus->pcm_rx_counter), xbus->xbus_frag_count);
	len += sprintf(page + len, "max_rx_process = %2ld.%ld ms\n",
		xbus->max_rx_process / 1000,
		xbus->max_rx_process % 1000);
	xbus->max_rx_process = 0;
	len += sprintf(page + len, "\nTRANSPORT: max_send_size=%d refcount=%d\n",
			MAX_SEND_SIZE(xbus),
			atomic_read(&xbus->transport.transport_refcount)
			);
	len += sprintf(page + len, "PCM Metrices:\n");
	len += sprintf(page + len, "\tPCM TX: min=%ld  max=%ld\n",
				xbus->min_tx_sync, xbus->max_tx_sync);
	len += sprintf(page + len, "\tPCM RX: min=%ld  max=%ld\n",
				xbus->min_rx_sync, xbus->max_rx_sync);
	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);
	put_xbus(__FUNCTION__, xbus);	/* from xbus_read_proc() */
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 waitfor_xpds(xbus_t *xbus, char *buf)
{
	struct xbus_workqueue	*worker;
	unsigned long		flags;
	int			ret;
	int			len = 0;

	/*
	 * FIXME: worker is created before ?????
	 * So by now it exists and initialized.
	 */
	xbus = get_xbus(__FUNCTION__, xbus);	/* until end of waitfor_xpds_show() */
	if(!xbus)
		return -ENODEV;
	worker = xbus->worker;
	BUG_ON(!worker);
	XBUS_DBG(DEVICES, xbus,
		"Waiting for card initialization of %d XPD's max %d seconds\n",
		worker->num_units,
		INITIALIZATION_TIMEOUT/HZ);
	/*
	 * when polling is finished xbus_poll():
	 *   - Unset worker->is_polling
	 *   - Sets worker->count_xpds_to_initialize.
	 * So we wait until polling is finished (is_polling == 0) and:
	 *   - No poll answers from Astribank (e.g: defective firmware).
	 *   - Or no units to initialize (e.g: mini-AB with only main card).
	 *   - Or we finished initializing all existing units.
	 *   - Or A timeout passed.
	 */
	ret = wait_event_interruptible_timeout(
		worker->wait_for_xpd_initialization,
		!XBUS_IS(xbus, RECVD_DESC) || worker->xpds_init_done,
		INITIALIZATION_TIMEOUT);
	if(ret == 0) {
		XBUS_ERR(xbus, "Card Initialization Timeout\n");
		len = -ETIMEDOUT;
		goto out;
	} else if(ret < 0) {
		XBUS_ERR(xbus, "Card Initialization Interrupted %d\n", ret);
		len = ret;
		goto out;
	} else
		XBUS_DBG(DEVICES, xbus,
			"Finished initialization of %d XPD's in %d seconds.\n",
			worker->num_units_initialized,
			(INITIALIZATION_TIMEOUT - ret)/HZ);
	spin_lock_irqsave(&xbus->lock, flags);
	len += sprintf(buf, "XPDS_READY: %s: %d/%d\n",
			xbus->busname,
			worker->num_units_initialized, worker->num_units);
	spin_unlock_irqrestore(&xbus->lock, flags);
out:
	put_xbus(__FUNCTION__, xbus);	/* from start of waitfor_xpds_show() */
	return len;
}

#ifdef	OLD_PROC
static int xbus_read_waitfor_xpds(char *page, char **start, off_t off, int count, int *eof, void *data)
{
	int			len = 0;
	unsigned long		flags;
	xbus_t			*xbus = data;
	struct xbus_workqueue	*worker;
	int			ret;

	if(!xbus)
		goto out;
	XBUS_NOTICE(xbus, "%s: DEPRECATED: %s[%d] read from /proc interface instead of /sys\n",
		__FUNCTION__, current->comm, current->tgid);
	/* first handle special cases */
	if(!count || off)
		goto out;
	len = waitfor_xpds(xbus, page);
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

#ifdef	PROTOCOL_DEBUG
static int proc_xbus_command_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
{
	char			*buf;
	xbus_t			*xbus = data;
	char			*p;
	byte			*pack_start;
	byte			*q;
	xframe_t		*xframe;
	size_t			len;
	const size_t		max_len = xbus->transport.max_send_size;
	const size_t		max_text = max_len * 3 + 10;

	if(count > max_text) {
		XBUS_ERR(xbus, "%s: line too long (%ld > %zd)\n", __FUNCTION__, count, max_len);
		return -EFBIG;
	}
	/* 3 bytes per hex-digit and space */
	buf = kmalloc(max_text, GFP_KERNEL);
	if(!buf)
		return -ENOMEM;
	if(copy_from_user(buf, buffer, count)) {
		count = -EINVAL;
		goto out;
	}
	buf[count] = '\0';
	XBUS_DBG(GENERAL, xbus, "count=%ld\n", count);
	/*
	 * We replace the content of buf[] from
	 * ascii representation to packet content
	 * as the binary representation is shorter
	 */
	q = pack_start = buf;
	for(p = buf; *p;) {
		int	val;
		char	hexdigit[3];

		while(*p && isspace(*p))	// skip whitespace
			p++;
		if(!(*p))
			break;
		if(!isxdigit(*p)) {
			XBUS_ERR(xbus, "%s: bad hex value ASCII='0x%X' at position %ld\n",
					__FUNCTION__, *p, (long)(p - buf));
			count = -EINVAL;
			goto out;
		}
		hexdigit[0] = *p++;
		hexdigit[1] = '\0';
		hexdigit[2] = '\0';
		if(isxdigit(*p))
			hexdigit[1] = *p++;
		if(sscanf(hexdigit, "%2X", &val) != 1) {
			XBUS_ERR(xbus, "%s: bad hex value '%s' at position %ld\n",
					__FUNCTION__, hexdigit, (long)(p - buf));
			count = -EINVAL;
			goto out;
		}
		*q++ = val;
		XBUS_DBG(GENERAL, xbus, "%3zd> '%s' val=%d\n", q - pack_start, hexdigit, val);
	}
	len = q - pack_start;
	xframe = ALLOC_SEND_XFRAME(xbus);
	if(!xframe) {
		count = -ENOMEM;
		goto out;
	}
	if(len > max_len)
		len = max_len;
	atomic_set(&xframe->frame_len, len);
	memcpy(xframe->packets, pack_start, len);		/* FIXME: checksum? */
	dump_xframe("COMMAND", xbus, xframe, debug);
	send_cmd_frame(xbus, xframe);
out:
	kfree(buf);
	return count;
}
#endif


static int read_proc_xbuses(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_num(i);

		if(xbus) {
			len += sprintf(page + len, "%s: CONNECTOR=%s LABEL=[%s] STATUS=%s\n",
					xbus->busname,
					xbus->connector,
					xbus->label,
					(!XBUS_IS(xbus, DISCONNECTED)) ? "connected" : "missing"
				      );
		}
	}
#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;

}
#endif

static void transport_init(xbus_t *xbus, struct xbus_ops *ops, ushort max_send_size, void *priv)
{
	BUG_ON(!xbus);
	BUG_ON(!ops);
	BUG_ON(!ops->xframe_send_pcm);
	BUG_ON(!ops->xframe_send_cmd);
	BUG_ON(!ops->alloc_xframe);
	BUG_ON(!ops->free_xframe);
	xbus->transport.ops = ops;
	xbus->transport.max_send_size = max_send_size;
	xbus->transport.priv = priv;
	xbus->transport.xbus_state = XBUS_STATE_START;
	spin_lock_init(&xbus->transport.state_lock);
	spin_lock_init(&xbus->transport.lock);
	atomic_set(&xbus->transport.transport_refcount, 0);
	init_waitqueue_head(&xbus->transport.transport_unused);
}

static void transport_destroy(xbus_t *xbus)
{
	int	ret;

	BUG_ON(!xbus);
	XBUS_DBG(DEVICES, xbus, "Waiting... (transport_refcount=%d)\n",
		atomic_read(&xbus->transport.transport_refcount));
	ret = wait_event_interruptible(xbus->transport.transport_unused,
			atomic_read(&xbus->transport.transport_refcount) == 0);
	if(ret)
		XBUS_ERR(xbus, "Waiting for transport_refcount interrupted!!!\n");
	xbus->transport.ops = NULL;
	xbus->transport.priv = NULL;
}

struct xbus_ops *transportops_get(xbus_t *xbus)
{
	struct xbus_ops	*ops;

	BUG_ON(!xbus);
	atomic_inc(&xbus->transport.transport_refcount);
	ops = xbus->transport.ops;
	if(!ops)
		atomic_dec(&xbus->transport.transport_refcount);
	/* fall through */
	return ops;
}

void transportops_put(xbus_t *xbus)
{
	struct xbus_ops	*ops;

	BUG_ON(!xbus);
	ops = xbus->transport.ops;
	BUG_ON(!ops);
	if(atomic_dec_and_test(&xbus->transport.transport_refcount))
		wake_up(&xbus->transport.transport_unused);
}

/*------------------------- Initialization -------------------------*/
static void xbus_core_cleanup(void)
{
	finalize_xbuses_array();
#ifdef	XPP_DEBUGFS
	if(debugfs_root) {
		DBG(GENERAL, "Removing xpp from debugfs\n");
		debugfs_remove(debugfs_root);
	}
#endif
#ifdef CONFIG_PROC_FS
	if(proc_xbuses) {
		DBG(PROC, "Removing " PROC_XBUSES " from proc\n");
		remove_proc_entry(PROC_XBUSES, xpp_proc_toplevel);
		proc_xbuses = NULL;
	}
#endif
}

int __init xbus_core_init(void)
{
	int	ret = 0;

	initialize_xbuses_array();
#ifdef PROTOCOL_DEBUG
	INFO("FEATURE: with PROTOCOL_DEBUG\n");
#endif
#ifdef	XPP_DEBUGFS
	INFO("FEATURE: with XPP_DEBUGFS support\n");
#endif
#ifdef CONFIG_PROC_FS
	proc_xbuses = create_proc_read_entry(PROC_XBUSES, 0444, xpp_proc_toplevel, read_proc_xbuses, NULL);
	if (!proc_xbuses) {
		ERR("Failed to create proc file %s\n", PROC_XBUSES);
		ret = -EFAULT;
		goto err;
	}
	proc_xbuses->owner = THIS_MODULE;
#endif
#ifdef	XPP_DEBUGFS
	DBG(GENERAL, "Creating debugfs xpp root\n");
	debugfs_root = debugfs_create_dir("xpp", NULL);
	if(!debugfs_root) {
		ERR("Failed to create debugfs root\n");
		ret = -EFAULT;
		goto err;
	}
#endif
	if((ret = xpp_driver_init()) < 0)
		goto err;
	return 0;
err:
	xbus_core_cleanup();
	return ret;
}


void xbus_core_shutdown(void)
{
	int		i;

	for(i = 0; i < MAX_BUSES; i++)
		BUG_ON(xbus_num(i));
	xbus_core_cleanup();
	xpp_driver_exit();
}

EXPORT_SYMBOL(xpd_of);
EXPORT_SYMBOL(xpd_byaddr);
EXPORT_SYMBOL(xbus_num);
EXPORT_SYMBOL(xbus_setstate);
EXPORT_SYMBOL(xbus_statename);
EXPORT_SYMBOL(xbus_new);
EXPORT_SYMBOL(xbus_free);
EXPORT_SYMBOL(xbus_connect);
EXPORT_SYMBOL(xbus_activate);
EXPORT_SYMBOL(xbus_deactivate);
EXPORT_SYMBOL(xbus_disconnect);
EXPORT_SYMBOL(xbus_receive_xframe);
EXPORT_SYMBOL(xbus_reset_counters);
EXPORT_SYMBOL(xframe_next_packet);
EXPORT_SYMBOL(dump_xframe);
EXPORT_SYMBOL(send_pcm_frame);
EXPORT_SYMBOL(send_cmd_frame);
EXPORT_SYMBOL(xframe_init);
EXPORT_SYMBOL(transportops_get);
EXPORT_SYMBOL(transportops_put);
EXPORT_SYMBOL(xbus_command_queue_tick);
#ifdef XPP_DEBUGFS
EXPORT_SYMBOL(xbus_log);
#endif
