/*
 * Written by Oron Peled <oron@actcom.co.il>
 * Copyright (C) 2004-2005, 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 "xpd.h"
#include "xproto.h"
#include "xpp_zap.h"
#include <linux/module.h>

static const char rcsid[] = "$Id: xproto.c,v 1.15 2006/04/11 09:05:16 svn-mirror Exp $";

extern	int print_dbg;
static int packet_process(xbus_t *xbus, int xpd_num, xpacket_t *pack);

static const xproto_table_t *xprotocol_tables[XPD_TYPE_NOMODULE];

bool valid_xpd_addr(const xpd_addr_t *addr)
{
	return ((addr->bank_num & ~0x1) == 0) && ((addr->card_id & ~0x3) == 0);
}

int xpd_addr2num(const xpd_addr_t *addr)
{
	BUG_ON(!valid_xpd_addr(addr));
	return addr->bank_num * 4 + addr->card_id;
}

void xpd_set_addr(xpd_addr_t *addr, int xpd_num)
{
	if(xpd_num < 4) {
		addr->card_id = xpd_num;
		addr->bank_num = 0;
	} else {
		addr->card_id = xpd_num % 4;
		addr->bank_num = xpd_num / 4;
	}
}


/*---------------- General Protocol Management ----------------------------*/

const xproto_entry_t *xproto_card_entry(const xproto_table_t *table, byte opcode)
{
	const xproto_entry_t *xe;

	//DBG("\n");
	xe = &table->entries[opcode];
	return (xe->handler != NULL) ? xe : NULL;
}

const xproto_entry_t *xproto_global_entry(byte opcode)
{
	const xproto_entry_t *xe;

	xe = xproto_card_entry(&PROTO_TABLE(GLOBAL), opcode);
	//DBG("opcode=0x%X xe=%p\n", opcode, xe);
	return xe;
}

const xproto_handler_t xproto_global_handler(byte opcode)
{
	return xproto_card_handler(&PROTO_TABLE(GLOBAL), opcode);
}

const xproto_table_t *xproto_table(xpd_type_t cardtype)
{
	if(cardtype >= XPD_TYPE_NOMODULE)
		return NULL;
	return xprotocol_tables[cardtype];
}

const xproto_table_t *get_xproto_table(xpd_type_t cardtype)
{
	const xproto_table_t *xtable;

	if(cardtype >= XPD_TYPE_NOMODULE)
		return NULL;
	xtable = xprotocol_tables[cardtype];
	if(!xtable) {	/* Try to load the relevant module */
		int ret = request_module("xpd-type-%d", cardtype);
		if(ret != 0) {
			NOTICE("%s: Failed to load module for type=%d. exit status=%d.\n",
					__FUNCTION__, cardtype, ret);
			/* Drop through: we may be luck... */
		}
		xtable = xprotocol_tables[cardtype];
	}
	return xtable;
}

const xproto_handler_t xproto_card_handler(const xproto_table_t *table, byte opcode)
{
	const xproto_entry_t *xe;

	//DBG("\n");
	xe = xproto_card_entry(table, opcode);
	return xe->handler;
}

const xproto_entry_t *find_xproto_entry(xpd_t *xpd, byte opcode)
{
	const xproto_entry_t *xe;
	
	xe = xproto_global_entry(opcode);
	// DBG("opcode=0x%X xe=%p\n", opcode, xe);
	if(!xe) {
		const xproto_table_t *xtable;
		
		if(!xpd)
			return NULL;
		xtable = xproto_table(xpd->type);
		if(!xtable)
			return NULL;
		xe = xproto_card_entry(xtable, opcode);
		if(!xe)
			return NULL;
	}
	return xe;
}

const xops_t *get_xops(xpd_type_t xpd_type)
{
	const xproto_table_t	*proto_table;

	if(xpd_type >= XPD_TYPE_NOMODULE)
		return NULL;
	proto_table = xprotocol_tables[xpd_type];
	if(!proto_table)
		return NULL;
	return &proto_table->xops;
}

int packet_receive(xbus_t *xbus, xpacket_t *pack)
{
	int	xpd_num;

	if(!valid_xpd_addr(&pack->content.addr)) {
		static int rate_limit = 0;

		if((rate_limit++ % 5003) < 3)
			dump_packet("bad address", pack, print_dbg);
		xbus->ops->packet_free(xbus, pack);
		return -EPROTO;
	}
	xpd_num = XPD_NUM(pack->content.addr);
#if SOFT_SIMULATOR
	if(xbus->sim[xpd_num].simulated) {
		//dump_packet("packet_receive -> simulate", pack, print_dbg);
		return simulate_xpd(xbus, xpd_num, pack);
	} else
#endif
	{
		//dump_packet("packet_receive -> process", pack, print_dbg);
		return packet_process(xbus, xpd_num, pack);
	}
}

