/******************************************************************************
 *
 * Name:	skrlmt.c
 * Project:	SysKonnect SK-9Dxx Gigabit Ethernet
 * Version:	$Revision: 1.2 $
 * Date:	$Date: 2001/08/08 13:39:15 $
 * Purpose:	Manage links on SysKonnect SK-9Dxx Gigabit Ethernet adapters.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *	(C)Copyright 2001 SysKonnect GmbH.
 *
 *	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.
 *
 *	The information in this file is provided "AS IS" without warranty.
 *
 ******************************************************************************/

/******************************************************************************
 *
 * History:
 *
 *	$Log: skrlmt.c,v $
 *	Revision 1.2  2001/08/08 13:39:15  rschmidt
 *	Moved all defines and structure definitions into header file
 *	
 *	Revision 1.1  2001/06/05 08:28:25  rassmann
 *	First public version.
 *	
 *
 ******************************************************************************/

/******************************************************************************
 *
 * Description:
 *
 * This module contains code for Link Management of
 * SysKonnect SK-9Dxx Gigabit Ethernet adapters.
 *
 * Include File Hierarchy:
 *
 *	"h/skdrv1st.h"
 *	...
 *	"h/sktypes.h"
 *	"h/skqueue.h"
 *	"h/sktimer.h"
 *	"h/skaddr.h"
 *	"h/skrlmt.h"
 *	...
 *	"h/skdrv2nd.h"
 *
 ******************************************************************************/

#ifndef	lint
static const char SysKonnectFileId[] =
	"@(#) $Id: skrlmt.c,v 1.2 2001/08/08 13:39:15 rschmidt Exp $ (C) SysKonnect.";
#endif	/* !defined(lint) */

#define __SKRLMT_C

#ifdef __cplusplus
#error C++ is not yet supported.
extern "C" {
#endif	/* cplusplus */

#include "h/skdrv1st.h"
#include "h/sktypes.h"
#include "h/skqueue.h"
#include "h/sktimer.h"
#include "h/skaddr.h"
#include "h/skrlmt.h"
#include "h/skdrv2nd.h"


/* global variables ***********************************************************/

SK_MAC_ADDR	SkRlmtMcAddr =	{{0x01,  0x00,  0x5A,  0x52,  0x4C,  0x4D}};
SK_MAC_ADDR	BridgeMcAddr =	{{0x01,  0x80,  0xC2,  0x00,  0x00,  0x00}};
SK_MAC_ADDR	BcAddr = 		{{0xFF,  0xFF,  0xFF,  0xFF,  0xFF,  0xFF}};

/* local variables ************************************************************/

/* None. */

/* functions ******************************************************************/

/******************************************************************************
 *
 *	SkRlmtInit - initialize data, set state to init
 *
 * Description:
 *
 *	SK_INIT_DATA
 *	============
 *
 *	This routine initializes all RLMT-related variables to a known state.
 *	The initial state is SK_RLMT_RS_INIT.
 *	All ports are initialized to SK_RLMT_PS_INIT.
 *
 *
 *	SK_INIT_IO
 *	==========
 *
 *	Nothing.
 *
 *
 *	SK_INIT_RUN
 *	===========
 *
 *	Determine the adapter's random value.
 *	Set the hw registers, the "logical MAC address", the
 *	RLMT multicast address, and eventually the BPDU multicast address.
 *
 * Context:
 *	init, pageable
 *
 * Returns:
 *	Nothing.
 */
void	SkRlmtInit(
SK_AC	*pAC,	/* Adapter Context */
SK_IOC	IoC,	/* I/O Context */
int		Level)	/* Initialization Level */
{
	SK_U32		j;
	SK_U64		Random;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_INIT,
		("RLMT Init level %d.\n", Level))

	switch (Level) {
	case SK_INIT_DATA:	/* Initialize data structures. */
		SK_MEMSET((char *)&pAC->Rlmt, 0, sizeof(SK_RLMT));

		pAC->Rlmt.Port.PortState = SK_RLMT_PS_INIT;
		pAC->Rlmt.Port.LinkDown = SK_TRUE;
		pAC->Rlmt.Port.PortDown = SK_TRUE;
		pAC->Rlmt.Port.PortStarted = SK_FALSE;
		pAC->Rlmt.Port.RootIdSet = SK_FALSE;
		pAC->Rlmt.Port.Net = &pAC->Rlmt.Net;
		pAC->Rlmt.Port.AddrPort = &pAC->Addr.Port;

		pAC->Rlmt.NumNets = 1;
		pAC->Rlmt.Net.RlmtState = SK_RLMT_RS_INIT;
		pAC->Rlmt.Net.RootIdSet = SK_FALSE;
		/* Just assuming. */
		pAC->Rlmt.Net.RlmtMode = SK_RLMT_DEF_MODE;

		pAC->Rlmt.Net.Port = &pAC->Rlmt.Port;
		break;

	case SK_INIT_IO:	/* GIMacsFound first available here. */
		/* Initialize HW registers? */
		break;

	case SK_INIT_RUN:
		Random = SkOsGetTime(pAC);
		*(SK_U32*)&pAC->Rlmt.Port.Random[0] = *(SK_U32*)&Random;

		for (j = 0; j < 4; j++) {
			pAC->Rlmt.Port.Random[j] ^= pAC->Rlmt.Port.AddrPort->
				CurrentMacAddress.a[SK_MAC_ADDR_LEN - 1 - j];
		}

		/* Add RLMT MC address. */
		(void)SkAddrMcClear(pAC, IoC, SK_ADDR_PERMANENT | SK_MC_SW_ONLY);
		(void)SkAddrMcAdd(pAC, IoC, &SkRlmtMcAddr, SK_ADDR_PERMANENT);
		(void)SkAddrMcUpdate(pAC, IoC);
		break;

	default:	/* error */
		break;
	}
	return;
}	/* SkRlmtInit */


