/*
 * 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/device.h>
#include <linux/delay.h>	/* for mdelay() to debug */
#include "xpd.h"
#include "xpp_zap.h"
#include "xbus-core.h"
#include "zap_debug.h"

static const char rcsid[] = "$Id: xbus-core.c 1557 2006-11-06 18:12:16Z tzafrir $";

/* Defines */
#define	POLL_TIMEOUT		(MAX_XPDS)	/* in jiffies */
#define	INITIALIZATION_TIMEOUT	(40*HZ)		/* in jiffies */
#define	PROC_XBUSES		"xbuses"
#define	PROC_XBUS_SUMMARY	"summary"
#define	PROC_XBUS_WAITFOR_XPDS	"waitfor_xpds"

#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 print_dbg;

/* Forward declarations */
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
#define	DEVICE_ATTR_FUNC(name,dev,buf)	\
		ssize_t name(struct device *dev, struct device_attribute *attr, char *buf)
#else
#define	DEVICE_ATTR_FUNC(name,dev,buf)	\
		ssize_t name(struct device *dev, char *buf)
#endif

static DEVICE_ATTR_FUNC(connector_show, dev, buf);
static DEVICE_ATTR_FUNC(status_show, dev, buf);

static int xbus_poll(void *data);
static void xbus_release(struct device *dev);
static int xbus_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data);
static int xbus_read_waitfor_xpds(char *page, char **start, off_t off, int count, int *eof, void *data);

/* Data structures */
struct workqueue_struct		*xpp_worker = NULL;
static spinlock_t		xbuses_lock = SPIN_LOCK_UNLOCKED;
static xbus_t			*xbuses_array[MAX_BUSES] = {};
static int			bus_count = 0;
static struct proc_dir_entry	*proc_xbuses = NULL;

static	DEVICE_ATTR(connector, S_IRUGO, connector_show, NULL);
static	DEVICE_ATTR(status, S_IRUGO, status_show, NULL);

/*------------------------- Packet Handling ------------------------*/
static kmem_cache_t	*packet_cache = NULL;
static atomic_t	xpacket_count = ATOMIC_INIT(0);

/**
 * 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	*xbus_packet_new(xbus_t *xbus, gfp_t 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 xbus_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));
}


/*------------------------- 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];
}

int xbus_register_xpd(xbus_t *xbus, xpd_t *xpd)
{
	unsigned int	xpd_num = xpd->id;
	unsigned long	flags;
	int		ret = 0;

	spin_lock_irqsave(&xbus->lock, flags);
	if(!VALID_XPD_NUM(xpd_num)) {
		ERR("%s: Bad xpd_num = %d\n", xbus->busname, xpd_num);
		ret = -EINVAL;
		goto out;
	}
	if(xbus->xpds[xpd_num] != NULL) {
		xpd_t	*other = xbus->xpds[xpd_num];

		ERR("%s: xpd_num=%d is occupied by %p (%s)\n",
				xbus->busname, xpd_num, other, other->xpdname);
		ret = -EINVAL;
		goto out;
	}
	xbus->xpds[xpd_num] = xpd;
	xbus->num_xpds++;
out:
	spin_unlock_irqrestore(&xbus->lock, flags);
	return ret;
}

int xbus_unregister_xpd(xbus_t *xbus, xpd_t *xpd)
{
	unsigned int	xpd_num = xpd->id;
	unsigned long	flags;
	int		ret = 0;

	spin_lock_irqsave(&xbus->lock, flags);
	if(!VALID_XPD_NUM(xpd_num)) {
		ERR("%s: Bad xpd_num = %d\n", xbus->busname, xpd_num);
		ret = -EINVAL;
		goto out;
	}
	if(xbus->xpds[xpd_num] != xpd) {
		xpd_t	*other = xbus->xpds[xpd_num];

		ERR("%s: xpd_num=%d is occupied by %p (%s)\n",
				xbus->busname, xpd_num, other, other->xpdname);
		ret = -EINVAL;
		goto out;
	}
	xbus->xpds[xpd_num] = NULL;
	xbus->num_xpds--;
	xpd->xbus = NULL;
out:
	spin_unlock_irqrestore(&xbus->lock, flags);
	return ret;
}

/*
 * This must be called from synchronous (non-interrupt) context
 * it returns only when all XPD's on the bus are detected and
 * initialized.
 */
