/*-
 * Copyright (c) 1997, 2001 Hellmuth Michaelis. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*---------------------------------------------------------------------------
 *
 *	i4b_l1fsm.c - isdn4bsd layer 1 I.430 state machine
 *	--------------------------------------------------
 *      last edit-date: [Wed Jan 24 09:12:18 2001]
 *
 *---------------------------------------------------------------------------*/

#include <sys/cdefs.h>
__FBSDID("$FreeBSD: src/sys/i4b/layer1/isic/i4b_l1fsm.c,v 1.8 2005/01/06 22:18:20 imp Exp $");

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/socket.h>

#include <net/if.h>

#include <machine/i4b_debug.h>
#include <machine/i4b_ioctl.h>
#include <machine/i4b_trace.h>

#include <i4b/layer1/isic/i4b_isic.h>

#include <i4b/layer1/i4b_l1.h>

#include <i4b/include/i4b_global.h>

#include <i4b/include/i4b_mbuf.h>

#if DO_I4B_DEBUG
static char *state_text[N_STATES] = {
	"F3 Deactivated",
	"F4 Awaiting Signal",
	"F5 Identifying Input",
	"F6 Synchronized",
	"F7 Activated",
	"F8 Lost Framing",
	"Illegal State"	
};

static char *event_text[N_EVENTS] = {
	"EV_PHAR PH_ACT_REQ",
	"EV_T3 Timer 3 expired",
	"EV_INFO0 INFO0 received",
	"EV_RSY Level Detected",
	"EV_INFO2 INFO2 received",
	"EV_INFO48 INFO4 received",
	"EV_INFO410 INFO4 received",
	"EV_DR Deactivate Req",
	"EV_PU Power UP",
	"EV_DIS Disconnected",
	"EV_EI Error Ind",
	"Illegal Event"
};
#endif

/* Function prototypes */

static void timer3_expired (struct l1_softc *sc);
static void T3_start (struct l1_softc *sc);
static void T3_stop (struct l1_softc *sc);
static void F_T3ex (struct l1_softc *sc);
static void timer4_expired (struct l1_softc *sc);
static void T4_start (struct l1_softc *sc);
static void T4_stop (struct l1_softc *sc);
static void F_AI8 (struct l1_softc *sc);
static void F_AI10 (struct l1_softc *sc);
static void F_I01 (struct l1_softc *sc);
static void F_I02 (struct l1_softc *sc);
static void F_I03 (struct l1_softc *sc);
static void F_I2 (struct l1_softc *sc);
static void F_ill (struct l1_softc *sc);
static void F_NULL (struct l1_softc *sc);

/*---------------------------------------------------------------------------*
 *	I.430 Timer T3 expire function
 *---------------------------------------------------------------------------*/	
static void
timer3_expired(struct l1_softc *sc)
{
	if(sc->sc_I430T3)
	{
		NDBGL1(L1_T_ERR, "state = %s", isic_printstate(sc));
		sc->sc_I430T3 = 0;

		/* XXX try some recovery here XXX */

		isic_recover(sc);

		sc->sc_init_tries++;	/* increment retry count */

/*XXX*/		if(sc->sc_init_tries > 4)
		{
			int s = SPLI4B();

			sc->sc_init_tries = 0;
			
			if(sc->sc_obuf2 != NULL)
			{
				i4b_Dfreembuf(sc->sc_obuf2);
				sc->sc_obuf2 = NULL;
			}
			if(sc->sc_obuf != NULL)
			{
				i4b_Dfreembuf(sc->sc_obuf);
				sc->sc_obuf = NULL;
				sc->sc_freeflag = 0;
				sc->sc_op = NULL;
				sc->sc_ol = 0;
			}

			splx(s);

			i4b_l1_mph_status_ind(L0ISICUNIT(sc->sc_unit), STI_NOL1ACC, 0, NULL);
		}
		
		isic_next_state(sc, EV_T3);		
	}
	else
	{
		NDBGL1(L1_T_ERR, "expired without starting it ....");
	}
}

/*---------------------------------------------------------------------------*
 *	I.430 Timer T3 start
 *---------------------------------------------------------------------------*/	
static void
T3_start(struct l1_softc *sc)
{
	NDBGL1(L1_T_MSG, "state = %s", isic_printstate(sc));
	sc->sc_I430T3 = 1;
	sc->sc_T3_callout = timeout((TIMEOUT_FUNC_T)timer3_expired,(struct l1_softc *)sc, 2*hz);
}

/*---------------------------------------------------------------------------*
 *	I.430 Timer T3 stop
 *---------------------------------------------------------------------------*/	