/******************************************************************************
 *
 *	SkRlmtBuildPacket - build an RLMT packet
 *
 * Description:
 *	This routine sets up an RLMT packet.
 *
 * Context:
 *	runtime, pageable?
 *
 * Returns:
 *	NULL or pointer to RLMT mbuf
 */
RLMT_STATIC SK_MBUF	*SkRlmtBuildPacket(
SK_AC		*pAC,		/* Adapter Context */
SK_IOC		IoC,		/* I/O Context */
SK_U16		PacketType,	/* RLMT packet type */
SK_MAC_ADDR	*SrcAddr,	/* Source address */
SK_MAC_ADDR	*DestAddr)	/* Destination address */
{
	int				i;
	SK_U16			Length;
	SK_MBUF			*pMb;
	SK_RLMT_PACKET	*pPacket;

	if ((pMb = SkDrvAllocRlmtMbuf(pAC, IoC, SK_RLMT_MAX_PACKET_SIZE)) != NULL) {
		pPacket = (SK_RLMT_PACKET*)pMb->pData;
		for (i = 0; i < SK_MAC_ADDR_LEN; i++) {
			pPacket->DstAddr[i] = DestAddr->a[i];
			pPacket->SrcAddr[i] = SrcAddr->a[i];
		}
		pPacket->DSap = SK_RLMT_DSAP;
		pPacket->SSap = SK_RLMT_SSAP;
		pPacket->Ctrl = SK_RLMT_CTRL;
		pPacket->Indicator[0] = SK_RLMT_INDICATOR0;
		pPacket->Indicator[1] = SK_RLMT_INDICATOR1;
		pPacket->Indicator[2] = SK_RLMT_INDICATOR2;
		pPacket->Indicator[3] = SK_RLMT_INDICATOR3;
		pPacket->Indicator[4] = SK_RLMT_INDICATOR4;
		pPacket->Indicator[5] = SK_RLMT_INDICATOR5;
		pPacket->Indicator[6] = SK_RLMT_INDICATOR6;

		SK_U16_TO_NETWORK_ORDER(PacketType, &pPacket->RlmtPacketType[0]);

		for (i = 0; i < 4; i++) {
			pPacket->Random[i] = pAC->Rlmt.Port.Random[i];
		}
		
		SK_U16_TO_NETWORK_ORDER(
			SK_RLMT_PACKET_VERSION, &pPacket->RlmtPacketVersion[0]);

		for (i = 0; i < SK_PACKET_DATA_LEN; i++) {
			pPacket->Data[i] = 0x00;
		}

		Length = SK_RLMT_MAX_PACKET_SIZE;	/* Or smaller. */
		pMb->Length = Length;
		Length -= 14;
		SK_U16_TO_NETWORK_ORDER(Length, &pPacket->TypeLen[0]);

		if (PacketType == SK_PACKET_ALIVE) {
			pAC->Rlmt.Port.TxHelloCts++;
		}
	}

	return (pMb);
}	/* SkRlmtBuildPacket */