static int xbus_poll(void *data)
{
	int			id;
	int			ret = 0;
	unsigned long		flags;
	struct list_head	*card;
	struct list_head	*next_card;
	struct list_head	removal_list;
	struct list_head	additions_list;
	int			count_removed;
	int			count_added;
	int			xpd_num;
	xbus_t			*xbus = data;

	if(!down_read_trylock(&xbus->in_use)) {
		ERR("%s is being removed...\n", xbus->busname);
		return -EBUSY;
	}
	mdelay(2);	/* roundtrip for older polls */
	spin_lock_irqsave(&xbus->lock, flags);
	DBG("%s\n", xbus->busname);

	/*
	 * Send out the polls
	 */
	for(id = 0; id < MAX_XPDS; id++) {
		if(!xbus->hardware_exists)
			break;
		// DBG("  Polling slot %d %s\n", id, xbus->busname);
		spin_unlock_irqrestore(&xbus->lock, flags);
		ret = CALL_PROTO(GLOBAL, DESC_REQ, xbus, NULL, id);
		spin_lock_irqsave(&xbus->lock, flags);
		if(ret < 0) {
			ERR("xpp: %s: Failed sending DESC_REQ to XPD #%d\n", __FUNCTION__, id);
			goto out;
		}
		mdelay(1);	/* FIXME: debugging for Dima */
	}
	spin_unlock_irqrestore(&xbus->lock, flags);
	/*
	 * Wait for replies
	 */
	DBG("%s: Polled %d XPD's. Waiting for replies max %d jiffies\n", xbus->busname, MAX_XPDS, POLL_TIMEOUT);
	ret = wait_event_interruptible_timeout(xbus->wait_for_polls, atomic_read(&xbus->count_poll_answers) >= MAX_XPDS, POLL_TIMEOUT);
	if(ret == 0) {
		ERR("%s: Poll timeout\n", xbus->busname);
		goto out;
	} else if(ret < 0) {
		ERR("%s: Poll interrupted %d\n", xbus->busname, ret);
		goto out;
	}
	DBG("%s: Poll finished in %d jiffies. Start processing.\n", xbus->busname, POLL_TIMEOUT - ret);
	/*
	 * Build removals/additions lists
	 */
	spin_lock_irqsave(&xbus->lock, flags);
	INIT_LIST_HEAD(&removal_list);
	INIT_LIST_HEAD(&additions_list);
	count_removed = 0;
	count_added = 0;
	list_for_each_safe(card, next_card, &xbus->poll_results) {
		struct card_desc_struct	*card_desc = list_entry(card, struct card_desc_struct, card_list);
		byte			type = card_desc->type;
		xpd_t			*xpd;

		BUG_ON(card_desc->magic != CARD_DESC_MAGIC);
		xpd_num = xpd_addr2num(&card_desc->xpd_addr);
		xpd = xpd_of(xbus, xpd_num);

		if(xpd && type == XPD_TYPE_NOMODULE) {		/* card removal */
			list_move_tail(card, &removal_list);
			count_removed++;
		} else if(!xpd && type != XPD_TYPE_NOMODULE) {	/* card detection */
			list_move_tail(card, &additions_list);
			count_added++;
		} else {					/* same same */
			list_del(card);
			kfree(card_desc);
		}
	}
	atomic_set(&xbus->count_xpds_to_initialize, count_added);
	spin_unlock_irqrestore(&xbus->lock, flags);
	INFO("%s: Poll results: removals=%d additions=%d\n", xbus->busname, count_removed, count_added);
	/*
	 * Process removals first
	 */
	list_for_each_safe(card, next_card, &removal_list) {
		struct card_desc_struct	*card_desc = list_entry(card, struct card_desc_struct, card_list);
		xpd_t			*xpd;

		list_del(card);
		xpd_num = xpd_addr2num(&card_desc->xpd_addr);
		xpd = xpd_of(xbus, xpd_num);
		if(xpd)
			xpd_disconnect(xpd);
		kfree(card);
	}
	/*
	 * Now process additions
	 */
	list_for_each_safe(card, next_card, &additions_list) {
		struct card_desc_struct	*card_desc = list_entry(card, struct card_desc_struct, card_list);

		list_del(card);
		/* FIXME: card_detected() should have a return value for count_xpds_initialized */
		card_detected(card_desc);
		atomic_inc(&xbus->count_xpds_initialized);
	}
	wake_up(&xbus->wait_for_xpd_initialization);
out:
	up_read(&xbus->in_use);
	return ret;
}


void xbus_activate(xbus_t *xbus)
{
	xbus_ops_t *ops;

	BUG_ON(!xbus);
	ops = xbus->ops;
	BUG_ON(!ops);
	/* Sanity checks */
	BUG_ON(!ops->packet_send);
	BUG_ON(!ops->packet_new || !ops->packet_free);
	xbus->hardware_exists = 1;
	DBG("Activating: %s\n", xbus->busname);
	/* Poll it */
	INIT_WORK(&xbus->xpds_init_work, (void (*)(void *))xbus_poll, (void *)xbus);
	if(!queue_work(xpp_worker, &xbus->xpds_init_work)) {
		ERR("Failed to queue xpd initialization work\n");
		/* FIXME: need to return error */
	}
}