static void
T3_stop(struct l1_softc *sc)
{
	NDBGL1(L1_T_MSG, "state = %s", isic_printstate(sc));

	sc->sc_init_tries = 0;	/* init connect retry count */
	
	if(sc->sc_I430T3)
	{
		sc->sc_I430T3 = 0;
		untimeout((TIMEOUT_FUNC_T)timer3_expired,(struct l1_softc *)sc, sc->sc_T3_callout);
	}
}

/*---------------------------------------------------------------------------*
 *	I.430 Timer T3 expiry
 *---------------------------------------------------------------------------*/	
static void
F_T3ex(struct l1_softc *sc)
{
	NDBGL1(L1_F_MSG, "FSM function F_T3ex executing");
	if(ctrl_desc[sc->sc_unit].protocol != PROTOCOL_D64S)
		i4b_l1_ph_deactivate_ind(L0ISICUNIT(sc->sc_unit));
}

/*---------------------------------------------------------------------------*
 *	Timer T4 expire function
 *---------------------------------------------------------------------------*/	
static void
timer4_expired(struct l1_softc *sc)
{
	if(sc->sc_I430T4)
	{
		NDBGL1(L1_T_MSG, "state = %s", isic_printstate(sc));
		sc->sc_I430T4 = 0;
		i4b_l1_mph_status_ind(L0ISICUNIT(sc->sc_unit), STI_PDEACT, 0, NULL);
	}
	else
	{
		NDBGL1(L1_T_ERR, "expired without starting it ....");
	}
}

/*---------------------------------------------------------------------------*
 *	Timer T4 start
 *---------------------------------------------------------------------------*/	
static void
T4_start(struct l1_softc *sc)
{
	NDBGL1(L1_T_MSG, "state = %s", isic_printstate(sc));
	sc->sc_I430T4 = 1;
	sc->sc_T4_callout = timeout((TIMEOUT_FUNC_T)timer4_expired,(struct l1_softc *)sc, hz);
}

/*---------------------------------------------------------------------------*
 *	Timer T4 stop
 *---------------------------------------------------------------------------*/	
static void
T4_stop(struct l1_softc *sc)
{
	NDBGL1(L1_T_MSG, "state = %s", isic_printstate(sc));

	if(sc->sc_I430T4)
	{
		sc->sc_I430T4 = 0;
		untimeout((TIMEOUT_FUNC_T)timer4_expired,(struct l1_softc *)sc, sc->sc_T4_callout);
	}
}

/*---------------------------------------------------------------------------*
 *	FSM function: received AI8
 *---------------------------------------------------------------------------*/	
static void
F_AI8(struct l1_softc *sc)
{
	T4_stop(sc);

	NDBGL1(L1_F_MSG, "FSM function F_AI8 executing");

	if(ctrl_desc[sc->sc_unit].protocol != PROTOCOL_D64S)
		i4b_l1_ph_activate_ind(L0ISICUNIT(sc->sc_unit));

	T3_stop(sc);

	if(sc->sc_trace & TRACE_I)
	{
		i4b_trace_hdr_t hdr;
		char info = INFO4_8;
		
		hdr.unit = L0ISICUNIT(sc->sc_unit);
		hdr.type = TRC_CH_I;
		hdr.dir = FROM_NT;
		hdr.count = 0;
		MICROTIME(hdr.time);
		i4b_l1_trace_ind(&hdr, 1, &info);
	}
}

/*---------------------------------------------------------------------------*
 *	FSM function: received AI10
 *---------------------------------------------------------------------------*/	
static void
F_AI10(struct l1_softc *sc)
{
	T4_stop(sc);
	
	NDBGL1(L1_F_MSG, "FSM function F_AI10 executing");

	if(ctrl_desc[sc->sc_unit].protocol != PROTOCOL_D64S)
		i4b_l1_ph_activate_ind(L0ISICUNIT(sc->sc_unit));

	T3_stop(sc);

	if(sc->sc_trace & TRACE_I)
	{
		i4b_trace_hdr_t hdr;
		char info = INFO4_10;
		
		hdr.unit = L0ISICUNIT(sc->sc_unit);
		hdr.type = TRC_CH_I;
		hdr.dir = FROM_NT;
		hdr.count = 0;
		MICROTIME(hdr.time);
		i4b_l1_trace_ind(&hdr, 1, &info);
	}
}

/*---------------------------------------------------------------------------*
 *	FSM function: received INFO 0 in states F3 .. F5
 *---------------------------------------------------------------------------*/	
