Logo Search packages:      
Sourcecode: linux version File versions  Download package

skrlmt.c

/******************************************************************************
 *
 * Name:    skrlmt.c
 * Project: GEnesis, PCI Gigabit Ethernet Adapter
 * Version: $Revision: 1.69 $
 * Date:    $Date: 2003/04/15 09:39:22 $
 * Purpose: Manage links on SK-NET Adapters, esp. redundant ones.
 *
 ******************************************************************************/

/******************************************************************************
 *
 *    (C)Copyright 1998-2002 SysKonnect GmbH.
 *    (C)Copyright 2002-2003 Marvell.
 *
 *    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.
 *
 ******************************************************************************/

/******************************************************************************
 *
 * Description:
 *
 * This module contains code for Link ManagemenT (LMT) of SK-NET Adapters.
 * It is mainly intended for adapters with more than one link.
 * For such adapters, this module realizes Redundant Link ManagemenT (RLMT).
 *
 * Include File Hierarchy:
 *
 *    "skdrv1st.h"
 *    "skdrv2nd.h"
 *
 ******************************************************************************/

#ifndef     lint
static const char SysKonnectFileId[] =
      "@(#) $Id: skrlmt.c,v 1.69 2003/04/15 09:39:22 tschilli Exp $ (C) Marvell.";
#endif      /* !defined(lint) */

#define __SKRLMT_C

#ifdef __cplusplus
extern "C" {
#endif      /* cplusplus */

#include "h/skdrv1st.h"
#include "h/skdrv2nd.h"

/* defines ********************************************************************/

#ifndef SK_HWAC_LINK_LED
#define SK_HWAC_LINK_LED(a,b,c,d)
#endif      /* !defined(SK_HWAC_LINK_LED) */

#ifndef DEBUG
#define RLMT_STATIC     static
#else /* DEBUG */
#define RLMT_STATIC

#ifndef SK_LITTLE_ENDIAN
/* First 32 bits */
#define OFFS_LO32 1

/* Second 32 bits */
#define OFFS_HI32 0
#else /* SK_LITTLE_ENDIAN */
/* First 32 bits */
#define OFFS_LO32 0

/* Second 32 bits */
#define OFFS_HI32 1
#endif      /* SK_LITTLE_ENDIAN */

#endif      /* DEBUG */

/* ----- Private timeout values ----- */

#define SK_RLMT_MIN_TO_VAL                   125000   /* 1/8 sec. */
#define SK_RLMT_DEF_TO_VAL                  1000000   /* 1 sec. */
#define SK_RLMT_PORTDOWN_TIM_VAL       900000   /* another 0.9 sec. */
#define SK_RLMT_PORTSTART_TIM_VAL      100000   /* 0.1 sec. */
#define SK_RLMT_PORTUP_TIM_VAL              2500000   /* 2.5 sec. */
#define SK_RLMT_SEG_TO_VAL                900000000   /* 15 min. */

/* Assume tick counter increment is 1 - may be set OS-dependent. */
#ifndef SK_TICK_INCR
#define SK_TICK_INCR    SK_CONSTU64(1)
#endif      /* !defined(SK_TICK_INCR) */

/*
 * Amount that a time stamp must be later to be recognized as "substantially
 * later". This is about 1/128 sec, but above 1 tick counter increment.
 */
#define SK_RLMT_BC_DELTA            (1 + ((SK_TICKS_PER_SEC >> 7) > SK_TICK_INCR ? \
                                                      (SK_TICKS_PER_SEC >> 7) : SK_TICK_INCR))

/* ----- Private RLMT defaults ----- */

#define SK_RLMT_DEF_PREF_PORT 0                             /* "Lower" port. */
#define SK_RLMT_DEF_MODE            SK_RLMT_CHECK_LINK      /* Default RLMT Mode. */

/* ----- Private RLMT checking states ----- */

#define SK_RLMT_RCS_SEG             1           /* RLMT Check State: check seg. */
#define SK_RLMT_RCS_START_SEG 2           /* RLMT Check State: start check seg. */
#define SK_RLMT_RCS_SEND_SEG  4           /* RLMT Check State: send BPDU packet */
#define SK_RLMT_RCS_REPORT_SEG      8           /* RLMT Check State: report seg. */

/* ----- Private PORT checking states ----- */

#define SK_RLMT_PCS_TX              1           /* Port Check State: check tx. */
#define SK_RLMT_PCS_RX              2           /* Port Check State: check rx. */

/* ----- Private PORT events ----- */

/* Note: Update simulation when changing these. */
#define SK_RLMT_PORTSTART_TIM 1100  /* Port start timeout. */
#define SK_RLMT_PORTUP_TIM          1101  /* Port can now go up. */
#define SK_RLMT_PORTDOWN_RX_TIM     1102  /* Port did not receive once ... */
#define SK_RLMT_PORTDOWN            1103  /* Port went down. */
#define SK_RLMT_PORTDOWN_TX_TIM     1104  /* Partner did not receive ... */

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

/* Note: Update simulation when changing these. */
#define SK_RLMT_TIM                       2100  /* RLMT timeout. */
#define SK_RLMT_SEG_TIM             2101  /* RLMT segmentation check timeout. */

#define TO_SHORTEN(tim) ((tim) / 2)

/* Error numbers and messages. */
#define SKERR_RLMT_E001       (SK_ERRBASE_RLMT + 0)
#define SKERR_RLMT_E001_MSG   "No Packet."
#define SKERR_RLMT_E002       (SKERR_RLMT_E001 + 1)
#define SKERR_RLMT_E002_MSG   "Short Packet."
#define SKERR_RLMT_E003       (SKERR_RLMT_E002 + 1)
#define SKERR_RLMT_E003_MSG   "Unknown RLMT event."
#define SKERR_RLMT_E004       (SKERR_RLMT_E003 + 1)
#define SKERR_RLMT_E004_MSG   "PortsUp incorrect."
#define SKERR_RLMT_E005       (SKERR_RLMT_E004 + 1)
#define SKERR_RLMT_E005_MSG   \
 "Net seems to be segmented (different root bridges are reported on the ports)."
#define SKERR_RLMT_E006       (SKERR_RLMT_E005 + 1)
#define SKERR_RLMT_E006_MSG   "Duplicate MAC Address detected."
#define SKERR_RLMT_E007       (SKERR_RLMT_E006 + 1)
#define SKERR_RLMT_E007_MSG   "LinksUp incorrect."
#define SKERR_RLMT_E008       (SKERR_RLMT_E007 + 1)
#define SKERR_RLMT_E008_MSG   "Port not started but link came up."
#define SKERR_RLMT_E009       (SKERR_RLMT_E008 + 1)
#define SKERR_RLMT_E009_MSG   "Corrected illegal setting of Preferred Port."
#define SKERR_RLMT_E010       (SKERR_RLMT_E009 + 1)
#define SKERR_RLMT_E010_MSG   "Ignored illegal Preferred Port."

/* LLC field values. */
#define LLC_COMMAND_RESPONSE_BIT          1
#define LLC_TEST_COMMAND                        0xE3
#define LLC_UI                                        0x03

/* RLMT Packet fields. */
#define     SK_RLMT_DSAP                              0
#define     SK_RLMT_SSAP                              0
#define SK_RLMT_CTRL                            (LLC_TEST_COMMAND)
#define SK_RLMT_INDICATOR0                      0x53  /* S */
#define SK_RLMT_INDICATOR1                      0x4B  /* K */
#define SK_RLMT_INDICATOR2                      0x2D  /* - */
#define SK_RLMT_INDICATOR3                      0x52  /* R */
#define SK_RLMT_INDICATOR4                      0x4C  /* L */
#define SK_RLMT_INDICATOR5                      0x4D  /* M */
#define SK_RLMT_INDICATOR6                      0x54  /* T */
#define SK_RLMT_PACKET_VERSION                  0

/* RLMT SPT Flag values. */
#define     SK_RLMT_SPT_FLAG_CHANGE             0x01
#define     SK_RLMT_SPT_FLAG_CHANGE_ACK         0x80

/* RLMT SPT Packet fields. */
#define     SK_RLMT_SPT_DSAP                    0x42
#define     SK_RLMT_SPT_SSAP                    0x42
#define SK_RLMT_SPT_CTRL                        (LLC_UI)
#define     SK_RLMT_SPT_PROTOCOL_ID0            0x00
#define     SK_RLMT_SPT_PROTOCOL_ID1            0x00
#define     SK_RLMT_SPT_PROTOCOL_VERSION_ID     0x00
#define     SK_RLMT_SPT_BPDU_TYPE               0x00
#define     SK_RLMT_SPT_FLAGS                   0x00  /* ?? */
#define     SK_RLMT_SPT_ROOT_ID0                0xFF  /* Lowest possible priority. */
#define     SK_RLMT_SPT_ROOT_ID1                0xFF  /* Lowest possible priority. */

/* Remaining 6 bytes will be the current port address. */
#define     SK_RLMT_SPT_ROOT_PATH_COST0         0x00
#define     SK_RLMT_SPT_ROOT_PATH_COST1         0x00
#define     SK_RLMT_SPT_ROOT_PATH_COST2         0x00
#define     SK_RLMT_SPT_ROOT_PATH_COST3         0x00
#define     SK_RLMT_SPT_BRIDGE_ID0              0xFF  /* Lowest possible priority. */
#define     SK_RLMT_SPT_BRIDGE_ID1              0xFF  /* Lowest possible priority. */

/* Remaining 6 bytes will be the current port address. */
#define     SK_RLMT_SPT_PORT_ID0                0xFF  /* Lowest possible priority. */
#define     SK_RLMT_SPT_PORT_ID1                0xFF  /* Lowest possible priority. */
#define     SK_RLMT_SPT_MSG_AGE0                0x00
#define     SK_RLMT_SPT_MSG_AGE1                0x00
#define     SK_RLMT_SPT_MAX_AGE0                0x00
#define     SK_RLMT_SPT_MAX_AGE1                0xFF
#define     SK_RLMT_SPT_HELLO_TIME0             0x00
#define     SK_RLMT_SPT_HELLO_TIME1             0xFF
#define     SK_RLMT_SPT_FWD_DELAY0              0x00
#define     SK_RLMT_SPT_FWD_DELAY1              0x40

/* Size defines. */
#define SK_RLMT_MIN_PACKET_SIZE                 34
#define SK_RLMT_MAX_PACKET_SIZE                 (SK_RLMT_MAX_TX_BUF_SIZE)
#define SK_PACKET_DATA_LEN                      (SK_RLMT_MAX_PACKET_SIZE - \
                                                            SK_RLMT_MIN_PACKET_SIZE)

/* ----- RLMT packet types ----- */
#define SK_PACKET_ANNOUNCE                      1     /* Port announcement. */
#define SK_PACKET_ALIVE                         2     /* Alive packet to port. */
#define SK_PACKET_ADDR_CHANGED                  3     /* Port address changed. */
#define SK_PACKET_CHECK_TX                      4     /* Check your tx line. */

#ifdef SK_LITTLE_ENDIAN
#define SK_U16_TO_NETWORK_ORDER(Val,Addr) { \
      SK_U8 *_Addr = (SK_U8*)(Addr); \
      SK_U16      _Val = (SK_U16)(Val); \
      *_Addr++ = (SK_U8)(_Val >> 8); \
      *_Addr = (SK_U8)(_Val & 0xFF); \
}
#endif      /* SK_LITTLE_ENDIAN */

#ifdef SK_BIG_ENDIAN
#define SK_U16_TO_NETWORK_ORDER(Val,Addr) (*(SK_U16*)(Addr) = (SK_U16)(Val))
#endif      /* SK_BIG_ENDIAN */

#define AUTONEG_FAILED  SK_FALSE
#define AUTONEG_SUCCESS SK_TRUE


/* typedefs *******************************************************************/

/* RLMT packet.  Length: SK_RLMT_MAX_PACKET_SIZE (60) bytes. */
typedef struct s_RlmtPacket {
      SK_U8 DstAddr[SK_MAC_ADDR_LEN];
      SK_U8 SrcAddr[SK_MAC_ADDR_LEN];
      SK_U8 TypeLen[2];
      SK_U8 DSap;
      SK_U8 SSap;
      SK_U8 Ctrl;
      SK_U8 Indicator[7];
      SK_U8 RlmtPacketType[2];
      SK_U8 Align1[2];
      SK_U8 Random[4];                    /* Random value of requesting(!) station. */
      SK_U8 RlmtPacketVersion[2];   /* RLMT Packet version. */
      SK_U8 Data[SK_PACKET_DATA_LEN];
} SK_RLMT_PACKET;