/******************************************************************************
 *
 *	SkRlmtPortReceives - check if port is (going) down and bring it up
 *
 * Description:
 *	This routine checks if a port who received a non-BPDU packet
 *	needs to go up or needs to be stopped going down.
 *
 * Context:
 *	runtime, pageable?
 *
 * Returns:
 *	Nothing.
 */
RLMT_STATIC void	SkRlmtPortReceives(
SK_AC	*pAC,			/* Adapter Context */
SK_IOC	IoC)			/* I/O Context */
{
	SK_RLMT_PORT	*pRPort;
	SK_EVPARA		Para;

	pRPort = &pAC->Rlmt.Port;

	if (pRPort->PortState == SK_RLMT_PS_DOWN) {
		/*
		 * Port is marked down (rx), but received a non-BPDU packet.
		 * Bring it up.
		 */
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
			("SkRlmtPacketReceive: Received on PortDown.\n"))

		pRPort->PortState = SK_RLMT_PS_GOING_UP;
		Para.Para32[0] = 0;		/* PortNumber */
		Para.Para32[1] = (SK_U32)-1;
		SkTimerStart(pAC, IoC, &pRPort->UpTimer, SK_RLMT_PORTUP_TIM_VAL,
			SKGI_RLMT, SK_RLMT_PORTUP_TIM, Para);
	}	/* PortDown && !SuspectTx */

	return;
}	/* SkRlmtPortReceives */


/******************************************************************************
 *
 *	SkRlmtPacketReceive - receive a packet for closer examination
 *
 * Description:
 *	This routine examines a packet more closely than SK_RLMT_LOOKAHEAD.
 *
 * Context:
 *	runtime, pageable?
 *
 * Returns:
 *	Nothing.
 */
RLMT_STATIC void	SkRlmtPacketReceive(
SK_AC	*pAC,	/* Adapter Context */
SK_IOC	IoC,	/* I/O Context */
SK_MBUF	*pMb)	/* Received packet */
{
#ifdef xDEBUG
	extern	void DumpData(char *p, int size);
#endif	/* DEBUG */
	int					i;
	SK_U16				PacketType;
	SK_ADDR_PORT		*pAPort;
	SK_RLMT_PORT		*pRPort;
	SK_RLMT_PACKET		*pRPacket;
	SK_SPTREE_PACKET	*pSPacket;
	SK_EVPARA			Para;

	pAPort = &pAC->Addr.Port;
	pRPort = &pAC->Rlmt.Port;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX, ("SkRlmtPacketReceive.\n"))

	pRPacket = (SK_RLMT_PACKET*)pMb->pData;
	pSPacket = (SK_SPTREE_PACKET*)pRPacket;

#ifdef xDEBUG
	DumpData((char *)pRPacket, 32);