static int packet_process(xbus_t *xbus, int xpd_num, xpacket_t *pack)
{
	byte			op;
	const xproto_entry_t	*xe;
	xproto_handler_t	handler;
	xproto_table_t		*table;
	xpd_t			*xpd;
	int			ret = 0;

	BUG_ON(!pack);
	op = pack->content.opcode;
	xpd_num = XPD_NUM(pack->content.addr);
	xpd = xpd_of(xbus, xpd_num);
	xe = find_xproto_entry(xpd, op);
	/*-------- Validations -----------*/
	if(!xe) {
		ERR("xpp: %s -- bad command op=0x%02X\n", __FUNCTION__, op);
		dump_packet("packet_process -- bad command", pack, print_dbg);
		ret = -EPROTO;
		goto out;
	}
	table = xe->table;
	BUG_ON(!table);
	if(!table->packet_is_valid(pack)) {
		ERR("xpp: %s: wrong size %d for op=0x%02X\n",
					__FUNCTION__, pack->datalen, op);
		dump_packet("packet_process -- wrong size", pack, print_dbg);
		ret = -EPROTO;
		goto out;
	}
	handler = xe->handler;
	BUG_ON(!handler);
	XBUS_COUNTER(xbus, RX_BYTES) += pack->datalen;
	handler(xbus, xpd, xe, pack);
out:
	xbus->ops->packet_free(xbus, pack);
	return ret;
}

#define	VERBOSE_DEBUG		1
#define	ERR_REPORT_LIMIT	20

void dump_packet(const char *msg, xpacket_t *packet, bool print_dbg)
{
	byte	op = packet->content.opcode;

	if(!print_dbg)
		return;
	DBG("%s: @0x%1X%1X OP=0x%02X LEN=%d\n",
			msg,
			packet->content.addr.bank_num,
			packet->content.addr.card_id,
			op,
			(byte)packet->datalen);
#if VERBOSE_DEBUG
	{
		int i;
		byte	*p = packet->content.data;

		for(i = 0; i < packet->datalen; i++) {
			static int limiter = 0;

			if(i >= sizeof(xpacket_raw_t)) {
				if(limiter < ERR_REPORT_LIMIT) {
					ERR("%s: length overflow i=%d > sizeof(xpacket_raw_t)=%d\n",
							__FUNCTION__, i+1, sizeof(xpacket_raw_t));
				} else if(limiter == ERR_REPORT_LIMIT) {
					ERR("%s: error packet #%d... squelsh reports.\n",
							__FUNCTION__, limiter);
				}
				limiter++;
				break;
			}
			DBG("        %2d> %02X\n", i+1, p[i]);
		}
	}
#endif
}

const char *xproto_name(xpd_type_t xpd_type)
{
	const xproto_table_t	*proto_table;

	BUG_ON(xpd_type >= XPD_TYPE(NOMODULE));
	proto_table = xprotocol_tables[xpd_type];
	if(!proto_table)
		return NULL;
	return proto_table->name;
}

#define	CHECK_XOP(f)	\
		if(!(xops)->f) { \
			ERR("%s: missing xmethod %s [%s (%d)]\n", __FUNCTION__, #f, name, type);	\
			return -EINVAL;	\
		}

int xproto_register(const xproto_table_t *proto_table)
{
	int		type;
	const char	*name;
	const xops_t	*xops;
	
	BUG_ON(!proto_table);
	type = proto_table->type;
	name = proto_table->name;
	if(type >= XPD_TYPE(NOMODULE)) {
		NOTICE("%s: Bad xproto type %d\n", __FUNCTION__, type);
		return -EINVAL;
	}
	DBG("%s (%d)\n", name, type);
	if(xprotocol_tables[type])
		NOTICE("%s: overriding registration of %s (%d)\n", __FUNCTION__, name, type);
	xops = &proto_table->xops;
	CHECK_XOP(card_new);
	CHECK_XOP(card_init);
	CHECK_XOP(card_remove);
	CHECK_XOP(card_tick);
	CHECK_XOP(SYNC_SOURCE);
	CHECK_XOP(PCM_WRITE);
	CHECK_XOP(CHAN_ENABLE);
	CHECK_XOP(CHAN_POWER);
	CHECK_XOP(CHAN_CID);
	CHECK_XOP(RING);
	CHECK_XOP(SETHOOK);
	CHECK_XOP(LED);
	CHECK_XOP(RELAY_OUT);

	xprotocol_tables[type] = proto_table;
	return 0;
}

void xproto_unregister(const xproto_table_t *proto_table)
{
	int		type;
	const char	*name;
	
	BUG_ON(!proto_table);
	type = proto_table->type;
	name = proto_table->name;
	DBG("%s (%d)\n", name, type);
	if(type >= XPD_TYPE(NOMODULE)) {
		NOTICE("%s: Bad xproto type %s (%d)\n", __FUNCTION__, name, type);
		return;
	}
	if(!xprotocol_tables[type])
		NOTICE("%s: xproto type %s (%d) is already unregistered\n", __FUNCTION__, name, type);
	xprotocol_tables[type] = NULL;
}

EXPORT_SYMBOL(dump_packet);
EXPORT_SYMBOL(packet_receive);
EXPORT_SYMBOL(valid_xpd_addr);
EXPORT_SYMBOL(xpd_addr2num);
EXPORT_SYMBOL(xpd_set_addr);
EXPORT_SYMBOL(xproto_global_entry);
EXPORT_SYMBOL(xproto_card_entry);
EXPORT_SYMBOL(xproto_name);
EXPORT_SYMBOL(xproto_register);
EXPORT_SYMBOL(xproto_unregister);