typedef struct s_SpTreeRlmtPacket {
      SK_U8 DstAddr[SK_MAC_ADDR_LEN];
      SK_U8 SrcAddr[SK_MAC_ADDR_LEN];
      SK_U8 TypeLen[2];
      SK_U8 DSap;
      SK_U8 SSap;
      SK_U8 Ctrl;
      SK_U8 ProtocolId[2];
      SK_U8 ProtocolVersionId;
      SK_U8 BpduType;
      SK_U8 Flags;
      SK_U8 RootId[8];
      SK_U8 RootPathCost[4];
      SK_U8 BridgeId[8];
      SK_U8 PortId[2];
      SK_U8 MessageAge[2];
      SK_U8 MaxAge[2];
      SK_U8 HelloTime[2];
      SK_U8 ForwardDelay[2];
} SK_SPTREE_PACKET;

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

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

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

/* None. */

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

RLMT_STATIC void  SkRlmtCheckSwitch(
      SK_AC *pAC,
      SK_IOC      IoC,
      SK_U32      NetIdx);
RLMT_STATIC void  SkRlmtCheckSeg(
      SK_AC *pAC,
      SK_IOC      IoC,
      SK_U32      NetIdx);
RLMT_STATIC void  SkRlmtEvtSetNets(
      SK_AC       *pAC,
      SK_IOC            IoC,
      SK_EVPARA   Para);

/******************************************************************************
 *
 *    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            i, j;
      SK_U64            Random;
      SK_EVPARA   Para;
    SK_MAC_ADDR         VirtualMacAddress;
    SK_MAC_ADDR         PhysicalAMacAddress;
    SK_BOOL       VirtualMacAddressSet;
    SK_BOOL       PhysicalAMacAddressSet;

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

            for (i = 0; i < SK_MAX_MACS; i++) {
                  pAC->Rlmt.Port[i].PortState = SK_RLMT_PS_INIT;
                  pAC->Rlmt.Port[i].LinkDown = SK_TRUE;
                  pAC->Rlmt.Port[i].PortDown = SK_TRUE;
                  pAC->Rlmt.Port[i].PortStarted = SK_FALSE;
                  pAC->Rlmt.Port[i].PortNoRx = SK_FALSE;
                  pAC->Rlmt.Port[i].RootIdSet = SK_FALSE;
                  pAC->Rlmt.Port[i].PortNumber = i;
                  pAC->Rlmt.Port[i].Net = &pAC->Rlmt.Net[0];
                  pAC->Rlmt.Port[i].AddrPort = &pAC->Addr.Port[i];
            }

            pAC->Rlmt.NumNets = 1;
            for (i = 0; i < SK_MAX_NETS; i++) {
                  pAC->Rlmt.Net[i].RlmtState = SK_RLMT_RS_INIT;
                  pAC->Rlmt.Net[i].RootIdSet = SK_FALSE;
                  pAC->Rlmt.Net[i].PrefPort = SK_RLMT_DEF_PREF_PORT;
                  pAC->Rlmt.Net[i].Preference = 0xFFFFFFFF;   /* Automatic. */
                  /* Just assuming. */
                  pAC->Rlmt.Net[i].ActivePort = pAC->Rlmt.Net[i].PrefPort;
                  pAC->Rlmt.Net[i].RlmtMode = SK_RLMT_DEF_MODE;
                  pAC->Rlmt.Net[i].TimeoutValue = SK_RLMT_DEF_TO_VAL;
                  pAC->Rlmt.Net[i].NetNumber = i;
            }

            pAC->Rlmt.Net[0].Port[0] = &pAC->Rlmt.Port[0];
            pAC->Rlmt.Net[0].Port[1] = &pAC->Rlmt.Port[1];
#if SK_MAX_NETS > 1
            pAC->Rlmt.Net[1].Port[0] = &pAC->Rlmt.Port[1];
#endif      /* SK_MAX_NETS > 1 */
            break;

      case SK_INIT_IO:  /* GIMacsFound first available here. */
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_INIT,
                  ("RLMT: %d MACs were detected.\n", pAC->GIni.GIMacsFound))

            pAC->Rlmt.Net[0].NumPorts = pAC->GIni.GIMacsFound;

            /* Initialize HW registers? */
            if (pAC->GIni.GIMacsFound == 1) {
                  Para.Para32[0] = SK_RLMT_MODE_CLS;
                  Para.Para32[1] = 0;
                  (void)SkRlmtEvent(pAC, IoC, SK_RLMT_MODE_CHANGE, Para);
            }
            break;

      case SK_INIT_RUN:
            /* Ensure RLMT is set to one net. */
            if (pAC->Rlmt.NumNets > 1) {
                  Para.Para32[0] = 1;
                  Para.Para32[1] = -1;
                  SkRlmtEvtSetNets(pAC, IoC, Para);
            }

            for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
                  Random = SkOsGetTime(pAC);
                  *(SK_U32*)&pAC->Rlmt.Port[i].Random = *(SK_U32*)&Random;

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

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

                  if (pAC->Rlmt.Net[0].RlmtMode & SK_RLMT_CHECK_SEG) {
                        /* Add BPDU MC address. */
                        (void)SkAddrMcAdd(pAC, IoC, i, &BridgeMcAddr, SK_ADDR_PERMANENT);
                  }

                  (void)SkAddrMcUpdate(pAC, IoC, i);
            }

      VirtualMacAddressSet = SK_FALSE;
            /* Read virtual MAC address from Control Register File. */
            for (j = 0; j < SK_MAC_ADDR_LEN; j++) {
                  
            SK_IN8(IoC, B2_MAC_1 + j, &VirtualMacAddress.a[j]);
            VirtualMacAddressSet |= VirtualMacAddress.a[j];
            }
      
        PhysicalAMacAddressSet = SK_FALSE;
            /* Read physical MAC address for MAC A from Control Register File. */
            for (j = 0; j < SK_MAC_ADDR_LEN; j++) {
                  
            SK_IN8(IoC, B2_MAC_2 + j, &PhysicalAMacAddress.a[j]);
            PhysicalAMacAddressSet |= PhysicalAMacAddress.a[j];
            }

        /* check if the two mac addresses contain reasonable values */
        if (!VirtualMacAddressSet || !PhysicalAMacAddressSet) {

            pAC->Rlmt.RlmtOff = SK_TRUE;
        }

        /* if the two mac addresses are equal switch off the RLMT_PRE_LOOKAHEAD
           and the RLMT_LOOKAHEAD macros */
        else if (SK_ADDR_EQUAL(PhysicalAMacAddress.a, VirtualMacAddress.a)) {

            pAC->Rlmt.RlmtOff = SK_TRUE;
        }
            else {
                  pAC->Rlmt.RlmtOff = SK_FALSE;
            }
            break;

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


/******************************************************************************
 *
 *    SkRlmtBuildCheckChain - build the check chain
 *
 * Description:
 *    This routine builds the local check chain:
 *    - Each port that is up checks the next port.
 *    - The last port that is up checks the first port that is up.
 *
 * Notes:
 *    - Currently only local ports are considered when building the chain.
 *    - Currently the SuspectState is just reset;
 *      it would be better to save it ...
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    Nothing
 */
RLMT_STATIC void  SkRlmtBuildCheckChain(
SK_AC *pAC, /* Adapter Context */
SK_U32      NetIdx)     /* Net Number */
{
      SK_U32                  i;
      SK_U32                  NumMacsUp;
      SK_RLMT_PORT *    FirstMacUp;
      SK_RLMT_PORT *    PrevMacUp;

      FirstMacUp  = NULL;
      PrevMacUp   = NULL;
      
      if (!(pAC->Rlmt.Net[NetIdx].RlmtMode & SK_RLMT_CHECK_LOC_LINK)) {
            for (i = 0; i < pAC->Rlmt.Net[i].NumPorts; i++) {
                  pAC->Rlmt.Net[NetIdx].Port[i]->PortsChecked = 0;
            }
            return;     /* Done. */
      }
                  
      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SkRlmtBuildCheckChain.\n"))

      NumMacsUp = 0;

      for (i = 0; i < pAC->Rlmt.Net[NetIdx].NumPorts; i++) {
            pAC->Rlmt.Net[NetIdx].Port[i]->PortsChecked = 0;
            pAC->Rlmt.Net[NetIdx].Port[i]->PortsSuspect = 0;
            pAC->Rlmt.Net[NetIdx].Port[i]->CheckingState &=
                  ~(SK_RLMT_PCS_RX | SK_RLMT_PCS_TX);

            /*
             * If more than two links are detected we should consider
             * checking at least two other ports:
             * 1. the next port that is not LinkDown and
             * 2. the next port that is not PortDown.
             */
            if (!pAC->Rlmt.Net[NetIdx].Port[i]->LinkDown) {
                  if (NumMacsUp == 0) {
                        FirstMacUp = pAC->Rlmt.Net[NetIdx].Port[i];
                  }
                  else {
                        PrevMacUp->PortCheck[
                              pAC->Rlmt.Net[NetIdx].Port[i]->PortsChecked].CheckAddr =
                              pAC->Rlmt.Net[NetIdx].Port[i]->AddrPort->CurrentMacAddress;
                        PrevMacUp->PortCheck[
                              PrevMacUp->PortsChecked].SuspectTx = SK_FALSE;
                        PrevMacUp->PortsChecked++;
                  }
                  PrevMacUp = pAC->Rlmt.Net[NetIdx].Port[i];
                  NumMacsUp++;
            }
      }

      if (NumMacsUp > 1) {
            PrevMacUp->PortCheck[PrevMacUp->PortsChecked].CheckAddr =
                  FirstMacUp->AddrPort->CurrentMacAddress;
            PrevMacUp->PortCheck[PrevMacUp->PortsChecked].SuspectTx =
                  SK_FALSE;
            PrevMacUp->PortsChecked++;
      }

#ifdef DEBUG
      for (i = 0; i < pAC->Rlmt.Net[NetIdx].NumPorts; i++) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("Port %d checks %d other ports: %2X.\n", i,
                        pAC->Rlmt.Net[NetIdx].Port[i]->PortsChecked,
                        pAC->Rlmt.Net[NetIdx].Port[i]->PortCheck[0].CheckAddr.a[5]))
      }
#endif      /* DEBUG */

      return;
}     /* SkRlmtBuildCheckChain */


/******************************************************************************
 *
 *    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_U32            PortNumber, /* Sending port */
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;

#ifdef DEBUG
      SK_U8 CheckSrc  = 0;
      SK_U8 CheckDest = 0;
      
      for (i = 0; i < SK_MAC_ADDR_LEN; ++i) {
            CheckSrc  |= SrcAddr->a[i];
            CheckDest |= DestAddr->a[i];
      }

      if ((CheckSrc == 0) || (CheckDest == 0)) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_ERR,
                  ("SkRlmtBuildPacket: Invalid %s%saddr.\n",
                   (CheckSrc == 0 ? "Src" : ""), (CheckDest == 0 ? "Dest" : "")))
      }
#endif

      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[PortNumber].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;
            pMb->PortIdx = PortNumber;
            Length -= 14;
            SK_U16_TO_NETWORK_ORDER(Length, &pPacket->TypeLen[0]);

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

      return (pMb);
}     /* SkRlmtBuildPacket */


/******************************************************************************
 *
 *    SkRlmtBuildSpanningTreePacket - build spanning tree check packet
 *
 * Description:
 *    This routine sets up a BPDU packet for spanning tree check.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    NULL or pointer to RLMT mbuf
 */