#endif	/* DEBUG */

	if (pRPort->PacketsPerTimeSlot != 0) {
		SkRlmtPortReceives(pAC, IoC);
	}
	
	/* Check destination address. */

	if (!SK_ADDR_EQUAL(pAPort->CurrentMacAddress.a, pRPacket->DstAddr) &&
		!SK_ADDR_EQUAL(SkRlmtMcAddr.a, pRPacket->DstAddr) &&
		!SK_ADDR_EQUAL(BridgeMcAddr.a, pRPacket->DstAddr)) {

		/* Not sent to current MAC or registered MC address => Trash it. */
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
			("SkRlmtPacketReceive: Not for me.\n"))

		SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
		return;
	}
	else if (SK_ADDR_EQUAL(pAPort->CurrentMacAddress.a, pRPacket->SrcAddr)) {
		/*
		 * Was sent by same port (may happen during port switching
		 * or in case of duplicate MAC addresses).
		 */

		/*
		 * Check for duplicate address here:
		 * If Packet.Random != My.Random => DupAddr.
		 */
		for (i = 3; i >= 0; i--) {
			if (pRPort->Random[i] != pRPacket->Random[i]) {
				break;
			}
		}

		/*
		 * CAUTION: Do not check for duplicate MAC address in RLMT Alive Reply
		 * packets (they have the LLC_COMMAND_RESPONSE_BIT set in
		 * pRPacket->SSap).
		 */
		if (i >= 0 && pRPacket->DSap == SK_RLMT_DSAP &&
			pRPacket->Ctrl == SK_RLMT_CTRL &&
			pRPacket->SSap == SK_RLMT_SSAP &&
			pRPacket->Indicator[0] == SK_RLMT_INDICATOR0 &&
			pRPacket->Indicator[1] == SK_RLMT_INDICATOR1 &&
			pRPacket->Indicator[2] == SK_RLMT_INDICATOR2 &&
			pRPacket->Indicator[3] == SK_RLMT_INDICATOR3 &&
			pRPacket->Indicator[4] == SK_RLMT_INDICATOR4 &&
			pRPacket->Indicator[5] == SK_RLMT_INDICATOR5 &&
			pRPacket->Indicator[6] == SK_RLMT_INDICATOR6) {

			SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
				("SkRlmtPacketReceive: Duplicate MAC Address.\n"))

			/* Error Log entry. */
			SK_ERR_LOG(pAC, SK_ERRCL_COMM, SKERR_RLMT_E006, SKERR_RLMT_E006_MSG);
		}
		else {
			/* Simply trash it. */
			SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
				("SkRlmtPacketReceive: Sent by me.\n"))
		}

		SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
		return;
	}

	/* Determine type of packet. */
	if (pRPacket->DSap == SK_RLMT_DSAP &&
		pRPacket->Ctrl == SK_RLMT_CTRL &&
		(pRPacket->SSap & ~LLC_COMMAND_RESPONSE_BIT) == SK_RLMT_SSAP &&
		pRPacket->Indicator[0] == SK_RLMT_INDICATOR0 &&
		pRPacket->Indicator[1] == SK_RLMT_INDICATOR1 &&
		pRPacket->Indicator[2] == SK_RLMT_INDICATOR2 &&
		pRPacket->Indicator[3] == SK_RLMT_INDICATOR3 &&
		pRPacket->Indicator[4] == SK_RLMT_INDICATOR4 &&
		pRPacket->Indicator[5] == SK_RLMT_INDICATOR5 &&
		pRPacket->Indicator[6] == SK_RLMT_INDICATOR6) {

		/* It's an RLMT packet. */
		PacketType = (SK_U16)((pRPacket->RlmtPacketType[0] << 8) |
			pRPacket->RlmtPacketType[1]);

		switch (PacketType) {
		case SK_PACKET_ANNOUNCE:	/* Not yet used. */
			SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
				("SkRlmtPacketReceive: Announce.\n"))

			SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
			break;

		case SK_PACKET_ALIVE:
			if (pRPacket->SSap & LLC_COMMAND_RESPONSE_BIT) {
				SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
					("SkRlmtPacketReceive: Alive Reply.\n"))

				if (!(pAC->Addr.Port.PromMode & SK_PROM_MODE_LLC) ||
					SK_ADDR_EQUAL(pRPacket->DstAddr,
					pAPort->CurrentMacAddress.a)) {

					/* Obviously we could send something. */
					if (pRPort->PortState == SK_RLMT_PS_DOWN) {
						pRPort->PortState = SK_RLMT_PS_GOING_UP;

						SkTimerStop(pAC, IoC, &pRPort->DownTxTimer);

						Para.Para32[0] = 0;		/* PortNumber */
						Para.Para32[1] = (SK_U32)-1;
						SkTimerStart(pAC, IoC, &pRPort->UpTimer,
							SK_RLMT_PORTUP_TIM_VAL, SKGI_RLMT,
							SK_RLMT_PORTUP_TIM, Para);
					}
				}

				/* Mark sending port as alive? */
				SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
			}
			else {	/* Alive Request Packet. */
				SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
					("SkRlmtPacketReceive: Alive Request.\n"))

				pRPort->RxHelloCts++;

				/* Answer. */
				for (i = 0; i < SK_MAC_ADDR_LEN; i++) {
					pRPacket->DstAddr[i] = pRPacket->SrcAddr[i];
					pRPacket->SrcAddr[i] = pAC->Addr.Port.CurrentMacAddress.a[i];
				}
				pRPacket->SSap |= LLC_COMMAND_RESPONSE_BIT;

				Para.pParaPtr = pMb;
				SkEventQueue(pAC, SKGI_DRV, SK_DRV_RLMT_SEND, Para);
			}
			break;

		case SK_PACKET_ADDR_CHANGED:
			SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
				("SkRlmtPacketReceive: Address Change.\n"))
			SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
			break;

		default:
			SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
				("SkRlmtPacketReceive: Unknown RLMT packet.\n"))
			SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
		}
	}
	else {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
			("SkRlmtPacketReceive: Unknown Packet Type.\n"))

		/* Unknown packet. */
		SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
	}
	return;
}	/* SkRlmtPacketReceive */