void xbus_disconnect(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_disconnect(xpd);
	}
	DBG("%s (deactivated)\n", xbus->busname);
	if(xbus->open_counter == 0) {
		xbus_remove(xbus);
	}
}

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

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


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

	if(!xbus)
		return;
	spin_lock_irqsave(&xbuses_lock, flags);
	BUG_ON(!xbus_of(xbus->num));
	BUG_ON(xbus != xbus_of(xbus->num));
	xbuses_array[xbus->num] = NULL;
	bus_count--;
	spin_unlock_irqrestore(&xbuses_lock, flags);
#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;
		}
		if(xbus->proc_xbus_waitfor_xpds) {
			DBG("Removing proc '%s' for %s\n", PROC_XBUS_WAITFOR_XPDS, xbus->busname);
			remove_proc_entry(PROC_XBUS_WAITFOR_XPDS, xbus->proc_xbus_dir);
			xbus->proc_xbus_waitfor_xpds = NULL;
		}
#ifdef	PROTOCOL_DEBUG
		if(xbus->proc_xbus_command) {
			DBG("Removing proc '%s' for %s\n", PROC_XBUS_COMMAND, xbus->busname);
			remove_proc_entry(PROC_XBUS_COMMAND, xbus->proc_xbus_dir);
			xbus->proc_xbus_command = NULL;
		}
#endif
		DBG("Removing proc directory %s\n", xbus->busname);
		remove_proc_entry(xbus->busname, xpp_proc_toplevel);
		xbus->proc_xbus_dir = NULL;
	}
#endif
	device_remove_file(&xbus->the_bus, &dev_attr_status);
	device_remove_file(&xbus->the_bus, &dev_attr_connector);
	device_unregister(&xbus->the_bus);
	kfree(xbus);
}

static void xbus_release(struct device *dev)
{
	xbus_t	*xbus;

	BUG_ON(!dev);
	xbus = dev->driver_data;
	DBG("%s\n", xbus->busname);
}


xbus_t *xbus_new(xbus_ops_t *ops)
{
	int		err;
	xbus_t		*xbus = NULL;

	BUG_ON(!ops);
	xbus = xbus_alloc();
	if(!xbus)
		return NULL;

	/* 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);

	/* poll related variables */
	atomic_set(&xbus->count_poll_answers, 0);
	atomic_set(&xbus->count_xpds_to_initialize, 0);
	atomic_set(&xbus->count_xpds_initialized, 0);
	INIT_LIST_HEAD(&xbus->poll_results);
	init_waitqueue_head(&xbus->wait_for_polls);
	init_waitqueue_head(&xbus->wait_for_xpd_initialization);
	xbus->num_xpds = 0;

	init_rwsem(&xbus->in_use);
	xbus_reset_counters(xbus);

	/* Device-Model */
	snprintf(xbus->the_bus.bus_id, BUS_ID_SIZE, "xbus-%d", xbus->num);
	xbus->the_bus.driver_data = xbus;
	xbus->the_bus.release = xbus_release;

	err = device_register(&xbus->the_bus);
	if(err) {
		ERR("%s: device_register failed: %d\n", __FUNCTION__, err);
		goto nobus;
	}
	err = device_create_file(&xbus->the_bus, &dev_attr_connector);
	if(err) {
		ERR("%s: device_create_file failed: %d\n", __FUNCTION__, err);
		goto nobus;
	}
	err = device_create_file(&xbus->the_bus, &dev_attr_status);
	if(err) {
		ERR("%s: device_create_file failed: %d\n", __FUNCTION__, err);
		goto nobus;
	}

#ifdef CONFIG_PROC_FS
	DBG("Creating xbus proc directory %s.\n",xbus->busname);
	xbus->proc_xbus_dir = proc_mkdir(xbus->busname, xpp_proc_toplevel);
	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;
	}
	xbus->proc_xbus_summary->owner = THIS_MODULE;
	xbus->proc_xbus_waitfor_xpds = create_proc_read_entry(PROC_XBUS_WAITFOR_XPDS, 0444, xbus->proc_xbus_dir,
			xbus_read_waitfor_xpds, xbus);
	if (!xbus->proc_xbus_waitfor_xpds) {
		ERR("Failed to create '%s' proc file for xbus %s\n", PROC_XBUS_WAITFOR_XPDS, xbus->busname);
		err = -EIO;
		goto nobus;
	}
	xbus->proc_xbus_waitfor_xpds->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) {
		ERR("Failed to create '%s' proc file for xbus %s\n", PROC_XBUS_COMMAND, xbus->busname);
		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
	/* Sanity checks */
	if(!ops->packet_send) {
		ERR("%s: missing mandatory handler: packet_send\n", __FUNCTION__);
		goto nobus;
	}
	if(!ops->packet_new || !ops->packet_free) {
		NOTICE("%s: Using default packet allocators\n", __FUNCTION__);
		ops->packet_new = xbus_packet_new;
		ops->packet_free = xbus_packet_free;
	}

	xbus->ops = ops;
	return xbus;
nobus:
	xbus_free(xbus);
	return NULL;
}