RLMT_STATIC SK_MBUF     *SkRlmtBuildSpanningTreePacket(
SK_AC *pAC,       /* Adapter Context */
SK_IOC      IoC,        /* I/O Context */
SK_U32      PortNumber) /* Sending port */
{
      unsigned                i;
      SK_U16                        Length;
      SK_MBUF                       *pMb;
      SK_SPTREE_PACKET  *pSPacket;

      if ((pMb = SkDrvAllocRlmtMbuf(pAC, IoC, SK_RLMT_MAX_PACKET_SIZE)) !=
            NULL) {
            pSPacket = (SK_SPTREE_PACKET*)pMb->pData;
            for (i = 0; i < SK_MAC_ADDR_LEN; i++) {
                  pSPacket->DstAddr[i] = BridgeMcAddr.a[i];
                  pSPacket->SrcAddr[i] =
                        pAC->Addr.Port[PortNumber].CurrentMacAddress.a[i];
            }
            pSPacket->DSap = SK_RLMT_SPT_DSAP;
            pSPacket->SSap = SK_RLMT_SPT_SSAP;
            pSPacket->Ctrl = SK_RLMT_SPT_CTRL;

            pSPacket->ProtocolId[0] = SK_RLMT_SPT_PROTOCOL_ID0;
            pSPacket->ProtocolId[1] = SK_RLMT_SPT_PROTOCOL_ID1;
            pSPacket->ProtocolVersionId = SK_RLMT_SPT_PROTOCOL_VERSION_ID;
            pSPacket->BpduType = SK_RLMT_SPT_BPDU_TYPE;
            pSPacket->Flags = SK_RLMT_SPT_FLAGS;
            pSPacket->RootId[0] = SK_RLMT_SPT_ROOT_ID0;
            pSPacket->RootId[1] = SK_RLMT_SPT_ROOT_ID1;
            pSPacket->RootPathCost[0] = SK_RLMT_SPT_ROOT_PATH_COST0;
            pSPacket->RootPathCost[1] = SK_RLMT_SPT_ROOT_PATH_COST1;
            pSPacket->RootPathCost[2] = SK_RLMT_SPT_ROOT_PATH_COST2;
            pSPacket->RootPathCost[3] = SK_RLMT_SPT_ROOT_PATH_COST3;
            pSPacket->BridgeId[0] = SK_RLMT_SPT_BRIDGE_ID0;
            pSPacket->BridgeId[1] = SK_RLMT_SPT_BRIDGE_ID1;

            /*
             * Use logical MAC address as bridge ID and filter these packets
             * on receive.
             */
            for (i = 0; i < SK_MAC_ADDR_LEN; i++) {
                  pSPacket->BridgeId[i + 2] = pSPacket->RootId[i + 2] =
                        pAC->Addr.Net[pAC->Rlmt.Port[PortNumber].Net->NetNumber].
                              CurrentMacAddress.a[i];
            }
            pSPacket->PortId[0] = SK_RLMT_SPT_PORT_ID0;
            pSPacket->PortId[1] = SK_RLMT_SPT_PORT_ID1;
            pSPacket->MessageAge[0] = SK_RLMT_SPT_MSG_AGE0;
            pSPacket->MessageAge[1] = SK_RLMT_SPT_MSG_AGE1;
            pSPacket->MaxAge[0] = SK_RLMT_SPT_MAX_AGE0;
            pSPacket->MaxAge[1] = SK_RLMT_SPT_MAX_AGE1;
            pSPacket->HelloTime[0] = SK_RLMT_SPT_HELLO_TIME0;
            pSPacket->HelloTime[1] = SK_RLMT_SPT_HELLO_TIME1;
            pSPacket->ForwardDelay[0] = SK_RLMT_SPT_FWD_DELAY0;
            pSPacket->ForwardDelay[1] = SK_RLMT_SPT_FWD_DELAY1;

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

            pAC->Rlmt.Port[PortNumber].TxSpHelloReqCts++;
      }

      return (pMb);
}     /* SkRlmtBuildSpanningTreePacket */


/******************************************************************************
 *
 *    SkRlmtSend - build and send check packets
 *
 * Description:
 *    Depending on the RLMT state and the checking state, several packets
 *    are sent through the indicated port.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    Nothing.
 */
RLMT_STATIC void  SkRlmtSend(
SK_AC *pAC,       /* Adapter Context */
SK_IOC      IoC,        /* I/O Context */
SK_U32      PortNumber) /* Sending port */
{
      unsigned    j;
      SK_EVPARA   Para;
      SK_RLMT_PORT      *pRPort;

      pRPort = &pAC->Rlmt.Port[PortNumber];
      if (pAC->Rlmt.Port[PortNumber].Net->RlmtMode & SK_RLMT_CHECK_LOC_LINK) {
            if (pRPort->CheckingState & (SK_RLMT_PCS_TX | SK_RLMT_PCS_RX)) {
                  /* Port is suspicious. Send the RLMT packet to the RLMT mc addr. */
                  if ((Para.pParaPtr = SkRlmtBuildPacket(pAC, IoC, PortNumber,
                        SK_PACKET_ALIVE, &pAC->Addr.Port[PortNumber].CurrentMacAddress,
                        &SkRlmtMcAddr)) != NULL) {
                        SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para);
                  }
            }
            else {
                  /*
                   * Send a directed RLMT packet to all ports that are
                   * checked by the indicated port.
                   */
                  for (j = 0; j < pRPort->PortsChecked; j++) {
                        if ((Para.pParaPtr = SkRlmtBuildPacket(pAC, IoC, PortNumber,
                              SK_PACKET_ALIVE, &pAC->Addr.Port[PortNumber].CurrentMacAddress,
                              &pRPort->PortCheck[j].CheckAddr)) != NULL) {
                              SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para);
                        }
                  }
            }
      }

      if ((pAC->Rlmt.Port[PortNumber].Net->RlmtMode & SK_RLMT_CHECK_SEG) &&
            (pAC->Rlmt.Port[PortNumber].Net->CheckingState & SK_RLMT_RCS_SEND_SEG)) {
            /*
             * Send a BPDU packet to make a connected switch tell us
             * the correct root bridge.
             */
            if ((Para.pParaPtr =
                  SkRlmtBuildSpanningTreePacket(pAC, IoC, PortNumber)) != NULL) {
                  pAC->Rlmt.Port[PortNumber].Net->CheckingState &= ~SK_RLMT_RCS_SEND_SEG;
                  pRPort->RootIdSet = SK_FALSE;

                  SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para);
                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_TX,
                        ("SkRlmtSend: BPDU Packet on Port %u.\n", PortNumber))
            }
      }
      return;
}     /* SkRlmtSend */


/******************************************************************************
 *
 *    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_U32      PortNumber)       /* Port to check */
{
      SK_RLMT_PORT      *pRPort;
      SK_EVPARA         Para;

      pRPort = &pAC->Rlmt.Port[PortNumber];
      pRPort->PortNoRx = SK_FALSE;

      if ((pRPort->PortState == SK_RLMT_PS_DOWN) &&
            !(pRPort->CheckingState & SK_RLMT_PCS_TX)) {
            /*
             * 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;
            pRPort->GuTimeStamp = SkOsGetTime(pAC);
            Para.Para32[0] = PortNumber;
            Para.Para32[1] = (SK_U32)-1;
            SkTimerStart(pAC, IoC, &pRPort->UpTimer, SK_RLMT_PORTUP_TIM_VAL,
                  SKGE_RLMT, SK_RLMT_PORTUP_TIM, Para);
            pRPort->CheckingState &= ~SK_RLMT_PCS_RX;
            /* pAC->Rlmt.CheckSwitch = SK_TRUE; */
            SkRlmtCheckSwitch(pAC, IoC, pRPort->Net->NetNumber);
      }     /* PortDown && !SuspectTx */
      else if (pRPort->CheckingState & SK_RLMT_PCS_RX) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
                  ("SkRlmtPacketReceive: Stop bringing port down.\n"))
            SkTimerStop(pAC, IoC, &pRPort->DownRxTimer);
            pRPort->CheckingState &= ~SK_RLMT_PCS_RX;
            /* pAC->Rlmt.CheckSwitch = SK_TRUE; */
            SkRlmtCheckSwitch(pAC, IoC, pRPort->Net->NetNumber);
      }     /* PortGoingDown */

      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;
      unsigned                j;
      SK_U16                        PacketType;
      SK_U32                        PortNumber;
      SK_ADDR_PORT            *pAPort;
      SK_RLMT_PORT            *pRPort;
      SK_RLMT_PACKET          *pRPacket;
      SK_SPTREE_PACKET  *pSPacket;
      SK_EVPARA               Para;

      PortNumber  = pMb->PortIdx;
      pAPort = &pAC->Addr.Port[PortNumber];
      pRPort = &pAC->Rlmt.Port[PortNumber];

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
            ("SkRlmtPacketReceive: PortNumber == %d.\n", PortNumber))

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

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

      if ((pRPort->PacketsPerTimeSlot - pRPort->BpduPacketsPerTimeSlot) != 0) {
            SkRlmtPortReceives(pAC, IoC, PortNumber);
      }
      
      /* 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;
      }

      /* Check SuspectTx entries. */
      if (pRPort->PortsSuspect > 0) {
            for (j = 0; j < pRPort->PortsChecked; j++) {
                  if (pRPort->PortCheck[j].SuspectTx &&
                        SK_ADDR_EQUAL(
                              pRPacket->SrcAddr, pRPort->PortCheck[j].CheckAddr.a)) {
                        pRPort->PortCheck[j].SuspectTx = SK_FALSE;
                        pRPort->PortsSuspect--;
                        break;
                  }
            }
      }

      /* 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. */
#if 0
                  /* Build the check chain. */
                  SkRlmtBuildCheckChain(pAC);
#endif      /* 0 */

                  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[PortNumber].PromMode & SK_PROM_MODE_LLC) ||
                              SK_ADDR_EQUAL(
                                    pRPacket->DstAddr, pAPort->CurrentMacAddress.a)) {
                              /* Obviously we could send something. */
                              if (pRPort->CheckingState & SK_RLMT_PCS_TX) {
                                    pRPort->CheckingState &=  ~SK_RLMT_PCS_TX;
                                    SkTimerStop(pAC, IoC, &pRPort->DownTxTimer);
                              }

                              if ((pRPort->PortState == SK_RLMT_PS_DOWN) &&
                                    !(pRPort->CheckingState & SK_RLMT_PCS_RX)) {
                                    pRPort->PortState = SK_RLMT_PS_GOING_UP;
                                    pRPort->GuTimeStamp = SkOsGetTime(pAC);

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

                                    Para.Para32[0] = PortNumber;
                                    Para.Para32[1] = (SK_U32)-1;
                                    SkTimerStart(pAC, IoC, &pRPort->UpTimer,
                                          SK_RLMT_PORTUP_TIM_VAL, SKGE_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[PortNumber].CurrentMacAddress.a[i];
                        }
                        pRPacket->SSap |= LLC_COMMAND_RESPONSE_BIT;

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

            case SK_PACKET_CHECK_TX:
                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
                        ("SkRlmtPacketReceive: Check your tx line.\n"))

                  /* A port checking us requests us to check our tx line. */
                  pRPort->CheckingState |= SK_RLMT_PCS_TX;

                  /* Start PortDownTx timer. */
                  Para.Para32[0] = PortNumber;
                  Para.Para32[1] = (SK_U32)-1;
                  SkTimerStart(pAC, IoC, &pRPort->DownTxTimer,
                        SK_RLMT_PORTDOWN_TIM_VAL, SKGE_RLMT,
                        SK_RLMT_PORTDOWN_TX_TIM, Para);

                  SkDrvFreeRlmtMbuf(pAC, IoC, pMb);

                  if ((Para.pParaPtr = SkRlmtBuildPacket(pAC, IoC, PortNumber,
                        SK_PACKET_ALIVE, &pAC->Addr.Port[PortNumber].CurrentMacAddress,
                        &SkRlmtMcAddr)) != NULL) {
                        SkEventQueue(pAC, SKGE_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"))

                  /* Build the check chain. */
                  SkRlmtBuildCheckChain(pAC, pRPort->Net->NetNumber);
                  SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
                  break;

            default:
                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
                        ("SkRlmtPacketReceive: Unknown RLMT packet.\n"))

                  /* RA;:;: ??? */
                  SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
            }
      }
      else if (pSPacket->DSap == SK_RLMT_SPT_DSAP &&
            pSPacket->Ctrl == SK_RLMT_SPT_CTRL &&
            (pSPacket->SSap & ~LLC_COMMAND_RESPONSE_BIT) == SK_RLMT_SPT_SSAP) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
                  ("SkRlmtPacketReceive: BPDU Packet.\n"))

            /* Spanning Tree packet. */
            pRPort->RxSpHelloCts++;

            if (!SK_ADDR_EQUAL(&pSPacket->RootId[2], &pAC->Addr.Net[pAC->Rlmt.
                  Port[PortNumber].Net->NetNumber].CurrentMacAddress.a[0])) {
                  /*
                   * Check segmentation if a new root bridge is set and
                   * the segmentation check is not currently running.
                   */
                  if (!SK_ADDR_EQUAL(&pSPacket->RootId[2], &pRPort->Root.Id[2]) &&
                        (pAC->Rlmt.Port[PortNumber].Net->LinksUp > 1) &&
                        (pAC->Rlmt.Port[PortNumber].Net->RlmtMode & SK_RLMT_CHECK_SEG)
                        != 0 && (pAC->Rlmt.Port[PortNumber].Net->CheckingState &
                        SK_RLMT_RCS_SEG) == 0) {
                        pAC->Rlmt.Port[PortNumber].Net->CheckingState |=
                              SK_RLMT_RCS_START_SEG | SK_RLMT_RCS_SEND_SEG;
                  }

                  /* Store tree view of this port. */
                  for (i = 0; i < 8; i++) {
                        pRPort->Root.Id[i] = pSPacket->RootId[i];
                  }
                  pRPort->RootIdSet = SK_TRUE;

                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_DUMP,
                        ("Root ID %d: %02x %02x %02x %02x %02x %02x %02x %02x.\n",
                              PortNumber,
                              pRPort->Root.Id[0], pRPort->Root.Id[1],
                              pRPort->Root.Id[2], pRPort->Root.Id[3],
                              pRPort->Root.Id[4], pRPort->Root.Id[5],
                              pRPort->Root.Id[6], pRPort->Root.Id[7]))
            }

            SkDrvFreeRlmtMbuf(pAC, IoC, pMb);
            if ((pAC->Rlmt.Port[PortNumber].Net->CheckingState &
                  SK_RLMT_RCS_REPORT_SEG) != 0) {
                  SkRlmtCheckSeg(pAC, IoC, pAC->Rlmt.Port[PortNumber].Net->NetNumber);
            }
      }
      else {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_RX,
                  ("SkRlmtPacketReceive: Unknown Packet Type.\n"))

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