/******************************************************************************
 *
 *	SkRlmtPortStart - initialize port variables and start port
 *
 * Description:
 *	This routine initializes a port's variables and issues a PORT_START
 *	to the HWAC module.  This handles retries if the start fails or the
 *	link eventually goes down.
 *
 * Context:
 *	runtime, pageable?
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtPortStart(
SK_AC	*pAC,		/* Adapter Context */
SK_IOC	IoC)		/* I/O Context */
{
	SK_EVPARA	Para;

	pAC->Rlmt.Port.PortState = SK_RLMT_PS_LINK_DOWN;
	pAC->Rlmt.Port.PortStarted = SK_TRUE;
	pAC->Rlmt.Port.LinkDown = SK_TRUE;
	pAC->Rlmt.Port.PortDown = SK_TRUE;
	pAC->Rlmt.Port.RootIdSet = SK_FALSE;
	Para.Para32[0] = 0;		/* PortNumber */
	Para.Para32[1] = (SK_U32)-1;
	SkEventQueue(pAC, SKGI_HWAC, SK_HWEV_PORT_START, Para);
}	/* SkRlmtPortStart */


/******************************************************************************
 *
 *	SkRlmtEvtLinkUp - LINK_UP
 *
 * Description:
 *	This routine handles LLINK_UP events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtLinkUp(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 PortNumber; SK_U32 Undefined */
{
	SK_RLMT_PORT	*pRPort;
	SK_EVPARA		Para2;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_LINK_UP Event BEGIN.\n"))

	pRPort = &pAC->Rlmt.Port;
	if (!pRPort->PortStarted) {
		SK_ERR_LOG(pAC, SK_ERRCL_SW, SKERR_RLMT_E008, SKERR_RLMT_E008_MSG);
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
				("SK_RLMT_LINK_UP Event EMPTY.\n"))
		return;
	}

	if (!pRPort->LinkDown) {
		/* RA;:;: Any better solution? */
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_LINK_UP Event EMPTY.\n"))
		return;
	}

	SkTimerStop(pAC, IoC, &pRPort->UpTimer);
	SkTimerStop(pAC, IoC, &pRPort->DownRxTimer);
	SkTimerStop(pAC, IoC, &pRPort->DownTxTimer);

	/* Do something if timer already fired? */

	pRPort->LinkDown = SK_FALSE;
	pRPort->PortState = SK_RLMT_PS_GOING_UP;
	pRPort->Net->LinksUp++;

	Para2.Para32[0] = 0;		/* PortNumber */
	Para2.Para32[1] = (SK_U32)-1;
	SkTimerStart(pAC, IoC, &pRPort->UpTimer, SK_RLMT_PORTUP_TIM_VAL,
		SKGI_RLMT, SK_RLMT_PORTUP_TIM, Para2);
	
	if ((pRPort->Net->RlmtMode & SK_RLMT_TRANSPARENT) == 0 &&
		(pRPort->Net->RlmtMode & SK_RLMT_CHECK_LINK) != 0 &&
		(Para2.pParaPtr = SkRlmtBuildPacket(pAC, IoC, SK_PACKET_ANNOUNCE,
		&pAC->Addr.Port.CurrentMacAddress, &SkRlmtMcAddr)) != NULL) {

		/* Send "new" packet to RLMT multicast address. */
		SkEventQueue(pAC, SKGI_DRV, SK_DRV_RLMT_SEND, Para2);
	}

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_LINK_UP Event END.\n"))
}	/* SkRlmtEvtLinkUp */


/******************************************************************************
 *
 *	SkRlmtEvtPortUpTim - PORT_UP_TIM
 *
 * Description:
 *	This routine handles PORT_UP_TIM events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtPortUpTim(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 PortNumber; SK_U32 -1 */
{
	SK_RLMT_PORT	*pRPort;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_PORTUP_TIM Event BEGIN.\n"))

	if (Para.Para32[1] != (SK_U32)-1) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad Parameter.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_PORTUP_TIM Event EMPTY.\n"))
		return;
	}

	pRPort = &pAC->Rlmt.Port;
	if (pRPort->LinkDown || (pRPort->PortState == SK_RLMT_PS_UP)) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_PORTUP_TIM Event EMPTY.\n"))
		return;
	}

	pRPort->PortDown = SK_FALSE;
	pRPort->PortState = SK_RLMT_PS_UP;
	pRPort->Net->PortsUp++;
	if (pRPort->Net->RlmtState != SK_RLMT_RS_INIT) {
		SkEventQueue(pAC, SKGI_PNMI, SK_PNMI_EVT_RLMT_PORT_UP, Para);
	}

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_PORTUP_TIM Event END.\n"))
}	/* SkRlmtEvtPortUpTim */