static void
F_I01(struct l1_softc *sc)
{
	NDBGL1(L1_F_MSG, "FSM function F_I01 executing");

	if(sc->sc_trace & TRACE_I)
	{
		i4b_trace_hdr_t hdr;
		char info = INFO0;
		
		hdr.unit = L0ISICUNIT(sc->sc_unit);
		hdr.type = TRC_CH_I;
		hdr.dir = FROM_NT;
		hdr.count = 0;
		MICROTIME(hdr.time);
		i4b_l1_trace_ind(&hdr, 1, &info);
	}
}

/*---------------------------------------------------------------------------*
 *	FSM function: received INFO 0 in state F6
 *---------------------------------------------------------------------------*/	
static void
F_I02(struct l1_softc *sc)
{
	NDBGL1(L1_F_MSG, "FSM function F_I02 executing");

	if(ctrl_desc[sc->sc_unit].protocol != PROTOCOL_D64S)
		i4b_l1_ph_deactivate_ind(L0ISICUNIT(sc->sc_unit));

	if(sc->sc_trace & TRACE_I)
	{
		i4b_trace_hdr_t hdr;
		char info = INFO0;
		
		hdr.unit = L0ISICUNIT(sc->sc_unit);
		hdr.type = TRC_CH_I;
		hdr.dir = FROM_NT;
		hdr.count = 0;
		MICROTIME(hdr.time);
		i4b_l1_trace_ind(&hdr, 1, &info);
	}
}

/*---------------------------------------------------------------------------*
 *	FSM function: received INFO 0 in state F7 or F8
 *---------------------------------------------------------------------------*/	
static void
F_I03(struct l1_softc *sc)
{
	NDBGL1(L1_F_MSG, "FSM function F_I03 executing");

	if(ctrl_desc[sc->sc_unit].protocol != PROTOCOL_D64S)
		i4b_l1_ph_deactivate_ind(L0ISICUNIT(sc->sc_unit));

	T4_start(sc);
	
	if(sc->sc_trace & TRACE_I)
	{
		i4b_trace_hdr_t hdr;
		char info = INFO0;
		
		hdr.unit = L0ISICUNIT(sc->sc_unit);
		hdr.type = TRC_CH_I;
		hdr.dir = FROM_NT;
		hdr.count = 0;
		MICROTIME(hdr.time);
		i4b_l1_trace_ind(&hdr, 1, &info);
	}
}

/*---------------------------------------------------------------------------*
 *	FSM function: activate request
 *---------------------------------------------------------------------------*/	
static void
F_AR(struct l1_softc *sc)
{
	NDBGL1(L1_F_MSG, "FSM function F_AR executing");

	if(sc->sc_trace & TRACE_I)
	{
		i4b_trace_hdr_t hdr;
		char info = INFO1_8;
		
		hdr.unit = L0ISICUNIT(sc->sc_unit);
		hdr.type = TRC_CH_I;
		hdr.dir = FROM_TE;
		hdr.count = 0;
		MICROTIME(hdr.time);
		i4b_l1_trace_ind(&hdr, 1, &info);
	}

	isic_isac_l1_cmd(sc, CMD_AR8);

	T3_start(sc);
}

/*---------------------------------------------------------------------------*
 *	FSM function: received INFO2
 *---------------------------------------------------------------------------*/	
static void
F_I2(struct l1_softc *sc)
{
	NDBGL1(L1_F_MSG, "FSM function F_I2 executing");

	if(sc->sc_trace & TRACE_I)
	{
		i4b_trace_hdr_t hdr;
		char info = INFO2;
		
		hdr.unit = L0ISICUNIT(sc->sc_unit);
		hdr.type = TRC_CH_I;
		hdr.dir = FROM_NT;
		hdr.count = 0;
		MICROTIME(hdr.time);
		i4b_l1_trace_ind(&hdr, 1, &info);
	}		

}

/*---------------------------------------------------------------------------*
 *	illegal state default action
 *---------------------------------------------------------------------------*/	
static void
F_ill(struct l1_softc *sc)
{
	NDBGL1(L1_F_ERR, "FSM function F_ill executing");
}

/*---------------------------------------------------------------------------*
 *	No action
 *---------------------------------------------------------------------------*/	
static void
F_NULL(struct l1_softc *sc)
{
	NDBGL1(L1_F_MSG, "FSM function F_NULL executing");
}


/*---------------------------------------------------------------------------*
 *	layer 1 state transition table
 *---------------------------------------------------------------------------*/	