/******************************************************************************
 *
 *    SkRlmtCheckPort - check if a port works
 *
 * Description:
 *    This routine checks if a port whose link is up received something
 *    and if it seems to transmit successfully.
 *
 *    # PortState: PsInit, PsLinkDown, PsDown, PsGoingUp, PsUp
 *    # PortCheckingState (Bitfield): ChkTx, ChkRx, ChkSeg
 *    # RlmtCheckingState (Bitfield): ChkSeg, StartChkSeg, ReportSeg
 *
 *    if (Rx - RxBpdu == 0) { # No rx.
 *          if (state == PsUp) {
 *                PortCheckingState |= ChkRx
 *          }
 *          if (ModeCheckSeg && (Timeout ==
 *                TO_SHORTEN(RLMT_DEFAULT_TIMEOUT))) {
 *                RlmtCheckingState |= ChkSeg)
 *                PortCheckingState |= ChkSeg
 *          }
 *          NewTimeout = TO_SHORTEN(Timeout)
 *          if (NewTimeout < RLMT_MIN_TIMEOUT) {
 *                NewTimeout = RLMT_MIN_TIMEOUT
 *                PortState = PsDown
 *                ...
 *          }
 *    }
 *    else {      # something was received
 *          # Set counter to 0 at LinkDown?
 *          #   No - rx may be reported after LinkDown ???
 *          PortCheckingState &= ~ChkRx
 *          NewTimeout = RLMT_DEFAULT_TIMEOUT
 *          if (RxAck == 0) {
 *                possible reasons:
 *                is my tx line bad? --
 *                      send RLMT multicast and report
 *                      back internally? (only possible
 *                      between ports on same adapter)
 *          }
 *          if (RxChk == 0) {
 *                possible reasons:
 *                - tx line of port set to check me
 *                  maybe bad
 *                - no other port/adapter available or set
 *                  to check me
 *                - adapter checking me has a longer
 *                  timeout
 *                ??? anything that can be done here?
 *          }
 *    }
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    New timeout value.
 */
RLMT_STATIC SK_U32      SkRlmtCheckPort(
SK_AC *pAC,       /* Adapter Context */
SK_IOC      IoC,        /* I/O Context */
SK_U32      PortNumber) /* Port to check */
{
      unsigned          i;
      SK_U32                  NewTimeout;
      SK_RLMT_PORT      *pRPort;
      SK_EVPARA         Para;

      pRPort = &pAC->Rlmt.Port[PortNumber];

      if ((pRPort->PacketsPerTimeSlot - pRPort->BpduPacketsPerTimeSlot) == 0) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SkRlmtCheckPort %d: No (%d) receives in last time slot.\n",
                        PortNumber, pRPort->PacketsPerTimeSlot))

            /*
             * Check segmentation if there was no receive at least twice
             * in a row (PortNoRx is already set) and the segmentation
             * check is not currently running.
             */

            if (pRPort->PortNoRx && (pAC->Rlmt.Port[PortNumber].Net->LinksUp > 1) &&
                  (pAC->Rlmt.Port[PortNumber].Net->RlmtMode & SK_RLMT_CHECK_SEG) &&
                  !(pAC->Rlmt.Port[PortNumber].Net->CheckingState & SK_RLMT_RCS_SEG)) {
                  pAC->Rlmt.Port[PortNumber].Net->CheckingState |=
                        SK_RLMT_RCS_START_SEG | SK_RLMT_RCS_SEND_SEG;
            }

            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SkRlmtCheckPort: PortsSuspect %d, PcsRx %d.\n",
                        pRPort->PortsSuspect, pRPort->CheckingState & SK_RLMT_PCS_RX))

            if (pRPort->PortState != SK_RLMT_PS_DOWN) {
                  NewTimeout = TO_SHORTEN(pAC->Rlmt.Port[PortNumber].Net->TimeoutValue);
                  if (NewTimeout < SK_RLMT_MIN_TO_VAL) {
                        NewTimeout = SK_RLMT_MIN_TO_VAL;
                  }

                  if (!(pRPort->CheckingState & SK_RLMT_PCS_RX)) {
                        Para.Para32[0] = PortNumber;
                        pRPort->CheckingState |= SK_RLMT_PCS_RX;

                        /*
                         * What shall we do if the port checked by this one receives
                         * our request frames?  What's bad - our rx line or his tx line?
                         */
                        Para.Para32[1] = (SK_U32)-1;
                        SkTimerStart(pAC, IoC, &pRPort->DownRxTimer,
                              SK_RLMT_PORTDOWN_TIM_VAL, SKGE_RLMT,
                              SK_RLMT_PORTDOWN_RX_TIM, Para);

                        for (i = 0; i < pRPort->PortsChecked; i++) {
                              if (pRPort->PortCheck[i].SuspectTx) {
                                    continue;
                              }
                              pRPort->PortCheck[i].SuspectTx = SK_TRUE;
                              pRPort->PortsSuspect++;
                              if ((Para.pParaPtr =
                                    SkRlmtBuildPacket(pAC, IoC, PortNumber, SK_PACKET_CHECK_TX,
                                          &pAC->Addr.Port[PortNumber].CurrentMacAddress,
                                          &pRPort->PortCheck[i].CheckAddr)) != NULL) {
                                    SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para);
                              }
                        }
                  }
            }
            else {      /* PortDown -- or all partners suspect. */
                  NewTimeout = SK_RLMT_DEF_TO_VAL;
            }
            pRPort->PortNoRx = SK_TRUE;
      }
      else {      /* A non-BPDU packet was received. */
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SkRlmtCheckPort %d: %d (%d) receives in last time slot.\n",
                        PortNumber,
                        pRPort->PacketsPerTimeSlot - pRPort->BpduPacketsPerTimeSlot,
                        pRPort->PacketsPerTimeSlot))
            
            SkRlmtPortReceives(pAC, IoC, PortNumber);
            if (pAC->Rlmt.CheckSwitch) {
                  SkRlmtCheckSwitch(pAC, IoC, pRPort->Net->NetNumber);
            }

            NewTimeout = SK_RLMT_DEF_TO_VAL;
      }

      return (NewTimeout);
}     /* SkRlmtCheckPort */


/******************************************************************************
 *
 *    SkRlmtSelectBcRx - select new active port, criteria 1 (CLP)
 *
 * Description:
 *    This routine selects the port that received a broadcast frame
 *    substantially later than all other ports.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    SK_BOOL
 */
RLMT_STATIC SK_BOOL     SkRlmtSelectBcRx(
SK_AC *pAC,       /* Adapter Context */
SK_IOC      IoC,        /* I/O Context */
SK_U32      Active,           /* Active port */
SK_U32      PrefPort,   /* Preferred port */
SK_U32      *pSelect)   /* New active port */
{
      SK_U64            BcTimeStamp;
      SK_U32            i;
      SK_BOOL           PortFound;

      BcTimeStamp = 0;  /* Not totally necessary, but feeling better. */
      PortFound = SK_FALSE;
      
      /* Select port with the latest TimeStamp. */
      for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {

            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("TimeStamp Port %d (Down: %d, NoRx: %d): %08x %08x.\n",
                        i,
                        pAC->Rlmt.Port[i].PortDown, pAC->Rlmt.Port[i].PortNoRx,
                        *((SK_U32*)(&pAC->Rlmt.Port[i].BcTimeStamp) + OFFS_HI32),
                        *((SK_U32*)(&pAC->Rlmt.Port[i].BcTimeStamp) + OFFS_LO32)))

            if (!pAC->Rlmt.Port[i].PortDown && !pAC->Rlmt.Port[i].PortNoRx) {
                  if (!PortFound || pAC->Rlmt.Port[i].BcTimeStamp > BcTimeStamp) {
                        BcTimeStamp = pAC->Rlmt.Port[i].BcTimeStamp;
                        *pSelect = i;
                        PortFound = SK_TRUE;
                  }
            }
      }

      if (PortFound) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("Port %d received the last broadcast.\n", *pSelect))

            /* Look if another port's time stamp is similar. */
            for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
                  if (i == *pSelect) {
                        continue;
                  }
                  if (!pAC->Rlmt.Port[i].PortDown && !pAC->Rlmt.Port[i].PortNoRx &&
                        (pAC->Rlmt.Port[i].BcTimeStamp >
                         BcTimeStamp - SK_RLMT_BC_DELTA ||
                        pAC->Rlmt.Port[i].BcTimeStamp +
                         SK_RLMT_BC_DELTA > BcTimeStamp)) {
                        PortFound = SK_FALSE;
                        
                        SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                              ("Port %d received a broadcast at a similar time.\n", i))
                        break;
                  }
            }
      }

#ifdef DEBUG
      if (PortFound) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SK_RLMT_SELECT_BCRX found Port %d receiving the substantially "
                   "latest broadcast (%u).\n",
                        *pSelect,
                        BcTimeStamp - pAC->Rlmt.Port[1 - *pSelect].BcTimeStamp))
      }
#endif      /* DEBUG */

      return (PortFound);
}     /* SkRlmtSelectBcRx */


/******************************************************************************
 *
 *    SkRlmtSelectNotSuspect - select new active port, criteria 2 (CLP)
 *
 * Description:
 *    This routine selects a good port (it is PortUp && !SuspectRx).
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    SK_BOOL
 */
RLMT_STATIC SK_BOOL     SkRlmtSelectNotSuspect(
SK_AC *pAC,       /* Adapter Context */
SK_IOC      IoC,        /* I/O Context */
SK_U32      Active,           /* Active port */
SK_U32      PrefPort,   /* Preferred port */
SK_U32      *pSelect)   /* New active port */
{
      SK_U32            i;
      SK_BOOL           PortFound;

      PortFound = SK_FALSE;

      /* Select first port that is PortUp && !SuspectRx. */
      for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
            if (!pAC->Rlmt.Port[i].PortDown &&
                  !(pAC->Rlmt.Port[i].CheckingState & SK_RLMT_PCS_RX)) {
                  *pSelect = i;
                  if (!pAC->Rlmt.Port[Active].PortDown &&
                        !(pAC->Rlmt.Port[Active].CheckingState & SK_RLMT_PCS_RX)) {
                        *pSelect = Active;
                  }
                  if (!pAC->Rlmt.Port[PrefPort].PortDown &&
                        !(pAC->Rlmt.Port[PrefPort].CheckingState & SK_RLMT_PCS_RX)) {
                        *pSelect = PrefPort;
                  }
                  PortFound = SK_TRUE;
                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                        ("SK_RLMT_SELECT_NOTSUSPECT found Port %d up and not check RX.\n",
                              *pSelect))
                  break;
            }
      }
      return (PortFound);
}     /* SkRlmtSelectNotSuspect */


/******************************************************************************
 *
 *    SkRlmtSelectUp - select new active port, criteria 3, 4 (CLP)
 *
 * Description:
 *    This routine selects a port that is up.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    SK_BOOL
 */