/******************************************************************************
 *
 *	SkRlmtEvtPortDownTim - PORT_DOWN_*
 *
 * Description:
 *	This routine handles PORT_DOWN_* events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtPortDownX(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_U32		Event,	/* Event code */
SK_EVPARA	Para)	/* SK_U32 PortNumber; SK_U32 -1 */
{
	SK_RLMT_PORT	*pRPort;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_PORTDOWN* Event (%d) BEGIN.\n", Event))

	if (Para.Para32[1] != (SK_U32)-1) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad Parameter.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_PORTDOWN* Event EMPTY.\n"))
		return;
	}

	pRPort = &pAC->Rlmt.Port;
	if (!pRPort->PortStarted || (Event == SK_RLMT_PORTDOWN_TX_TIM)) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_PORTDOWN* Event (%d) EMPTY.\n", Event))
		return;
	}
	
	/* Stop port's timers. */
	SkTimerStop(pAC, IoC, &pRPort->UpTimer);
	SkTimerStop(pAC, IoC, &pRPort->DownRxTimer);
	SkTimerStop(pAC, IoC, &pRPort->DownTxTimer);

	if (pRPort->PortState != SK_RLMT_PS_LINK_DOWN) {
		pRPort->PortState = SK_RLMT_PS_DOWN;
	}

	if (!pRPort->PortDown) {
		pRPort->Net->PortsUp--;
		pRPort->PortDown = SK_TRUE;
		SkEventQueue(pAC, SKGI_PNMI, SK_PNMI_EVT_RLMT_PORT_DOWN, Para);
	}

	pRPort->PacketsPerTimeSlot = 0;
	/* pRPort->DataPacketsPerTimeSlot = 0; */

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_PORTDOWN* Event (%d) END.\n", Event))
}	/* SkRlmtEvtPortDownX */


/******************************************************************************
 *
 *	SkRlmtEvtLinkDown - LINK_DOWN
 *
 * Description:
 *	This routine handles LINK_DOWN events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtLinkDown(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 PortNumber; SK_U32 Undefined */
{
	SK_RLMT_PORT	*pRPort;

	pRPort = &pAC->Rlmt.Port;
	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_LINK_DOWN Event BEGIN.\n"))

	if (!pRPort->LinkDown) {
		pRPort->Net->LinksUp--;
		pRPort->LinkDown = SK_TRUE;
		pRPort->PortState = SK_RLMT_PS_LINK_DOWN;

		/* Ensure that port is marked down. */
		Para.Para32[1] = -1;
		(void)SkRlmtEvent(pAC, IoC, SK_RLMT_PORTDOWN, Para);
	}

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_LINK_DOWN Event END.\n"))
}	/* SkRlmtEvtLinkDown */


/******************************************************************************
 *
 *	SkRlmtEvtPortAddr - PORT_ADDR
 *
 * Description:
 *	This routine handles PORT_ADDR events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtPortAddr(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 PortNumber; SK_U32 -1 */
{
	SK_MAC_ADDR		*pOldMacAddr;
	SK_MAC_ADDR		*pNewMacAddr;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_PORT_ADDR Event BEGIN.\n"))

	if (Para.Para32[1] != (SK_U32)-1) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad Parameter.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_PORT_ADDR Event EMPTY.\n"))
		return;
	}

	/* Port's physical MAC address changed. */
	pOldMacAddr = &pAC->Addr.Port.PreviousMacAddress;
	pNewMacAddr = &pAC->Addr.Port.CurrentMacAddress;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_PORT_ADDR Event END.\n"))
}	/* SkRlmtEvtPortAddr */


/******************************************************************************
 *
 *	SkRlmtEvtStart - START
 *
 * Description:
 *	This routine handles START events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtStart(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 NetNumber; SK_U32 -1 */
{
	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_START Net Event BEGIN.\n"))

	if (Para.Para32[1] != (SK_U32)-1) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad Parameter.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_START Event EMPTY.\n"))
		return;
	}

	if (Para.Para32[0] >= pAC->Rlmt.NumNets) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad NetNumber %d.\n", Para.Para32[0]))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_START Event EMPTY.\n"))
		return;
	}

	if (pAC->Rlmt.Net.RlmtState != SK_RLMT_RS_INIT) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_START Event EMPTY.\n"))
		return;
	}

	if (pAC->Rlmt.NetsStarted >= pAC->Rlmt.NumNets) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("All nets should have been started.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_START Event EMPTY.\n"))
		return;
	}

	pAC->Rlmt.Net.LinksUp = 0;
	pAC->Rlmt.Net.PortsUp = 0;
	pAC->Rlmt.Net.RlmtState = SK_RLMT_RS_NET_DOWN;

	/* Start preferred port. */
	SkRlmtPortStart(pAC, IoC);
	pAC->Rlmt.NetsStarted++;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_START Event END.\n"))
}	/* SkRlmtEvtStart */