void xbus_remove(xbus_t *xbus)
{
	int	i;
	int	ret;

	BUG_ON(!xbus);
	if(!xbus_of(xbus->num)) {
		DBG("XBUS #%d was already removed. Skip.\n", xbus->num);
		return;
	}
	DBG("%s\n", xbus->busname);

	/* Block until no one use */
	down_write(&xbus->in_use);

	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) {
			if(xpd->id != i) {
				ERR("%s: BUG: xpd->id=%d != i=%d\n", __FUNCTION__, xpd->id, i);
				continue;
			}
			DBG("  Removing xpd id=%d\n", xpd->id);
			xpd_remove(xpd);
		}
		xbus->xpds[i] = NULL;
	}
	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_free(xbus);
}

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

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;
	}
}

#if CONFIG_PROC_FS
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, "POLLS: %d/%d\n",
			atomic_read(&xbus->count_poll_answers), MAX_XPDS);
	len += sprintf(page + len, "XPDS_READY: %d/%d\n",
			atomic_read(&xbus->count_xpds_initialized),
			atomic_read(&xbus->count_xpds_to_initialize));
	len += sprintf(page + len, "\nmax_packet_size=%d open_counter=%d packet_count=%d\n",
			xbus->max_packet_size,
			xbus->open_counter,
			atomic_read(&xbus->packet_counter)
		      );
	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;

}

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;
	int		ret;

	if(!xbus)
		goto out;
	DBG("%s: Waiting for card initialization of %d XPD's max %d seconds\n", xbus->busname, MAX_XPDS, INITIALIZATION_TIMEOUT/HZ);
	ret = wait_event_interruptible_timeout(xbus->wait_for_xpd_initialization,
			atomic_read(&xbus->count_xpds_initialized) >= atomic_read(&xbus->count_xpds_to_initialize),
			INITIALIZATION_TIMEOUT);
	if(ret == 0) {
		ERR("%s: Card Initialization Timeout\n", xbus->busname);
		return ret;
	} else if(ret < 0) {
		ERR("%s: Card Initialization Interrupted %d\n", xbus->busname, ret);
		return ret;
	}
	DBG("%s: Finished initialization of %d XPD's in %d seconds.\n", xbus->busname, MAX_XPDS, (INITIALIZATION_TIMEOUT - ret)/HZ);
	spin_lock_irqsave(&xbus->lock, flags);
	len += sprintf(page + len, "XPDS_READY: %d/%d\n",
			atomic_read(&xbus->count_xpds_initialized),
			atomic_read(&xbus->count_xpds_to_initialize));
	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;

}

#ifdef	PROTOCOL_DEBUG
static int proc_xbus_command_write(struct file *file, const char __user *buffer, unsigned long count, void *data)
{
	char			buf[MAX_PROC_WRITE];
	xbus_t			*xbus = data;
	xpacket_t		*pack;
	char			*p;
	byte			*pack_contents;
	byte			*q;

	if(count >= MAX_PROC_WRITE) {
		ERR("%s: line too long\n", __FUNCTION__);
		return -EFBIG;
	}
	if(copy_from_user(buf, buffer, count))
		return -EINVAL;
	buf[count] = '\0';
	pack = xbus->ops->packet_new(xbus, GFP_KERNEL);
	if(!pack)
		return -ENOMEM;
	q = pack_contents = (byte *)&pack->content;
	for(p = buf; *p;) {
		int	val;
		char	hexdigit[3];

		while(*p && isspace(*p))	// skip whitespace
			p++;
		if(!(*p))
			break;
		if(!isxdigit(*p)) {
			ERR("%s: %s: bad hex value ASCII='0x%X' at position %d\n",
					__FUNCTION__, xbus->busname, *p, p - buf);
			goto err;
		}
		hexdigit[0] = *p++;
		hexdigit[1] = '\0';
		hexdigit[2] = '\0';
		if(isxdigit(*p))
			hexdigit[1] = *p++;
		if(sscanf(hexdigit, "%2X", &val) != 1) {
			ERR("%s: %s: bad hex value '%s' at position %d\n",
					__FUNCTION__, xbus->busname, hexdigit, p - buf);
			goto err;
		}
		*q++ = val;
		// DBG("%s: %s: '%s' val=%d\n", __FUNCTION__, xbus->busname, hexdigit, val);
	}
	pack->datalen = q - pack_contents -
			sizeof(pack->content.opcode) - sizeof(pack->content.addr);
	packet_send(xbus, pack);
	return count;
err:
	xbus->ops->packet_free(xbus, pack);
	return -EINVAL;
}
#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_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;

}
#endif