RLMT_STATIC SK_BOOL     SkRlmtSelectUp(
SK_AC *pAC,             /* Adapter Context */
SK_IOC      IoC,              /* I/O Context */
SK_U32      Active,                 /* Active port */
SK_U32      PrefPort,         /* Preferred port */
SK_U32      *pSelect,         /* New active port */
SK_BOOL     AutoNegDone)      /* Successfully auto-negotiated? */
{
      SK_U32            i;
      SK_BOOL           PortFound;

      PortFound = SK_FALSE;

      /* Select first port that is PortUp. */
      for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
            if (pAC->Rlmt.Port[i].PortState == SK_RLMT_PS_UP &&
                  pAC->GIni.GP[i].PAutoNegFail != AutoNegDone) {
                  *pSelect = i;
                  if (pAC->Rlmt.Port[Active].PortState == SK_RLMT_PS_UP &&
                        pAC->GIni.GP[Active].PAutoNegFail != AutoNegDone) {
                        *pSelect = Active;
                  }
                  if (pAC->Rlmt.Port[PrefPort].PortState == SK_RLMT_PS_UP &&
                        pAC->GIni.GP[PrefPort].PAutoNegFail != AutoNegDone) {
                        *pSelect = PrefPort;
                  }
                  PortFound = SK_TRUE;
                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                        ("SK_RLMT_SELECT_UP found Port %d up.\n", *pSelect))
                  break;
            }
      }
      return (PortFound);
}     /* SkRlmtSelectUp */


/******************************************************************************
 *
 *    SkRlmtSelectGoingUp - select new active port, criteria 5, 6 (CLP)
 *
 * Description:
 *    This routine selects the port that is going up for the longest time.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    SK_BOOL
 */
RLMT_STATIC SK_BOOL     SkRlmtSelectGoingUp(
SK_AC *pAC,             /* Adapter Context */
SK_IOC      IoC,              /* I/O Context */
SK_U32      Active,                 /* Active port */
SK_U32      PrefPort,         /* Preferred port */
SK_U32      *pSelect,         /* New active port */
SK_BOOL     AutoNegDone)      /* Successfully auto-negotiated? */
{
      SK_U64            GuTimeStamp;
      SK_U32            i;
      SK_BOOL           PortFound;

      GuTimeStamp = 0;
      PortFound = SK_FALSE;

      /* Select port that is PortGoingUp for the longest time. */
      for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
            if (pAC->Rlmt.Port[i].PortState == SK_RLMT_PS_GOING_UP &&
                  pAC->GIni.GP[i].PAutoNegFail != AutoNegDone) {
                  GuTimeStamp = pAC->Rlmt.Port[i].GuTimeStamp;
                  *pSelect = i;
                  PortFound = SK_TRUE;
                  break;
            }
      }

      if (!PortFound) {
            return (SK_FALSE);
      }

      for (i = *pSelect + 1; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
            if (pAC->Rlmt.Port[i].PortState == SK_RLMT_PS_GOING_UP &&
                  pAC->Rlmt.Port[i].GuTimeStamp < GuTimeStamp &&
                  pAC->GIni.GP[i].PAutoNegFail != AutoNegDone) {
                  GuTimeStamp = pAC->Rlmt.Port[i].GuTimeStamp;
                  *pSelect = i;
            }
      }

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_SELECT_GOINGUP found Port %d going up.\n", *pSelect))
      return (SK_TRUE);
}     /* SkRlmtSelectGoingUp */


/******************************************************************************
 *
 *    SkRlmtSelectDown - select new active port, criteria 7, 8 (CLP)
 *
 * Description:
 *    This routine selects a port that is down.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    SK_BOOL
 */
RLMT_STATIC SK_BOOL     SkRlmtSelectDown(
SK_AC *pAC,             /* Adapter Context */
SK_IOC      IoC,              /* I/O Context */
SK_U32      Active,                 /* Active port */
SK_U32      PrefPort,         /* Preferred port */
SK_U32      *pSelect,         /* New active port */
SK_BOOL     AutoNegDone)      /* Successfully auto-negotiated? */
{
      SK_U32            i;
      SK_BOOL           PortFound;

      PortFound = SK_FALSE;

      /* Select first port that is PortDown. */
      for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
            if (pAC->Rlmt.Port[i].PortState == SK_RLMT_PS_DOWN &&
                  pAC->GIni.GP[i].PAutoNegFail != AutoNegDone) {
                  *pSelect = i;
                  if (pAC->Rlmt.Port[Active].PortState == SK_RLMT_PS_DOWN &&
                        pAC->GIni.GP[Active].PAutoNegFail != AutoNegDone) {
                        *pSelect = Active;
                  }
                  if (pAC->Rlmt.Port[PrefPort].PortState == SK_RLMT_PS_DOWN &&
                        pAC->GIni.GP[PrefPort].PAutoNegFail != AutoNegDone) {
                        *pSelect = PrefPort;
                  }
                  PortFound = SK_TRUE;
                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                        ("SK_RLMT_SELECT_DOWN found Port %d down.\n", *pSelect))
                  break;
            }
      }
      return (PortFound);
}     /* SkRlmtSelectDown */


/******************************************************************************
 *
 *    SkRlmtCheckSwitch - select new active port and switch to it
 *
 * Description:
 *    This routine decides which port should be the active one and queues
 *    port switching if necessary.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    Nothing.
 */
RLMT_STATIC void  SkRlmtCheckSwitch(
SK_AC *pAC, /* Adapter Context */
SK_IOC      IoC,  /* I/O Context */
SK_U32      NetIdx)     /* Net index */
{
      SK_EVPARA   Para;
      SK_U32            Active;
      SK_U32            PrefPort;
      SK_U32            i;
      SK_BOOL           PortFound;

      Active = pAC->Rlmt.Net[NetIdx].ActivePort;      /* Index of active port. */
      PrefPort = pAC->Rlmt.Net[NetIdx].PrefPort;      /* Index of preferred port. */
      PortFound = SK_FALSE;
      pAC->Rlmt.CheckSwitch = SK_FALSE;

#if 0 /* RW 2001/10/18 - active port becomes always prefered one */
      if (pAC->Rlmt.Net[NetIdx].Preference == 0xFFFFFFFF) { /* Automatic */
            /* disable auto-fail back */
            PrefPort = Active;
      }
#endif

      if (pAC->Rlmt.Net[NetIdx].LinksUp == 0) {
            /* Last link went down - shut down the net. */
            pAC->Rlmt.Net[NetIdx].RlmtState = SK_RLMT_RS_NET_DOWN;
            Para.Para32[0] = SK_RLMT_NET_DOWN_TEMP;
            Para.Para32[1] = NetIdx;
            SkEventQueue(pAC, SKGE_DRV, SK_DRV_NET_DOWN, Para);

            Para.Para32[0] = pAC->Rlmt.Net[NetIdx].
                  Port[pAC->Rlmt.Net[NetIdx].ActivePort]->PortNumber;
            Para.Para32[1] = NetIdx;
            SkEventQueue(pAC, SKGE_PNMI, SK_PNMI_EVT_RLMT_ACTIVE_DOWN, Para);
            return;
      }     /* pAC->Rlmt.LinksUp == 0 */
      else if (pAC->Rlmt.Net[NetIdx].LinksUp == 1 &&
            pAC->Rlmt.Net[NetIdx].RlmtState == SK_RLMT_RS_NET_DOWN) {
            /* First link came up - get the net up. */
            pAC->Rlmt.Net[NetIdx].RlmtState = SK_RLMT_RS_NET_UP;

            /*
             * If pAC->Rlmt.ActivePort != Para.Para32[0],
             * the DRV switches to the port that came up.
             */
            for (i = 0; i < pAC->Rlmt.Net[NetIdx].NumPorts; i++) {
                  if (!pAC->Rlmt.Net[NetIdx].Port[i]->LinkDown) {
                        if (!pAC->Rlmt.Net[NetIdx].Port[Active]->LinkDown) {
                              i = Active;
                        }
                        if (!pAC->Rlmt.Net[NetIdx].Port[PrefPort]->LinkDown) {
                              i = PrefPort;
                        }
                        PortFound = SK_TRUE;
                        break;
                  }
            }

            if (PortFound) {
                  Para.Para32[0] = pAC->Rlmt.Net[NetIdx].Port[i]->PortNumber;
                  Para.Para32[1] = NetIdx;
                  SkEventQueue(pAC, SKGE_PNMI, SK_PNMI_EVT_RLMT_ACTIVE_UP, Para);

                  pAC->Rlmt.Net[NetIdx].ActivePort = i;
                  Para.Para32[0] = pAC->Rlmt.Net[NetIdx].Port[i]->PortNumber;
                  Para.Para32[1] = NetIdx;
                  SkEventQueue(pAC, SKGE_DRV, SK_DRV_NET_UP, Para);

                  if ((pAC->Rlmt.Net[NetIdx].RlmtMode & SK_RLMT_TRANSPARENT) == 0 &&
                        (Para.pParaPtr = SkRlmtBuildPacket(pAC, IoC,
                        pAC->Rlmt.Net[NetIdx].Port[i]->PortNumber,
                        SK_PACKET_ANNOUNCE, &pAC->Addr.Net[NetIdx].
                        CurrentMacAddress, &SkRlmtMcAddr)) != NULL) {
                        /*
                         * Send announce packet to RLMT multicast address to force
                         * switches to learn the new location of the logical MAC address.
                         */
                        SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para);
                  }
            }
            else {
                  SK_ERR_LOG(pAC, SK_ERRCL_SW, SKERR_RLMT_E007, SKERR_RLMT_E007_MSG);
            }

            return;
      }     /* LinksUp == 1 && RlmtState == SK_RLMT_RS_NET_DOWN */
      else {      /* Cannot be reached in dual-net mode. */
            Para.Para32[0] = Active;

            /*
             * Preselection:
             *    If RLMT Mode != CheckLinkState
             *          select port that received a broadcast frame substantially later
             *          than all other ports
             *    else select first port that is not SuspectRx
             *    else select first port that is PortUp
             *    else select port that is PortGoingUp for the longest time
             *    else select first port that is PortDown
             *    else stop.
             *
             * For the preselected port:
             *    If ActivePort is equal in quality, select ActivePort.
             *
             *    If PrefPort is equal in quality, select PrefPort.
             *
             *    If ActivePort != SelectedPort,
             *          If old ActivePort is LinkDown,
             *                SwitchHard
             *          else
             *                SwitchSoft
             */
            /* check of ChgBcPrio flag added */
            if ((pAC->Rlmt.Net[0].RlmtMode != SK_RLMT_MODE_CLS) &&
                  (!pAC->Rlmt.Net[0].ChgBcPrio)) {
                  
                  if (!PortFound) {
                        PortFound = SkRlmtSelectBcRx(
                              pAC, IoC, Active, PrefPort, &Para.Para32[1]);
                  }

                  if (!PortFound) {
                        PortFound = SkRlmtSelectNotSuspect(
                              pAC, IoC, Active, PrefPort, &Para.Para32[1]);
                  }
            }     /* pAC->Rlmt.RlmtMode != SK_RLMT_MODE_CLS */

            /* with changed priority for last broadcast received */
            if ((pAC->Rlmt.Net[0].RlmtMode != SK_RLMT_MODE_CLS) &&
                  (pAC->Rlmt.Net[0].ChgBcPrio)) {
                  if (!PortFound) {
                        PortFound = SkRlmtSelectNotSuspect(
                              pAC, IoC, Active, PrefPort, &Para.Para32[1]);
                  }

                  if (!PortFound) {
                        PortFound = SkRlmtSelectBcRx(
                              pAC, IoC, Active, PrefPort, &Para.Para32[1]);
                  }
            }     /* pAC->Rlmt.RlmtMode != SK_RLMT_MODE_CLS */

            if (!PortFound) {
                  PortFound = SkRlmtSelectUp(
                        pAC, IoC, Active, PrefPort, &Para.Para32[1], AUTONEG_SUCCESS);
            }

            if (!PortFound) {
                  PortFound = SkRlmtSelectUp(
                        pAC, IoC, Active, PrefPort, &Para.Para32[1], AUTONEG_FAILED);
            }

            if (!PortFound) {
                  PortFound = SkRlmtSelectGoingUp(
                        pAC, IoC, Active, PrefPort, &Para.Para32[1], AUTONEG_SUCCESS);
            }

            if (!PortFound) {
                  PortFound = SkRlmtSelectGoingUp(
                        pAC, IoC, Active, PrefPort, &Para.Para32[1], AUTONEG_FAILED);
            }

            if (pAC->Rlmt.Net[0].RlmtMode != SK_RLMT_MODE_CLS) {
                  if (!PortFound) {
                        PortFound = SkRlmtSelectDown(pAC, IoC,
                              Active, PrefPort, &Para.Para32[1], AUTONEG_SUCCESS);
                  }

                  if (!PortFound) {
                        PortFound = SkRlmtSelectDown(pAC, IoC,
                              Active, PrefPort, &Para.Para32[1], AUTONEG_FAILED);
                  }
            }     /* pAC->Rlmt.RlmtMode != SK_RLMT_MODE_CLS */

            if (PortFound) {

                  if (Para.Para32[1] != Active) {
                        SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                              ("Active: %d, Para1: %d.\n", Active, Para.Para32[1]))
                        pAC->Rlmt.Net[NetIdx].ActivePort = Para.Para32[1];
                        Para.Para32[0] = pAC->Rlmt.Net[NetIdx].
                              Port[Para.Para32[0]]->PortNumber;
                        Para.Para32[1] = pAC->Rlmt.Net[NetIdx].
                              Port[Para.Para32[1]]->PortNumber;
                        SK_HWAC_LINK_LED(pAC, IoC, Para.Para32[1], SK_LED_ACTIVE);
                        if (pAC->Rlmt.Port[Active].LinkDown) {
                              SkEventQueue(pAC, SKGE_DRV, SK_DRV_SWITCH_HARD, Para);
                        }
                        else {
                              SK_HWAC_LINK_LED(pAC, IoC, Para.Para32[0], SK_LED_STANDBY);
                              SkEventQueue(pAC, SKGE_DRV, SK_DRV_SWITCH_SOFT, Para);
                        }
                        Para.Para32[1] = NetIdx;
                        Para.Para32[0] =
                              pAC->Rlmt.Net[NetIdx].Port[Para.Para32[0]]->PortNumber;
                        SkEventQueue(pAC, SKGE_PNMI, SK_PNMI_EVT_RLMT_ACTIVE_DOWN, Para);
                        Para.Para32[0] = pAC->Rlmt.Net[NetIdx].
                              Port[pAC->Rlmt.Net[NetIdx].ActivePort]->PortNumber;
                        SkEventQueue(pAC, SKGE_PNMI, SK_PNMI_EVT_RLMT_ACTIVE_UP, Para);
                        if ((pAC->Rlmt.Net[NetIdx].RlmtMode & SK_RLMT_TRANSPARENT) == 0 &&
                              (Para.pParaPtr = SkRlmtBuildPacket(pAC, IoC, Para.Para32[0],
                              SK_PACKET_ANNOUNCE, &pAC->Addr.Net[NetIdx].CurrentMacAddress,
                              &SkRlmtMcAddr)) != NULL) {
                              /*
                               * Send announce packet to RLMT multicast address to force
                               * switches to learn the new location of the logical
                               * MAC address.
                               */
                              SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para);
                        }     /* (Para.pParaPtr = SkRlmtBuildPacket(...)) != NULL */
                  }     /* Para.Para32[1] != Active */
            }     /* PortFound */
            else {
                  SK_ERR_LOG(pAC, SK_ERRCL_SW, SKERR_RLMT_E004, SKERR_RLMT_E004_MSG);
            }
      }     /* LinksUp > 1 || LinksUp == 1 && RlmtState != SK_RLMT_RS_NET_DOWN */
      return;
}     /* SkRlmtCheckSwitch */