/******************************************************************************
 *
 *	SkRlmtEvtStop - STOP
 *
 * Description:
 *	This routine handles STOP events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtStop(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 NetNumber; SK_U32 -1 */
{
	SK_EVPARA	Para2;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_STOP Net Event BEGIN.\n"))

	if (Para.Para32[1] != (SK_U32)-1) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad Parameter.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STOP Event EMPTY.\n"))
		return;
	}

	if (Para.Para32[0] >= pAC->Rlmt.NumNets) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad NetNumber %d.\n", Para.Para32[0]))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STOP Event EMPTY.\n"))
		return;
	}

	if (pAC->Rlmt.Net.RlmtState == SK_RLMT_RS_INIT) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STOP Event EMPTY.\n"))
		return;
	}

	if (pAC->Rlmt.NetsStarted == 0) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("All nets are stopped.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STOP Event EMPTY.\n"))
		return;
	}

	/* Stop net. */
	pAC->Rlmt.Net.RlmtState = SK_RLMT_RS_INIT;
	pAC->Rlmt.Net.RootIdSet = SK_FALSE;
	Para2.Para32[0] = SK_RLMT_NET_DOWN_FINAL;
	Para2.Para32[1] = 0;	/* Para.Para32[0] -- Net# */
	SkEventQueue(pAC, SKGI_DRV, SK_DRV_NET_DOWN, Para2);

	/* Stop ports. */
	if (pAC->Rlmt.Port.PortState != SK_RLMT_PS_INIT) {
		SkTimerStop(pAC, IoC, &pAC->Rlmt.Port.UpTimer);
		SkTimerStop(pAC, IoC, &pAC->Rlmt.Port.DownRxTimer);
		SkTimerStop(pAC, IoC, &pAC->Rlmt.Port.DownTxTimer);

		pAC->Rlmt.Port.PortState = SK_RLMT_PS_INIT;
		pAC->Rlmt.Port.RootIdSet = SK_FALSE;
		pAC->Rlmt.Port.PortStarted = SK_FALSE;
		Para2.Para32[0] = 0;	/* PortNumber */
		Para2.Para32[1] = (SK_U32)-1;
		SkEventQueue(pAC, SKGI_HWAC, SK_HWEV_PORT_STOP, Para2);
	}

	pAC->Rlmt.NetsStarted--;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_STOP Event END.\n"))
}	/* SkRlmtEvtStop */


/******************************************************************************
 *
 *	SkRlmtEvtPacketRx - PACKET_RECEIVED
 *
 * Description:
 *	This routine handles PACKET_RECEIVED events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtPacketRx(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_MBUF *pMb */
{
	SK_MBUF	*pMb;
	SK_MBUF	*pNextMb;

#if 0
	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_PACKET_RECEIVED Event BEGIN.\n"))
#endif	/* 0 */

#ifdef DEBUG
	pMb = Para.pParaPtr;
	if (pMb == NULL) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL, ("No mbuf.\n"))
	}
	else if (pMb->pNext != NULL) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("More than one mbuf or pMb->pNext not set.\n"))
	}
#endif	/* DEBUG */

	for (pMb = Para.pParaPtr; pMb != NULL; pMb = pNextMb) {
		pNextMb = pMb->pNext;
		pMb->pNext = NULL;

		if (pAC->Rlmt.Net.RlmtState == SK_RLMT_RS_INIT) {
			SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
		}
		else {
			SkRlmtPacketReceive(pAC, IoC, pMb);
		}
	}

#if 0
	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_PACKET_RECEIVED Event END.\n"))
#endif	/* 0 */
}	/* SkRlmtEvtPacketRx */