struct isic_state_tab {
	void (*func) (struct l1_softc *sc);	/* function to execute */
	int newstate;				/* next state */
} isic_state_tab[N_EVENTS][N_STATES] = {

/* STATE:	F3			F4			F5			F6			F7			F8			ILLEGAL STATE     */
/* -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------*/
/* EV_PHAR x*/	{{F_AR,   ST_F4},	{F_NULL, ST_F4},	{F_NULL, ST_F5},	{F_NULL, ST_F6},	{F_ill,  ST_ILL},	{F_NULL, ST_F8},	{F_ill, ST_ILL}},
/* EV_T3   x*/	{{F_NULL, ST_F3},	{F_T3ex, ST_F3},	{F_T3ex, ST_F3},	{F_T3ex, ST_F3},	{F_NULL, ST_F7},	{F_NULL, ST_F8},	{F_ill, ST_ILL}},
/* EV_INFO0 */	{{F_I01,  ST_F3},	{F_I01,  ST_F4},	{F_I01,  ST_F5},	{F_I02,  ST_F3},	{F_I03,  ST_F3},	{F_I03,  ST_F3},	{F_ill, ST_ILL}},
/* EV_RSY  x*/	{{F_NULL, ST_F3},	{F_NULL, ST_F5},	{F_NULL, ST_F5}, 	{F_NULL, ST_F8},	{F_NULL, ST_F8},	{F_NULL, ST_F8},	{F_ill, ST_ILL}},
/* EV_INFO2 */	{{F_I2,   ST_F6},	{F_I2,   ST_F6},	{F_I2,   ST_F6},	{F_I2,   ST_F6},	{F_I2,   ST_F6},	{F_I2,   ST_F6},	{F_ill, ST_ILL}},
/* EV_INFO48*/	{{F_AI8,  ST_F7},	{F_AI8,  ST_F7},	{F_AI8,  ST_F7},	{F_AI8,  ST_F7},	{F_NULL, ST_F7},	{F_AI8,  ST_F7},	{F_ill, ST_ILL}},
/* EV_INFO41*/	{{F_AI10, ST_F7},	{F_AI10, ST_F7},	{F_AI10, ST_F7},	{F_AI10, ST_F7},	{F_NULL, ST_F7},	{F_AI10, ST_F7},	{F_ill, ST_ILL}},
/* EV_DR    */	{{F_NULL, ST_F3},	{F_NULL, ST_F4},	{F_NULL, ST_F5},	{F_NULL, ST_F6},	{F_NULL, ST_F7},	{F_NULL, ST_F8},	{F_ill, ST_ILL}},
/* EV_PU    */	{{F_NULL, ST_F3},	{F_NULL, ST_F4},	{F_NULL, ST_F5},	{F_NULL, ST_F6},	{F_NULL, ST_F7},	{F_NULL, ST_F8},	{F_ill, ST_ILL}},
/* EV_DIS   */	{{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill, ST_ILL}},
/* EV_EI    */	{{F_NULL, ST_F3},	{F_NULL, ST_F3},	{F_NULL, ST_F3},	{F_NULL, ST_F3},	{F_NULL, ST_F3},	{F_NULL, ST_F3},	{F_ill, ST_ILL}},
/* EV_ILL   */	{{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill,  ST_ILL},	{F_ill, ST_ILL}}
};

/*---------------------------------------------------------------------------*
 *	event handler
 *---------------------------------------------------------------------------*/	
void
isic_next_state(struct l1_softc *sc, int event)
{
	int currstate, newstate;

	if(event >= N_EVENTS)
		panic("i4b_l1fsm.c: event >= N_EVENTS\n");

	currstate = sc->sc_I430state;

	if(currstate >= N_STATES)
		panic("i4b_l1fsm.c: currstate >= N_STATES\n");	

	newstate = isic_state_tab[event][currstate].newstate;

	if(newstate >= N_STATES)
		panic("i4b_l1fsm.c: newstate >= N_STATES\n");	
	
	NDBGL1(L1_F_MSG, "FSM event [%s]: [%s => %s]", event_text[event],
                                           state_text[currstate],
                                           state_text[newstate]);

        (*isic_state_tab[event][currstate].func)(sc);

	if(newstate == ST_ILL)
	{
		newstate = ST_F3;
		NDBGL1(L1_F_ERR, "FSM Illegal State ERROR, oldstate = %s, newstate = %s, event = %s!",
					state_text[currstate],
					state_text[newstate],
					event_text[event]);
	}

	sc->sc_I430state = newstate;
}

#if DO_I4B_DEBUG
/*---------------------------------------------------------------------------*
 *	return pointer to current state description
 *---------------------------------------------------------------------------*/	
char *
isic_printstate(struct l1_softc *sc)
{
	return((char *) state_text[sc->sc_I430state]);
}
#endif