/******************************************************************************
 *
 *    SkRlmtCheckSeg - Report if segmentation is detected
 *
 * Description:
 *    This routine checks if the ports see different root bridges and reports
 *    segmentation in such a case.
 *
 * Context:
 *    runtime, pageable?
 *
 * Returns:
 *    Nothing.
 */
RLMT_STATIC void  SkRlmtCheckSeg(
SK_AC *pAC, /* Adapter Context */
SK_IOC      IoC,  /* I/O Context */
SK_U32      NetIdx)     /* Net number */
{
      SK_EVPARA   Para;
      SK_RLMT_NET *pNet;
      SK_U32            i, j;
      SK_BOOL           Equal;

      pNet = &pAC->Rlmt.Net[NetIdx];
      pNet->RootIdSet = SK_FALSE;
      Equal = SK_TRUE;

      for (i = 0; i < pNet->NumPorts; i++) {
            if (pNet->Port[i]->LinkDown || !pNet->Port[i]->RootIdSet) {
                  continue;
            }

            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_DUMP,
                  ("Root ID %d: %02x %02x %02x %02x %02x %02x %02x %02x.\n", i,
                        pNet->Port[i]->Root.Id[0], pNet->Port[i]->Root.Id[1],
                        pNet->Port[i]->Root.Id[2], pNet->Port[i]->Root.Id[3],
                        pNet->Port[i]->Root.Id[4], pNet->Port[i]->Root.Id[5],
                        pNet->Port[i]->Root.Id[6], pNet->Port[i]->Root.Id[7]))

            if (!pNet->RootIdSet) {
                  pNet->Root = pNet->Port[i]->Root;
                  pNet->RootIdSet = SK_TRUE;
                  continue;
            }

            for (j = 0; j < 8; j ++) {
                  Equal &= pNet->Port[i]->Root.Id[j] == pNet->Root.Id[j];
                  if (!Equal) {
                        break;
                  }
            }
            
            if (!Equal) {
                  SK_ERR_LOG(pAC, SK_ERRCL_COMM, SKERR_RLMT_E005, SKERR_RLMT_E005_MSG);
                  Para.Para32[0] = NetIdx;
                  Para.Para32[1] = (SK_U32)-1;
                  SkEventQueue(pAC, SKGE_PNMI, SK_PNMI_EVT_RLMT_SEGMENTATION, Para);

                  pNet->CheckingState &= ~SK_RLMT_RCS_REPORT_SEG;

                  /* 2000-03-06 RA: New. */
                  Para.Para32[0] = NetIdx;
                  Para.Para32[1] = (SK_U32)-1;
                  SkTimerStart(pAC, IoC, &pNet->SegTimer, SK_RLMT_SEG_TO_VAL,
                        SKGE_RLMT, SK_RLMT_SEG_TIM, Para);
                  break;
            }
      }     /* for (i = 0; i < pNet->NumPorts; i++) */

      /* 2000-03-06 RA: Moved here. */
      /* Segmentation check not running anymore. */
      pNet->CheckingState &= ~SK_RLMT_RCS_SEG;

}     /* SkRlmtCheckSeg */


/******************************************************************************
 *
 *    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_U32      PortNumber) /* Port number */
{
      SK_EVPARA   Para;

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


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

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_PORTSTART_TIMEOUT Port %d Event BEGIN.\n", Para.Para32[0]))

            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_PORTSTART_TIMEOUT Event EMPTY.\n"))
            return;
      }

      /*
       * Used to start non-preferred ports if the preferred one
       * does not come up.
       * This timeout needs only be set when starting the first
       * (preferred) port.
       */
      if (pAC->Rlmt.Port[Para.Para32[0]].LinkDown) {
            /* PORT_START failed. */
            for (i = 0; i < pAC->Rlmt.Port[Para.Para32[0]].Net->NumPorts; i++) {
                  if (!pAC->Rlmt.Port[Para.Para32[0]].Net->Port[i]->PortStarted) {
                        SkRlmtPortStart(pAC, IoC,
                              pAC->Rlmt.Port[Para.Para32[0]].Net->Port[i]->PortNumber);
                  }
            }
      }

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