/******************************************************************************
 *
 *	SkRlmtEvtStatsClear - STATS_CLEAR
 *
 * Description:
 *	This routine handles STATS_CLEAR events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtStatsClear(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 NetNumber; SK_U32 -1 */
{
	SK_RLMT_PORT	*pRPort;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_STATS_CLEAR Event BEGIN.\n"))

	if (Para.Para32[1] != (SK_U32)-1) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad Parameter.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STATS_CLEAR Event EMPTY.\n"))
		return;
	}

	if (Para.Para32[0] >= pAC->Rlmt.NumNets) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad NetNumber %d.\n", Para.Para32[0]))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STATS_CLEAR Event EMPTY.\n"))
		return;
	}

	/* Clear statistics for logical and physical ports. */
	pRPort = &pAC->Rlmt.Port;
	pRPort->TxHelloCts = 0;
	pRPort->RxHelloCts = 0;

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_STATS_CLEAR Event END.\n"))
}	/* SkRlmtEvtStatsClear */


/******************************************************************************
 *
 *	SkRlmtEvtStatsUpdate - STATS_UPDATE
 *
 * Description:
 *	This routine handles STATS_UPDATE events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	Nothing
 */
RLMT_STATIC void	SkRlmtEvtStatsUpdate(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_EVPARA	Para)	/* SK_U32 NetNumber; SK_U32 -1 */
{
	if (Para.Para32[1] != (SK_U32)-1) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad Parameter.\n"))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STATS_UPDATE Event EMPTY.\n"))
		return;
	}

	if (Para.Para32[0] >= pAC->Rlmt.NumNets) {
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Bad NetNumber %d.\n", Para.Para32[0]))
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("SK_RLMT_STATS_UPDATE Event EMPTY.\n"))
		return;
	}

#if 0
	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_STATS_UPDATE Event BEGIN.\n"))

	/* Update statistics - currently always up-to-date. */

	SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
		("SK_RLMT_STATS_UPDATE Event END.\n"))
#endif	/* 0 */
}	/* SkRlmtEvtStatsUpdate */


/******************************************************************************
 *
 *	SkRlmtEvent - a PORT- or an RLMT-specific event happened
 *
 * Description:
 *	This routine calls subroutines to handle PORT- and RLMT-specific events.
 *
 * Context:
 *	runtime, pageable?
 *	may be called after SK_INIT_IO
 *
 * Returns:
 *	0
 */
int	SkRlmtEvent(
SK_AC		*pAC,	/* Adapter Context */
SK_IOC		IoC,	/* I/O Context */
SK_U32		Event,	/* Event code */
SK_EVPARA	Para)	/* Event-specific parameter */
{
	switch (Event) {
	
	/* ----- PORT events ----- */

	case SK_RLMT_LINK_UP:		/* From SIRQ. */
		SkRlmtEvtLinkUp(pAC, IoC, Para);
		break;

	case SK_RLMT_PORTUP_TIM:	/* From RLMT via TIME. */
		SkRlmtEvtPortUpTim(pAC, IoC, Para);
		break;

	case SK_RLMT_PORTDOWN:			/* From RLMT. */
	case SK_RLMT_PORTDOWN_RX_TIM:	/* From RLMT via TIME. */
	case SK_RLMT_PORTDOWN_TX_TIM:	/* From RLMT via TIME. */
		SkRlmtEvtPortDownX(pAC, IoC, Event, Para);
		break;

	case SK_RLMT_LINK_DOWN:		/* From SIRQ. */
		SkRlmtEvtLinkDown(pAC, IoC, Para);
		break;

	case SK_RLMT_PORT_ADDR:		/* From ADDR. */
		SkRlmtEvtPortAddr(pAC, IoC, Para);
		break;

	/* ----- RLMT events ----- */

	case SK_RLMT_START:		/* From DRV. */
		SkRlmtEvtStart(pAC, IoC, Para);
		break;

	case SK_RLMT_STOP:		/* From DRV. */
		SkRlmtEvtStop(pAC, IoC, Para);
		break;

	case SK_RLMT_PACKET_RECEIVED:	/* From DRV. */
		SkRlmtEvtPacketRx(pAC, IoC, Para);
		break;

	case SK_RLMT_STATS_CLEAR:	/* From PNMI. */
		SkRlmtEvtStatsClear(pAC, IoC, Para);
		break;

	case SK_RLMT_STATS_UPDATE:	/* From PNMI. */
		SkRlmtEvtStatsUpdate(pAC, IoC, Para);
		break;

	/* ----- Unknown events ----- */

	default:	/* Create error log entry. */
		SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
			("Unknown RLMT Event %d.\n", Event))
		SK_ERR_LOG(pAC, SK_ERRCL_SW, SKERR_RLMT_E003, SKERR_RLMT_E003_MSG);
		break;
	}	/* switch() */

	return (0);
}	/* SkRlmtEvent */

#ifdef __cplusplus
}
#endif	/* __cplusplus */