/*------------------------- Initialization -------------------------*/

static DEVICE_ATTR_FUNC(connector_show, dev, buf)
{
	xbus_t	*xbus;
	int	ret;

	xbus = dev->driver_data;
	ret = snprintf(buf, PAGE_SIZE, "%s\n", xbus->busdesc);
	return ret;
}

static DEVICE_ATTR_FUNC(status_show, dev, buf)
{
	xbus_t	*xbus;
	int	ret;

	xbus = dev->driver_data;
	ret = snprintf(buf, PAGE_SIZE, "%s\n", (xbus->hardware_exists)?"connected":"missing");
	return ret;
}

static int xbus_match(struct device *dev, struct device_driver *driver)
{
	DBG("dev->bus_id = %s, driver->name = %s\n", dev->bus_id, driver->name);
	return strncmp(dev->bus_id, driver->name, strlen(driver->name)) == 0;
}

#if 0
/* Hotplug replaced with uevent in 2.6.16 */
static int xbus_hotplug(struct device *device, char **envp, int envnum, char *buff, int bufsize)
{
	envp[0] = buff;
	if(snprintf(buff, bufsize, "XBUS_VERSION=%s", revision) >= bufsize)
		return -ENOMEM;
	envp[1] = NULL;
	return 0;
}
#endif

struct bus_type	xbus_bus_type = {
	.name = "xbus",
	.match = xbus_match,
/* FIXME: Hotplug replaced with uevent in 2.6.16 */
#if 0
	.hotplug = xbus_hotplug,
#endif
};

static void xbus_core_cleanup(void)
{
	if (xpp_worker) {
		flush_workqueue(xpp_worker);
		destroy_workqueue(xpp_worker);
		xpp_worker = NULL;
	}
#ifdef CONFIG_PROC_FS
	if(proc_xbuses) {
		DBG("Removing " PROC_XBUSES " from proc\n");
		remove_proc_entry(PROC_XBUSES, xpp_proc_toplevel);
		proc_xbuses = NULL;
	}
#endif
	if(packet_cache)
		kmem_cache_destroy(packet_cache);
}

int __init xbus_core_init(void)
{
	int	ret;

#ifdef PROTOCOL_DEBUG
	INFO("FEATURE: %s with PROTOCOL_DEBUG\n", THIS_MODULE->name);
#endif
	packet_cache = kmem_cache_create("xpp_packets",
			sizeof(xpacket_t),
			0, 0,
			NULL, NULL);
	if(!packet_cache) {
		return -ENOMEM;
	}
	xpp_worker = create_singlethread_workqueue("xppworker");
	if(!xpp_worker) {
		ERR("Failed to create card detector workqueue.\n");
		xbus_core_cleanup();
		return -ENOMEM;
	}
#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);
		xbus_core_cleanup();
		return -EFAULT;
	}
	proc_xbuses->owner = THIS_MODULE;
#endif
	ret = bus_register(&xbus_bus_type);
	if(ret) {
		ERR("%s: bus_register failed. Error number %d", __FUNCTION__, ret);
		xbus_core_cleanup();
		return ret;
	}
	return 0;
}


void __exit xbus_core_shutdown(void)
{
	int		i;

	for(i = 0; i < MAX_BUSES; i++) {
		xbus_t	*xbus = xbus_of(i);
		if(xbus)
			xbus_remove(xbus);
	}
	BUG_ON(bus_count);
	bus_unregister(&xbus_bus_type);
	xbus_core_cleanup();
}

EXPORT_SYMBOL(xpd_of);
EXPORT_SYMBOL(xbus_new);
EXPORT_SYMBOL(xbus_remove);
EXPORT_SYMBOL(xbus_activate);
EXPORT_SYMBOL(xbus_disconnect);
EXPORT_SYMBOL(xbus_reset_counters);