/******************************************************************************
 *
 *    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_U32                  i;
      SK_RLMT_PORT      *pRPort;
      SK_EVPARA         Para2;

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_LINK_UP Port %d Event BEGIN.\n", Para.Para32[0]))

      pRPort = &pAC->Rlmt.Port[Para.Para32[0]];
      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->GuTimeStamp = SkOsGetTime(pAC);
      pRPort->BcTimeStamp = 0;
      pRPort->Net->LinksUp++;
      if (pRPort->Net->LinksUp == 1) {
            SK_HWAC_LINK_LED(pAC, IoC, Para.Para32[0], SK_LED_ACTIVE);
      }
      else {
            SK_HWAC_LINK_LED(pAC, IoC, Para.Para32[0], SK_LED_STANDBY);
      }

      for (i = 0; i < pRPort->Net->NumPorts; i++) {
            if (!pRPort->Net->Port[i]->PortStarted) {
                  SkRlmtPortStart(pAC, IoC, pRPort->Net->Port[i]->PortNumber);
            }
      }

      SkRlmtCheckSwitch(pAC, IoC, pRPort->Net->NetNumber);

      if (pRPort->Net->LinksUp >= 2) {
            if (pRPort->Net->RlmtMode & SK_RLMT_CHECK_LOC_LINK) {
                  /* Build the check chain. */
                  SkRlmtBuildCheckChain(pAC, pRPort->Net->NetNumber);
            }
      }

      /* If the first link comes up, start the periodical RLMT timeout. */
      if (pRPort->Net->NumPorts > 1 && pRPort->Net->LinksUp == 1 &&
            (pRPort->Net->RlmtMode & SK_RLMT_CHECK_OTHERS) != 0) {
            Para2.Para32[0] = pRPort->Net->NetNumber;
            Para2.Para32[1] = (SK_U32)-1;
            SkTimerStart(pAC, IoC, &pRPort->Net->LocTimer,
                  pRPort->Net->TimeoutValue, SKGE_RLMT, SK_RLMT_TIM, Para2);
      }

      Para2 = Para;
      Para2.Para32[1] = (SK_U32)-1;
      SkTimerStart(pAC, IoC, &pRPort->UpTimer, SK_RLMT_PORTUP_TIM_VAL,
            SKGE_RLMT, SK_RLMT_PORTUP_TIM, Para2);
      
      /* Later: if (pAC->Rlmt.RlmtMode & SK_RLMT_CHECK_LOC_LINK) && */
      if ((pRPort->Net->RlmtMode & SK_RLMT_TRANSPARENT) == 0 &&
            (pRPort->Net->RlmtMode & SK_RLMT_CHECK_LINK) != 0 &&
            (Para2.pParaPtr =
                  SkRlmtBuildPacket(pAC, IoC, Para.Para32[0], SK_PACKET_ANNOUNCE,
                  &pAC->Addr.Port[Para.Para32[0]].CurrentMacAddress, &SkRlmtMcAddr)
            ) != NULL) {
            /* Send "new" packet to RLMT multicast address. */
            SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para2);
      }

      if (pRPort->Net->RlmtMode & SK_RLMT_CHECK_SEG) {
            if ((Para2.pParaPtr =
                  SkRlmtBuildSpanningTreePacket(pAC, IoC, Para.Para32[0])) != NULL) {
                  pAC->Rlmt.Port[Para.Para32[0]].RootIdSet = SK_FALSE;
                  pRPort->Net->CheckingState |=
                        SK_RLMT_RCS_SEG | SK_RLMT_RCS_REPORT_SEG;

                  SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para2);

                  Para.Para32[1] = (SK_U32)-1;
                  SkTimerStart(pAC, IoC, &pRPort->Net->SegTimer,
                        SK_RLMT_SEG_TO_VAL, SKGE_RLMT, SK_RLMT_SEG_TIM, Para);
            }
      }

      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 Port %d Event BEGIN.\n", Para.Para32[0]))

      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[Para.Para32[0]];
      if (pRPort->LinkDown || (pRPort->PortState == SK_RLMT_PS_UP)) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SK_RLMT_PORTUP_TIM Port %d Event EMPTY.\n", Para.Para32[0]))
            return;
      }

      pRPort->PortDown = SK_FALSE;
      pRPort->PortState = SK_RLMT_PS_UP;
      pRPort->Net->PortsUp++;
      if (pRPort->Net->RlmtState != SK_RLMT_RS_INIT) {
            if (pAC->Rlmt.NumNets <= 1) {
                  SkRlmtCheckSwitch(pAC, IoC, pRPort->Net->NetNumber);
            }
            SkEventQueue(pAC, SKGE_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* Port %d Event (%d) BEGIN.\n",
                  Para.Para32[0], 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[Para.Para32[0]];
      if (!pRPort->PortStarted || (Event == SK_RLMT_PORTDOWN_TX_TIM &&
            !(pRPort->CheckingState & SK_RLMT_PCS_TX))) {
            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, SKGE_PNMI, SK_PNMI_EVT_RLMT_PORT_DOWN, Para);
      }

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

      /*
       * RA;:;: To be checked:
       * - actions at RLMT_STOP: We should not switch anymore.
       */
      if (pRPort->Net->RlmtState != SK_RLMT_RS_INIT) {
            if (Para.Para32[0] ==
                  pRPort->Net->Port[pRPort->Net->ActivePort]->PortNumber) {
                  /* Active Port went down. */
                  SkRlmtCheckSwitch(pAC, IoC, pRPort->Net->NetNumber);
            }
      }

      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[Para.Para32[0]];
      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_LINK_DOWN Port %d Event BEGIN.\n", Para.Para32[0]))

      if (!pAC->Rlmt.Port[Para.Para32[0]].LinkDown) {
            pRPort->Net->LinksUp--;
            pRPort->LinkDown = SK_TRUE;
            pRPort->PortState = SK_RLMT_PS_LINK_DOWN;
            SK_HWAC_LINK_LED(pAC, IoC, Para.Para32[0], SK_LED_OFF);

            if ((pRPort->Net->RlmtMode & SK_RLMT_CHECK_LOC_LINK) != 0) {
                  /* Build the check chain. */
                  SkRlmtBuildCheckChain(pAC, pRPort->Net->NetNumber);
            }

            /* 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_U32                  i, j;
      SK_RLMT_PORT      *pRPort;
      SK_MAC_ADDR       *pOldMacAddr;
      SK_MAC_ADDR       *pNewMacAddr;

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_PORT_ADDR Port %d Event BEGIN.\n", Para.Para32[0]))

      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[Para.Para32[0]].PreviousMacAddress;
      pNewMacAddr = &pAC->Addr.Port[Para.Para32[0]].CurrentMacAddress;

      /*
       * NOTE: This is not scalable for solutions where ports are
       *     checked remotely.  There, we need to send an RLMT
       *     address change packet - and how do we ensure delivery?
       */
      for (i = 0; i < (SK_U32)pAC->GIni.GIMacsFound; i++) {
            pRPort = &pAC->Rlmt.Port[i];
            for (j = 0; j < pRPort->PortsChecked; j++) {
                  if (SK_ADDR_EQUAL(
                        pRPort->PortCheck[j].CheckAddr.a, pOldMacAddr->a)) {
                        pRPort->PortCheck[j].CheckAddr = *pNewMacAddr;
                  }
            }
      }

      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_EVPARA   Para2;
      SK_U32            PortIdx;
      SK_U32            PortNumber;

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_START Net %d Event BEGIN.\n", Para.Para32[0]))

      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[Para.Para32[0]].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;
      }

      if (pAC->Rlmt.Net[Para.Para32[0]].PrefPort >=
            pAC->Rlmt.Net[Para.Para32[0]].NumPorts) {
            SK_ERR_LOG(pAC, SK_ERRCL_SW, SKERR_RLMT_E009, SKERR_RLMT_E009_MSG);

            /* Change PrefPort to internal default. */
            Para2.Para32[0] = 0xFFFFFFFF;
            Para2.Para32[1] = Para.Para32[0];
            (void)SkRlmtEvent(pAC, IoC, SK_RLMT_PREFPORT_CHANGE, Para2);
      }

      PortIdx = pAC->Rlmt.Net[Para.Para32[0]].PrefPort;
      PortNumber = pAC->Rlmt.Net[Para.Para32[0]].Port[PortIdx]->PortNumber;

      pAC->Rlmt.Net[Para.Para32[0]].LinksUp = 0;
      pAC->Rlmt.Net[Para.Para32[0]].PortsUp = 0;
      pAC->Rlmt.Net[Para.Para32[0]].CheckingState = 0;
      pAC->Rlmt.Net[Para.Para32[0]].RlmtState = SK_RLMT_RS_NET_DOWN;

      /* Start preferred port. */
      SkRlmtPortStart(pAC, IoC, PortNumber);

      /* Start Timer (for first port only). */
      Para2.Para32[0] = PortNumber;
      Para2.Para32[1] = (SK_U32)-1;
      SkTimerStart(pAC, IoC, &pAC->Rlmt.Port[PortNumber].UpTimer,
            SK_RLMT_PORTSTART_TIM_VAL, SKGE_RLMT, SK_RLMT_PORTSTART_TIM, Para2);

      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_U32            PortNumber;
      SK_U32            i;

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_STOP Net %d Event BEGIN.\n", Para.Para32[0]))

      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[Para.Para32[0]].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 RLMT timers. */
      SkTimerStop(pAC, IoC, &pAC->Rlmt.Net[Para.Para32[0]].LocTimer);
      SkTimerStop(pAC, IoC, &pAC->Rlmt.Net[Para.Para32[0]].SegTimer);

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

      /* Stop ports. */
      for (i = 0; i < pAC->Rlmt.Net[Para.Para32[0]].NumPorts; i++) {
            PortNumber = pAC->Rlmt.Net[Para.Para32[0]].Port[i]->PortNumber;
            if (pAC->Rlmt.Port[PortNumber].PortState != SK_RLMT_PS_INIT) {
                  SkTimerStop(pAC, IoC, &pAC->Rlmt.Port[PortNumber].UpTimer);
                  SkTimerStop(pAC, IoC, &pAC->Rlmt.Port[PortNumber].DownRxTimer);
                  SkTimerStop(pAC, IoC, &pAC->Rlmt.Port[PortNumber].DownTxTimer);

                  pAC->Rlmt.Port[PortNumber].PortState = SK_RLMT_PS_INIT;
                  pAC->Rlmt.Port[PortNumber].RootIdSet = SK_FALSE;
                  pAC->Rlmt.Port[PortNumber].PortStarted = SK_FALSE;
                  Para2.Para32[0] = PortNumber;
                  Para2.Para32[1] = (SK_U32)-1;
                  SkEventQueue(pAC, SKGE_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 */


/******************************************************************************
 *
 *    SkRlmtEvtTim - TIM
 *
 * Description:
 *    This routine handles TIM events.
 *
 * Context:
 *    runtime, pageable?
 *    may be called after SK_INIT_IO
 *
 * Returns:
 *    Nothing
 */
RLMT_STATIC void  SkRlmtEvtTim(
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_U32                  Timeout;
      SK_U32                  NewTimeout;
      SK_U32                  PortNumber;
      SK_U32                  i;

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_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_TIM Event EMPTY.\n"))
            return;
      }

      if ((pAC->Rlmt.Net[Para.Para32[0]].RlmtMode & SK_RLMT_CHECK_OTHERS) == 0 ||
            pAC->Rlmt.Net[Para.Para32[0]].LinksUp == 0) {
            /* Mode changed or all links down: No more link checking. */
            return;
      }

#if 0
      pAC->Rlmt.SwitchCheckCounter--;
      if (pAC->Rlmt.SwitchCheckCounter == 0) {
            pAC->Rlmt.SwitchCheckCounter;
      }
#endif      /* 0 */

      NewTimeout = SK_RLMT_DEF_TO_VAL;
      for (i = 0; i < pAC->Rlmt.Net[Para.Para32[0]].NumPorts; i++) {
            PortNumber = pAC->Rlmt.Net[Para.Para32[0]].Port[i]->PortNumber;
            pRPort = &pAC->Rlmt.Port[PortNumber];
            if (!pRPort->LinkDown) {
                  Timeout = SkRlmtCheckPort(pAC, IoC, PortNumber);
                  if (Timeout < NewTimeout) {
                        NewTimeout = Timeout;
                  }

                  /*
                   * These counters should be set to 0 for all ports before the
                   * first frame is sent in the next loop.
                   */
                  pRPort->PacketsPerTimeSlot = 0;
                  /* pRPort->DataPacketsPerTimeSlot = 0; */
                  pRPort->BpduPacketsPerTimeSlot = 0;
            }
      }
      pAC->Rlmt.Net[Para.Para32[0]].TimeoutValue = NewTimeout;

      if (pAC->Rlmt.Net[Para.Para32[0]].LinksUp > 1) {
            /*
             * If checking remote ports, also send packets if
             *   (LinksUp == 1) &&
             *   this port checks at least one (remote) port.
             */

            /*
             * Must be new loop, as SkRlmtCheckPort can request to
             * check segmentation when e.g. checking the last port.
             */
            for (i = 0; i < pAC->Rlmt.Net[Para.Para32[0]].NumPorts; i++) {
                  if (!pAC->Rlmt.Net[Para.Para32[0]].Port[i]->LinkDown) {
                        SkRlmtSend(pAC, IoC,
                              pAC->Rlmt.Net[Para.Para32[0]].Port[i]->PortNumber);
                  }
            }
      }

      SkTimerStart(pAC, IoC, &pAC->Rlmt.Net[Para.Para32[0]].LocTimer,
            pAC->Rlmt.Net[Para.Para32[0]].TimeoutValue, SKGE_RLMT, SK_RLMT_TIM,
            Para);

      if (pAC->Rlmt.Net[Para.Para32[0]].LinksUp > 1 &&
            (pAC->Rlmt.Net[Para.Para32[0]].RlmtMode & SK_RLMT_CHECK_SEG) &&
            (pAC->Rlmt.Net[Para.Para32[0]].CheckingState & SK_RLMT_RCS_START_SEG)) {
            SkTimerStart(pAC, IoC, &pAC->Rlmt.Net[Para.Para32[0]].SegTimer,
                  SK_RLMT_SEG_TO_VAL, SKGE_RLMT, SK_RLMT_SEG_TIM, Para);
            pAC->Rlmt.Net[Para.Para32[0]].CheckingState &= ~SK_RLMT_RCS_START_SEG;
            pAC->Rlmt.Net[Para.Para32[0]].CheckingState |=
                  SK_RLMT_RCS_SEG | SK_RLMT_RCS_REPORT_SEG;
      }

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


/******************************************************************************
 *
 *    SkRlmtEvtSegTim - SEG_TIM
 *
 * Description:
 *    This routine handles SEG_TIM events.
 *
 * Context:
 *    runtime, pageable?
 *    may be called after SK_INIT_IO
 *
 * Returns:
 *    Nothing
 */
RLMT_STATIC void  SkRlmtEvtSegTim(
SK_AC       *pAC, /* Adapter Context */
SK_IOC            IoC,  /* I/O Context */
SK_EVPARA   Para) /* SK_U32 NetNumber; SK_U32 -1 */
{
#ifdef xDEBUG
      int j;
#endif      /* DEBUG */

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_SEG_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_SEG_TIM Event EMPTY.\n"))
            return;
      }

#ifdef xDEBUG
      for (j = 0; j < pAC->Rlmt.Net[Para.Para32[0]].NumPorts; j++) {
            SK_ADDR_PORT      *pAPort;
            SK_U32                  k;
            SK_U16                  *InAddr;
            SK_U8             InAddr8[6];

            InAddr = (SK_U16 *)&InAddr8[0];
            pAPort = pAC->Rlmt.Net[Para.Para32[0]].Port[j]->AddrPort;
            for (k = 0; k < pAPort->NextExactMatchRlmt; k++) {
                  /* Get exact match address k from port j. */
                  XM_INADDR(IoC, pAC->Rlmt.Net[Para.Para32[0]].Port[j]->PortNumber,
                        XM_EXM(k), InAddr);
                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                        ("MC address %d on Port %u: %02x %02x %02x %02x %02x %02x --  %02x %02x %02x %02x %02x %02x.\n",
                              k, pAC->Rlmt.Net[Para.Para32[0]].Port[j]->PortNumber,
                              InAddr8[0], InAddr8[1], InAddr8[2],
                              InAddr8[3], InAddr8[4], InAddr8[5],
                              pAPort->Exact[k].a[0], pAPort->Exact[k].a[1],
                              pAPort->Exact[k].a[2], pAPort->Exact[k].a[3],
                              pAPort->Exact[k].a[4], pAPort->Exact[k].a[5]))
            }
      }
#endif      /* xDEBUG */
                        
      SkRlmtCheckSeg(pAC, IoC, Para.Para32[0]);

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


/******************************************************************************
 *
 *    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;
      SK_U32      NetNumber;

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

      /* Should we ignore frames during port switching? */

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

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

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_PACKET_RECEIVED Event END.\n"))
}     /* 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_U32                  i;
      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. */
      for (i = 0; i < pAC->Rlmt.Net[Para.Para32[0]].NumPorts; i++) {
            pRPort =
                  &pAC->Rlmt.Port[pAC->Rlmt.Net[Para.Para32[0]].Port[i]->PortNumber];
            pRPort->TxHelloCts = 0;
            pRPort->RxHelloCts = 0;
            pRPort->TxSpHelloReqCts = 0;
            pRPort->RxSpHelloCts = 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 */
{
      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_STATS_UPDATE 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_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;
      }

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

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


/******************************************************************************
 *
 *    SkRlmtEvtPrefportChange - PREFPORT_CHANGE
 *
 * Description:
 *    This routine handles PREFPORT_CHANGE events.
 *
 * Context:
 *    runtime, pageable?
 *    may be called after SK_INIT_IO
 *
 * Returns:
 *    Nothing
 */
RLMT_STATIC void  SkRlmtEvtPrefportChange(
SK_AC       *pAC, /* Adapter Context */
SK_IOC            IoC,  /* I/O Context */
SK_EVPARA   Para) /* SK_U32 PortIndex; SK_U32 NetNumber */
{
      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_PREFPORT_CHANGE to Port %d Event BEGIN.\n", Para.Para32[0]))

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

      /* 0xFFFFFFFF == auto-mode. */
      if (Para.Para32[0] == 0xFFFFFFFF) {
            pAC->Rlmt.Net[Para.Para32[1]].PrefPort = SK_RLMT_DEF_PREF_PORT;
      }
      else {
            if (Para.Para32[0] >= pAC->Rlmt.Net[Para.Para32[1]].NumPorts) {
                  SK_ERR_LOG(pAC, SK_ERRCL_SW, SKERR_RLMT_E010, SKERR_RLMT_E010_MSG);

                  SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                        ("SK_RLMT_PREFPORT_CHANGE Event EMPTY.\n"))
                  return;
            }

            pAC->Rlmt.Net[Para.Para32[1]].PrefPort = Para.Para32[0];
      }

      pAC->Rlmt.Net[Para.Para32[1]].Preference = Para.Para32[0];

      if (pAC->Rlmt.Net[Para.Para32[1]].RlmtState != SK_RLMT_RS_INIT) {
            SkRlmtCheckSwitch(pAC, IoC, Para.Para32[1]);
      }

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


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

      SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
            ("SK_RLMT_SET_NETS 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_SET_NETS Event EMPTY.\n"))
            return;
      }

      if (Para.Para32[0] == 0 || Para.Para32[0] > SK_MAX_NETS ||
            Para.Para32[0] > (SK_U32)pAC->GIni.GIMacsFound) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("Bad number of nets: %d.\n", Para.Para32[0]))
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SK_RLMT_SET_NETS Event EMPTY.\n"))
            return;
      }

      if (Para.Para32[0] == pAC->Rlmt.NumNets) {      /* No change. */
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SK_RLMT_SET_NETS Event EMPTY.\n"))
            return;
      }

      /* Entering and leaving dual mode only allowed while nets are stopped. */
      if (pAC->Rlmt.NetsStarted > 0) {
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("Changing dual mode only allowed while all nets are stopped.\n"))
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SK_RLMT_SET_NETS Event EMPTY.\n"))
            return;
      }

      if (Para.Para32[0] == 1) {
            if (pAC->Rlmt.NumNets > 1) {
                  /* Clear logical MAC addr from second net's active port. */
                  (void)SkAddrOverride(pAC, IoC, pAC->Rlmt.Net[1].Port[pAC->Addr.
                        Net[1].ActivePort]->PortNumber, NULL, SK_ADDR_CLEAR_LOGICAL);
                  pAC->Rlmt.Net[1].NumPorts = 0;
            }

            pAC->Rlmt.NumNets = Para.Para32[0];
            for (i = 0; (SK_U32)i < pAC->Rlmt.NumNets; i++) {
                  pAC->Rlmt.Net[i].RlmtState = SK_RLMT_RS_INIT;
                  pAC->Rlmt.Net[i].RootIdSet = SK_FALSE;
                  pAC->Rlmt.Net[i].Preference = 0xFFFFFFFF;   /* "Automatic" */
                  pAC->Rlmt.Net[i].PrefPort = SK_RLMT_DEF_PREF_PORT;
                  /* Just assuming. */
                  pAC->Rlmt.Net[i].ActivePort = pAC->Rlmt.Net[i].PrefPort;
                  pAC->Rlmt.Net[i].RlmtMode = SK_RLMT_DEF_MODE;
                  pAC->Rlmt.Net[i].TimeoutValue = SK_RLMT_DEF_TO_VAL;
                  pAC->Rlmt.Net[i].NetNumber = i;
            }

            pAC->Rlmt.Port[1].Net= &pAC->Rlmt.Net[0];
            pAC->Rlmt.Net[0].NumPorts = pAC->GIni.GIMacsFound;

            SkEventQueue(pAC, SKGE_PNMI, SK_PNMI_EVT_RLMT_SET_NETS, Para);

            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("RLMT: Changed to one net with two ports.\n"))
      }
      else if (Para.Para32[0] == 2) {
            pAC->Rlmt.Port[1].Net= &pAC->Rlmt.Net[1];
            pAC->Rlmt.Net[1].NumPorts = pAC->GIni.GIMacsFound - 1;
            pAC->Rlmt.Net[0].NumPorts =
                  pAC->GIni.GIMacsFound - pAC->Rlmt.Net[1].NumPorts;
            
            pAC->Rlmt.NumNets = Para.Para32[0];
            for (i = 0; (SK_U32)i < pAC->Rlmt.NumNets; i++) {
                  pAC->Rlmt.Net[i].RlmtState = SK_RLMT_RS_INIT;
                  pAC->Rlmt.Net[i].RootIdSet = SK_FALSE;
                  pAC->Rlmt.Net[i].Preference = 0xFFFFFFFF;   /* "Automatic" */
                  pAC->Rlmt.Net[i].PrefPort = SK_RLMT_DEF_PREF_PORT;
                  /* Just assuming. */
                  pAC->Rlmt.Net[i].ActivePort = pAC->Rlmt.Net[i].PrefPort;
                  pAC->Rlmt.Net[i].RlmtMode = SK_RLMT_DEF_MODE;
                  pAC->Rlmt.Net[i].TimeoutValue = SK_RLMT_DEF_TO_VAL;

                  pAC->Rlmt.Net[i].NetNumber = i;
            }

            /* Set logical MAC addr on second net's active port. */
            (void)SkAddrOverride(pAC, IoC, pAC->Rlmt.Net[1].Port[pAC->Addr.
                  Net[1].ActivePort]->PortNumber, NULL, SK_ADDR_SET_LOGICAL);

            SkEventQueue(pAC, SKGE_PNMI, SK_PNMI_EVT_RLMT_SET_NETS, Para);

            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("RLMT: Changed to two nets with one port each.\n"))
      }
      else {
            /* Not implemented for more than two nets. */
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SetNets not implemented for more than two nets.\n"))
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SK_RLMT_SET_NETS Event EMPTY.\n"))
            return;
      }

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


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

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

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

      Para.Para32[0] |= SK_RLMT_CHECK_LINK;

      if ((pAC->Rlmt.Net[Para.Para32[1]].NumPorts == 1) &&
            Para.Para32[0] != SK_RLMT_MODE_CLS) {
            pAC->Rlmt.Net[Para.Para32[1]].RlmtMode = SK_RLMT_MODE_CLS;
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("Forced RLMT mode to CLS on single port net.\n"))
            SK_DBG_MSG(pAC, SK_DBGMOD_RLMT, SK_DBGCAT_CTRL,
                  ("SK_RLMT_MODE_CHANGE Event EMPTY.\n"))
            return;
      }

      /* Update RLMT mode. */
      PrevRlmtMode = pAC->Rlmt.Net[Para.Para32[1]].RlmtMode;
      pAC->Rlmt.Net[Para.Para32[1]].RlmtMode = Para.Para32[0];

      if ((PrevRlmtMode & SK_RLMT_CHECK_LOC_LINK) !=
            (pAC->Rlmt.Net[Para.Para32[1]].RlmtMode & SK_RLMT_CHECK_LOC_LINK)) {
            /* SK_RLMT_CHECK_LOC_LINK bit changed. */
            if ((PrevRlmtMode & SK_RLMT_CHECK_OTHERS) == 0 &&
                  pAC->Rlmt.Net[Para.Para32[1]].NumPorts > 1 &&
                  pAC->Rlmt.Net[Para.Para32[1]].PortsUp >= 1) {
                  /* 20001207 RA: Was "PortsUp == 1". */
                  Para2.Para32[0] = Para.Para32[1];
                  Para2.Para32[1] = (SK_U32)-1;
                  SkTimerStart(pAC, IoC, &pAC->Rlmt.Net[Para.Para32[1]].LocTimer,
                        pAC->Rlmt.Net[Para.Para32[1]].TimeoutValue,
                        SKGE_RLMT, SK_RLMT_TIM, Para2);
            }
      }

      if ((PrevRlmtMode & SK_RLMT_CHECK_SEG) !=
            (pAC->Rlmt.Net[Para.Para32[1]].RlmtMode & SK_RLMT_CHECK_SEG)) {
            /* SK_RLMT_CHECK_SEG bit changed. */
            for (i = 0; i < pAC->Rlmt.Net[Para.Para32[1]].NumPorts; i++) {
                  (void)SkAddrMcClear(pAC, IoC,
                        pAC->Rlmt.Net[Para.Para32[1]].Port[i]->PortNumber,
                        SK_ADDR_PERMANENT | SK_MC_SW_ONLY);

                  /* Add RLMT MC address. */
                  (void)SkAddrMcAdd(pAC, IoC,
                        pAC->Rlmt.Net[Para.Para32[1]].Port[i]->PortNumber,
                        &SkRlmtMcAddr, SK_ADDR_PERMANENT);

                  if ((pAC->Rlmt.Net[Para.Para32[1]].RlmtMode &
                        SK_RLMT_CHECK_SEG) != 0) {
                        /* Add BPDU MC address. */
                        (void)SkAddrMcAdd(pAC, IoC,
                              pAC->Rlmt.Net[Para.Para32[1]].Port[i]->PortNumber,
                              &BridgeMcAddr, SK_ADDR_PERMANENT);

                        if (pAC->Rlmt.Net[Para.Para32[1]].RlmtState != SK_RLMT_RS_INIT) {
                              if (!pAC->Rlmt.Net[Para.Para32[1]].Port[i]->LinkDown &&
                                    (Para2.pParaPtr = SkRlmtBuildSpanningTreePacket(
                                    pAC, IoC, i)) != NULL) {
                                    pAC->Rlmt.Net[Para.Para32[1]].Port[i]->RootIdSet =
                                          SK_FALSE;
                                    SkEventQueue(pAC, SKGE_DRV, SK_DRV_RLMT_SEND, Para2);
                              }
                        }
                  }
                  (void)SkAddrMcUpdate(pAC, IoC,
                        pAC->Rlmt.Net[Para.Para32[1]].Port[i]->PortNumber);
            }     /* for ... */

            if ((pAC->Rlmt.Net[Para.Para32[1]].RlmtMode & SK_RLMT_CHECK_SEG) != 0) {
                  Para2.Para32[0] = Para.Para32[1];
                  Para2.Para32[1] = (SK_U32)-1;
                  SkTimerStart(pAC, IoC, &pAC->Rlmt.Net[Para.Para32[1]].SegTimer,
                        SK_RLMT_SEG_TO_VAL, SKGE_RLMT, SK_RLMT_SEG_TIM, Para2);
            }
      }     /* SK_RLMT_CHECK_SEG bit changed. */

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


/******************************************************************************
 *
 *    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_PORTSTART_TIM:   /* From RLMT via TIME. */
            SkRlmtEvtPortStartTim(pAC, IoC, Para);
            break;
      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_TIM:       /* From RLMT via TIME. */
            SkRlmtEvtTim(pAC, IoC, Para);
            break;
      case SK_RLMT_SEG_TIM:
            SkRlmtEvtSegTim(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;
      case SK_RLMT_PREFPORT_CHANGE: /* From PNMI. */
            SkRlmtEvtPrefportChange(pAC, IoC, Para);
            break;
      case SK_RLMT_MODE_CHANGE:     /* From PNMI. */
            SkRlmtEvtModeChange(pAC, IoC, Para);
            break;
      case SK_RLMT_SET_NETS:  /* From DRV. */
            SkRlmtEvtSetNets(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 */

Generated by  Doxygen 1.6.0   Back to index