| /** | 
 |  * @file IxEthDataPlane.c | 
 |  * | 
 |  * @author Intel Corporation | 
 |  * @date 12-Feb-2002 | 
 |  * | 
 |  * @brief This file contains the implementation of the IXPxxx | 
 |  * Ethernet Access Data plane component | 
 |  * | 
 |  * Design Notes: | 
 |  * | 
 |  * @par | 
 |  * IXP400 SW Release version 2.0 | 
 |  * | 
 |  * -- Copyright Notice -- | 
 |  * | 
 |  * @par | 
 |  * Copyright 2001-2005, Intel Corporation. | 
 |  * All rights reserved. | 
 |  * | 
 |  * @par | 
 |  * Redistribution and use in source and binary forms, with or without | 
 |  * modification, are permitted provided that the following conditions | 
 |  * are met: | 
 |  * 1. Redistributions of source code must retain the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer. | 
 |  * 2. Redistributions in binary form must reproduce the above copyright | 
 |  *    notice, this list of conditions and the following disclaimer in the | 
 |  *    documentation and/or other materials provided with the distribution. | 
 |  * 3. Neither the name of the Intel Corporation nor the names of its contributors | 
 |  *    may be used to endorse or promote products derived from this software | 
 |  *    without specific prior written permission. | 
 |  * | 
 |  * @par | 
 |  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' | 
 |  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | 
 |  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | 
 |  * ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE | 
 |  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | 
 |  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | 
 |  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | 
 |  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | 
 |  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | 
 |  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | 
 |  * SUCH DAMAGE. | 
 |  * | 
 |  * @par | 
 |  * -- End of Copyright Notice -- | 
 |  */ | 
 |  | 
 | #include "IxNpeMh.h" | 
 | #include "IxEthAcc.h" | 
 | #include "IxEthDB.h" | 
 | #include "IxOsal.h" | 
 | #include "IxEthDBPortDefs.h" | 
 | #include "IxFeatureCtrl.h" | 
 | #include "IxEthAcc_p.h" | 
 | #include "IxEthAccQueueAssign_p.h" | 
 |  | 
 | extern PUBLIC IxEthAccMacState ixEthAccMacState[]; | 
 | extern PUBLIC UINT32 ixEthAccNewSrcMask; | 
 |  | 
 | /** | 
 |  * private functions prototype | 
 |  */ | 
 | PRIVATE IX_OSAL_MBUF * | 
 | ixEthAccEntryFromQConvert(UINT32 qEntry, UINT32 mask); | 
 |  | 
 | PRIVATE UINT32 | 
 | ixEthAccMbufRxQPrepare(IX_OSAL_MBUF *mbuf); | 
 |  | 
 | PRIVATE UINT32 | 
 | ixEthAccMbufTxQPrepare(IX_OSAL_MBUF *mbuf); | 
 |  | 
 | PRIVATE IxEthAccStatus | 
 | ixEthAccTxSwQHighestPriorityGet(IxEthAccPortId portId, | 
 | 				IxEthAccTxPriority *priorityPtr); | 
 |  | 
 | PRIVATE IxEthAccStatus | 
 | ixEthAccTxFromSwQ(IxEthAccPortId portId, | 
 | 		  IxEthAccTxPriority priority); | 
 |  | 
 | PRIVATE IxEthAccStatus | 
 | ixEthAccRxFreeFromSwQ(IxEthAccPortId portId); | 
 |  | 
 | PRIVATE void | 
 | ixEthAccMbufFromTxQ(IX_OSAL_MBUF *mbuf); | 
 |  | 
 | PRIVATE void | 
 | ixEthAccMbufFromRxQ(IX_OSAL_MBUF *mbuf); | 
 |  | 
 | PRIVATE IX_STATUS | 
 | ixEthAccQmgrLockTxWrite(IxEthAccPortId portId, | 
 | 			UINT32 qBuffer); | 
 |  | 
 | PRIVATE IX_STATUS | 
 | ixEthAccQmgrLockRxWrite(IxEthAccPortId portId, | 
 | 			UINT32 qBuffer); | 
 |  | 
 | PRIVATE IX_STATUS | 
 | ixEthAccQmgrTxWrite(IxEthAccPortId portId, | 
 | 		    UINT32 qBuffer, | 
 | 		    UINT32 priority); | 
 |  | 
 | /** | 
 |  * @addtogroup IxEthAccPri | 
 |  *@{ | 
 |  */ | 
 |  | 
 | /* increment a counter only when stats are enabled */ | 
 | #define TX_STATS_INC(port,field) \ | 
 |         IX_ETH_ACC_STATS_INC(ixEthAccPortData[port].ixEthAccTxData.stats.field) | 
 | #define RX_STATS_INC(port,field) \ | 
 |         IX_ETH_ACC_STATS_INC(ixEthAccPortData[port].ixEthAccRxData.stats.field) | 
 |  | 
 | /* always increment the counter (mainly used for unexpected errors) */ | 
 | #define TX_INC(port,field) \ | 
 |         ixEthAccPortData[port].ixEthAccTxData.stats.field++ | 
 | #define RX_INC(port,field) \ | 
 |         ixEthAccPortData[port].ixEthAccRxData.stats.field++ | 
 |  | 
 | PRIVATE IxEthAccDataPlaneStats     ixEthAccDataStats; | 
 |  | 
 | extern IxEthAccPortDataInfo   ixEthAccPortData[]; | 
 | extern IxEthAccInfo   ixEthAccDataInfo; | 
 |  | 
 | PRIVATE IxOsalFastMutex txWriteMutex[IX_ETH_ACC_NUMBER_OF_PORTS]; | 
 | PRIVATE IxOsalFastMutex rxWriteMutex[IX_ETH_ACC_NUMBER_OF_PORTS]; | 
 |  | 
 | /** | 
 |  * | 
 |  * @brief Mbuf header conversion macros : they implement the | 
 |  *  different conversions using a temporary value. They also double-check | 
 |  *  that the parameters can be converted to/from NPE format. | 
 |  * | 
 |  */ | 
 | #if defined(__wince) && !defined(IN_KERNEL) | 
 | #define PTR_VIRT2NPE(ptrSrc,dst) \ | 
 |   do { UINT32 temp; \ | 
 |       IX_OSAL_ENSURE(sizeof(ptrSrc) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       IX_OSAL_ENSURE(sizeof(dst) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       temp = (UINT32)IX_OSAL_MBUF_MBUF_VIRTUAL_TO_PHYSICAL_TRANSLATION((IX_OSAL_MBUF*)ptrSrc); \ | 
 |       (dst) = IX_OSAL_SWAP_BE_SHARED_LONG(temp); } \ | 
 |   while(0) | 
 |  | 
 | #define PTR_NPE2VIRT(type,src,ptrDst) \ | 
 |   do { void *temp; \ | 
 |       IX_OSAL_ENSURE(sizeof(type) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       IX_OSAL_ENSURE(sizeof(src) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       IX_OSAL_ENSURE(sizeof(ptrDst) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       temp = (void *)IX_OSAL_SWAP_BE_SHARED_LONG(src); \ | 
 |       (ptrDst) = (type)IX_OSAL_MBUF_MBUF_PHYSICAL_TO_VIRTUAL_TRANSLATION(temp); } \ | 
 |   while(0) | 
 | #else | 
 | #define PTR_VIRT2NPE(ptrSrc,dst) \ | 
 |   do { UINT32 temp; \ | 
 |       IX_OSAL_ENSURE(sizeof(ptrSrc) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       IX_OSAL_ENSURE(sizeof(dst) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       temp = (UINT32)IX_OSAL_MMU_VIRT_TO_PHYS(ptrSrc); \ | 
 |       (dst) = IX_OSAL_SWAP_BE_SHARED_LONG(temp); } \ | 
 |   while(0) | 
 |  | 
 | #define PTR_NPE2VIRT(type,src,ptrDst) \ | 
 |   do { void *temp; \ | 
 |       IX_OSAL_ENSURE(sizeof(type) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       IX_OSAL_ENSURE(sizeof(src) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       IX_OSAL_ENSURE(sizeof(ptrDst) == sizeof(UINT32), "Wrong parameter type"); \ | 
 |       temp = (void *)IX_OSAL_SWAP_BE_SHARED_LONG(src); \ | 
 |       (ptrDst) = (type)IX_OSAL_MMU_PHYS_TO_VIRT(temp); } \ | 
 |   while(0) | 
 | #endif | 
 |  | 
 | /** | 
 |  * | 
 |  * @brief Mbuf payload pointer conversion macros : Wince has its own | 
 |  *  method to convert the buffer pointers | 
 |  */ | 
 | #if defined(__wince) && !defined(IN_KERNEL) | 
 | #define DATAPTR_VIRT2NPE(ptrSrc,dst) \ | 
 |   do { UINT32 temp; \ | 
 |       temp = (UINT32)IX_OSAL_MBUF_DATA_VIRTUAL_TO_PHYSICAL_TRANSLATION(ptrSrc); \ | 
 |       (dst) = IX_OSAL_SWAP_BE_SHARED_LONG(temp); } \ | 
 |   while(0) | 
 |  | 
 | #else | 
 | #define DATAPTR_VIRT2NPE(ptrSrc,dst) PTR_VIRT2NPE(IX_OSAL_MBUF_MDATA(ptrSrc),dst) | 
 | #endif | 
 |  | 
 |  | 
 | /* Flush the shared part of the mbuf header */ | 
 | #define IX_ETHACC_NE_CACHE_FLUSH(mbufPtr) \ | 
 |   do { \ | 
 |       IX_OSAL_CACHE_FLUSH(IX_ETHACC_NE_SHARED(mbufPtr), \ | 
 | 			      sizeof(IxEthAccNe)); \ | 
 |     } \ | 
 |   while(0) | 
 |  | 
 | /* Invalidate the shared part of the mbuf header */ | 
 | #define IX_ETHACC_NE_CACHE_INVALIDATE(mbufPtr) \ | 
 |   do { \ | 
 |       IX_OSAL_CACHE_INVALIDATE(IX_ETHACC_NE_SHARED(mbufPtr), \ | 
 | 				   sizeof(IxEthAccNe)); \ | 
 |     } \ | 
 |   while(0) | 
 |  | 
 | /* Preload one cache line (shared mbuf headers are aligned | 
 |  * and their size is 1 cache line) | 
 |  * | 
 |  * IX_OSAL_CACHED  is defined when the mbuf headers are | 
 |  * allocated from cached memory. | 
 |  * | 
 |  * Other processor on emulation environment may not implement | 
 |  * preload function | 
 |  */ | 
 | #ifdef IX_OSAL_CACHED | 
 | 	#if (CPU!=SIMSPARCSOLARIS) && !defined (__wince) | 
 | 		#define IX_ACC_DATA_CACHE_PRELOAD(ptr) \ | 
 | 		do { /* preload a cache line (Xscale Processor) */ \ | 
 | 			__asm__ (" pld [%0]\n": : "r" (ptr)); \ | 
 | 		} \ | 
 | 		while(0) | 
 | 	#else | 
 | 		/* preload not implemented on different processor */ | 
 | 		#define IX_ACC_DATA_CACHE_PRELOAD(mbufPtr) \ | 
 | 		do { /* nothing */ } while (0) | 
 | 	#endif | 
 | #else | 
 | 	/* preload not needed if cache is not enabled */ | 
 | 	#define IX_ACC_DATA_CACHE_PRELOAD(mbufPtr) \ | 
 | 	do { /* nothing */ } while (0) | 
 | #endif | 
 |  | 
 | /** | 
 |  * | 
 |  * @brief function to retrieve the correct pointer from | 
 |  * a queue entry posted by the NPE | 
 |  * | 
 |  * @param qEntry : entry from qmgr queue | 
 |  *        mask : applicable mask for this queue | 
 |  *        (4 most significant bits are used for additional informations) | 
 |  * | 
 |  * @return IX_OSAL_MBUF * pointer to mbuf header | 
 |  * | 
 |  * @internal | 
 |  */ | 
 | PRIVATE IX_OSAL_MBUF * | 
 | ixEthAccEntryFromQConvert(UINT32 qEntry, UINT32 mask) | 
 | { | 
 |     IX_OSAL_MBUF *mbufPtr; | 
 |  | 
 |     if (qEntry != 0) | 
 |     { | 
 |         /* mask NPE bits (e.g. priority, port ...) */ | 
 |         qEntry &= mask; | 
 |  | 
 | #if IX_ACC_DRAM_PHYS_OFFSET != 0 | 
 |         /* restore the original address pointer (if PHYS_OFFSET is not 0) */ | 
 |         qEntry |= (IX_ACC_DRAM_PHYS_OFFSET & ~IX_ETHNPE_QM_Q_RXENET_ADDR_MASK); | 
 | #endif | 
 |         /* get the mbuf pointer address from the npe-shared address */ | 
 |         qEntry -= offsetof(IX_OSAL_MBUF,ix_ne); | 
 |  | 
 |         /* phys2virt mbuf */ | 
 |         mbufPtr = (IX_OSAL_MBUF *)IX_OSAL_MMU_PHYS_TO_VIRT(qEntry); | 
 |  | 
 |         /* preload the cacheline shared with NPE */ | 
 |         IX_ACC_DATA_CACHE_PRELOAD(IX_ETHACC_NE_SHARED(mbufPtr)); | 
 |  | 
 |         /* preload the cacheline used by xscale */ | 
 |         IX_ACC_DATA_CACHE_PRELOAD(mbufPtr); | 
 |     } | 
 |     else | 
 |     { | 
 | 	mbufPtr = NULL; | 
 |     } | 
 |  | 
 |     return mbufPtr; | 
 | } | 
 |  | 
 | /* Convert the mbuf header for NPE transmission */ | 
 | PRIVATE UINT32 | 
 | ixEthAccMbufTxQPrepare(IX_OSAL_MBUF *mbuf) | 
 | { | 
 |     UINT32 qbuf; | 
 |     UINT32 len; | 
 |  | 
 |     /* endianess swap for tci and flags | 
 |        note: this is done only once, even for chained buffers */ | 
 |     IX_ETHACC_NE_FLAGS(mbuf)   = IX_OSAL_SWAP_BE_SHARED_SHORT(IX_ETHACC_NE_FLAGS(mbuf)); | 
 |     IX_ETHACC_NE_VLANTCI(mbuf) = IX_OSAL_SWAP_BE_SHARED_SHORT(IX_ETHACC_NE_VLANTCI(mbuf)); | 
 |  | 
 |     /* test for unchained mbufs */ | 
 |     if (IX_OSAL_MBUF_NEXT_BUFFER_IN_PKT_PTR(mbuf) == NULL) | 
 |     { | 
 | 	/* "best case" scenario : unchained mbufs */ | 
 | 	IX_ETH_ACC_STATS_INC(ixEthAccDataStats.unchainedTxMBufs); | 
 |  | 
 | 	/* payload pointer conversion */ | 
 | 	DATAPTR_VIRT2NPE(mbuf, IX_ETHACC_NE_DATA(mbuf)); | 
 |  | 
 | 	/* unchained mbufs : the frame length is the mbuf length | 
 | 	 * and the 2 identical lengths are stored in the same | 
 | 	 * word. | 
 | 	 */ | 
 | 	len = IX_OSAL_MBUF_MLEN(mbuf); | 
 |  | 
 | 	/* set the length in both length and pktLen 16-bits fields */ | 
 | 	len |= (len << IX_ETHNPE_ACC_LENGTH_OFFSET); | 
 | 	IX_ETHACC_NE_LEN(mbuf) = IX_OSAL_SWAP_BE_SHARED_LONG(len); | 
 |  | 
 | 	/* unchained mbufs : next contains 0 */ | 
 | 	IX_ETHACC_NE_NEXT(mbuf) = 0; | 
 |  | 
 | 	/* flush shared header after all address conversions */ | 
 | 	IX_ETHACC_NE_CACHE_FLUSH(mbuf); | 
 |     } | 
 |     else | 
 |     { | 
 | 	/* chained mbufs */ | 
 | 	IX_OSAL_MBUF *ptr = mbuf; | 
 | 	IX_OSAL_MBUF *nextPtr; | 
 | 	UINT32 frmLen; | 
 |  | 
 | 	/* get the frame length from the header of the first buffer */ | 
 | 	frmLen = IX_OSAL_MBUF_PKT_LEN(mbuf); | 
 |  | 
 | 	do | 
 | 	{ | 
 | 	    IX_ETH_ACC_STATS_INC(ixEthAccDataStats.chainedTxMBufs); | 
 |  | 
 | 	    /* payload pointer */ | 
 | 	    DATAPTR_VIRT2NPE(ptr,IX_ETHACC_NE_DATA(ptr)); | 
 | 	    /* Buffer length and frame length are stored in the same word */ | 
 | 	    len = IX_OSAL_MBUF_MLEN(ptr); | 
 | 	    len = frmLen | (len << IX_ETHNPE_ACC_LENGTH_OFFSET); | 
 | 	    IX_ETHACC_NE_LEN(ptr) = IX_OSAL_SWAP_BE_SHARED_LONG(len); | 
 |  | 
 | 	    /* get the virtual next chain pointer */ | 
 | 	    nextPtr = IX_OSAL_MBUF_NEXT_BUFFER_IN_PKT_PTR(ptr); | 
 | 	    if (nextPtr != NULL) | 
 | 	    { | 
 | 		/* shared pointer of the next buffer is chained */ | 
 | 		PTR_VIRT2NPE(IX_ETHACC_NE_SHARED(nextPtr), | 
 | 			     IX_ETHACC_NE_NEXT(ptr)); | 
 | 	    } | 
 | 	    else | 
 | 	    { | 
 | 		IX_ETHACC_NE_NEXT(ptr) = 0; | 
 | 	    } | 
 |  | 
 | 	    /* flush shared header after all address conversions */ | 
 | 	    IX_ETHACC_NE_CACHE_FLUSH(ptr); | 
 |  | 
 | 	    /* move to next buffer */ | 
 | 	    ptr = nextPtr; | 
 |  | 
 | 	    /* the frame length field is set only in the first buffer | 
 | 	     * and is zeroed in the next buffers | 
 | 	     */ | 
 | 	    frmLen = 0; | 
 | 	} | 
 | 	while(ptr != NULL); | 
 |  | 
 |     } | 
 |  | 
 |     /* virt2phys mbuf itself */ | 
 |     qbuf = (UINT32)IX_OSAL_MMU_VIRT_TO_PHYS( | 
 | 		  IX_ETHACC_NE_SHARED(mbuf)); | 
 |  | 
 |     /* Ensure the bits which are reserved to exchange information with | 
 |      * the NPE are cleared | 
 |      * | 
 |      * If the mbuf address is not correctly aligned, or from an | 
 |      * incompatible memory range, there is no point to continue | 
 |      */ | 
 |     IX_OSAL_ENSURE(((qbuf & ~IX_ETHNPE_QM_Q_TXENET_ADDR_MASK) == 0), | 
 | 	      "Invalid address range"); | 
 |  | 
 |     return qbuf; | 
 | } | 
 |  | 
 | /* Convert the mbuf header for NPE reception */ | 
 | PRIVATE UINT32 | 
 | ixEthAccMbufRxQPrepare(IX_OSAL_MBUF *mbuf) | 
 | { | 
 |     UINT32 len; | 
 |     UINT32 qbuf; | 
 |  | 
 |     if (IX_OSAL_MBUF_NEXT_BUFFER_IN_PKT_PTR(mbuf) == NULL) | 
 |     { | 
 | 	/* "best case" scenario : unchained mbufs */ | 
 | 	IX_ETH_ACC_STATS_INC(ixEthAccDataStats.unchainedRxFreeMBufs); | 
 |  | 
 | 	/* unchained mbufs : payload pointer */ | 
 | 	DATAPTR_VIRT2NPE(mbuf, IX_ETHACC_NE_DATA(mbuf)); | 
 |  | 
 | 	/* unchained mbufs : set the buffer length | 
 | 	* and the frame length field is zeroed | 
 | 	*/ | 
 | 	len = (IX_OSAL_MBUF_MLEN(mbuf) << IX_ETHNPE_ACC_LENGTH_OFFSET); | 
 | 	IX_ETHACC_NE_LEN(mbuf) = IX_OSAL_SWAP_BE_SHARED_LONG(len); | 
 |  | 
 | 	/* unchained mbufs : next pointer is null */ | 
 | 	IX_ETHACC_NE_NEXT(mbuf) = 0; | 
 |  | 
 | 	/* flush shared header after all address conversions */ | 
 | 	IX_ETHACC_NE_CACHE_FLUSH(mbuf); | 
 |  | 
 | 	/* remove shared header cache line */ | 
 | 	IX_ETHACC_NE_CACHE_INVALIDATE(mbuf); | 
 |     } | 
 |     else | 
 |     { | 
 | 	/* chained mbufs */ | 
 | 	IX_OSAL_MBUF *ptr = mbuf; | 
 | 	IX_OSAL_MBUF *nextPtr; | 
 |  | 
 | 	do | 
 | 	{ | 
 | 	    /* chained mbufs */ | 
 | 	    IX_ETH_ACC_STATS_INC(ixEthAccDataStats.chainedRxFreeMBufs); | 
 |  | 
 | 	    /* we must save virtual next chain pointer */ | 
 | 	    nextPtr = IX_OSAL_MBUF_NEXT_BUFFER_IN_PKT_PTR(ptr); | 
 |  | 
 | 	    if (nextPtr != NULL) | 
 | 	    { | 
 | 		/* chaining pointer for NPE */ | 
 | 		PTR_VIRT2NPE(IX_ETHACC_NE_SHARED(nextPtr), | 
 | 			     IX_ETHACC_NE_NEXT(ptr)); | 
 | 	    } | 
 | 	    else | 
 | 	    { | 
 | 		IX_ETHACC_NE_NEXT(ptr) = 0; | 
 | 	    } | 
 |  | 
 | 	    /* payload pointer */ | 
 | 	    DATAPTR_VIRT2NPE(ptr,IX_ETHACC_NE_DATA(ptr)); | 
 |  | 
 | 	    /* buffer length */ | 
 | 	    len = (IX_OSAL_MBUF_MLEN(ptr) << IX_ETHNPE_ACC_LENGTH_OFFSET); | 
 | 	    IX_ETHACC_NE_LEN(ptr) = IX_OSAL_SWAP_BE_SHARED_LONG(len); | 
 |  | 
 | 	    /* flush shared header after all address conversions */ | 
 | 	    IX_ETHACC_NE_CACHE_FLUSH(ptr); | 
 |  | 
 | 	    /* remove shared header cache line */ | 
 | 	    IX_ETHACC_NE_CACHE_INVALIDATE(ptr); | 
 |  | 
 | 	    /* next mbuf in the chain */ | 
 | 	    ptr = nextPtr; | 
 | 	} | 
 | 	while(ptr != NULL); | 
 |     } | 
 |  | 
 |     /* virt2phys mbuf itself */ | 
 |     qbuf = (UINT32)IX_OSAL_MMU_VIRT_TO_PHYS( | 
 | 		  IX_ETHACC_NE_SHARED(mbuf)); | 
 |  | 
 |     /* Ensure the bits which are reserved to exchange information with | 
 |      * the NPE are cleared | 
 |      * | 
 |      * If the mbuf address is not correctly aligned, or from an | 
 |      * incompatible memory range, there is no point to continue | 
 |      */ | 
 |     IX_OSAL_ENSURE(((qbuf & ~IX_ETHNPE_QM_Q_RXENET_ADDR_MASK) == 0), | 
 | 	      "Invalid address range"); | 
 |  | 
 |     return qbuf; | 
 | } | 
 |  | 
 | /* Convert the mbuf header after NPE transmission | 
 |  * Since there is nothing changed by the NPE, there is no need | 
 |  * to process anything but the update of internal stats | 
 |  * when they are enabled | 
 | */ | 
 | PRIVATE void | 
 | ixEthAccMbufFromTxQ(IX_OSAL_MBUF *mbuf) | 
 | { | 
 | #ifndef NDEBUG | 
 |     /* test for unchained mbufs */ | 
 |     if (IX_ETHACC_NE_NEXT(mbuf) == 0) | 
 |     { | 
 | 	/* unchained mbufs : update the stats */ | 
 | 	IX_ETH_ACC_STATS_INC(ixEthAccDataStats.unchainedTxDoneMBufs); | 
 |     } | 
 |     else | 
 |     { | 
 | 	/* chained mbufs : walk the chain and update the stats */ | 
 | 	IX_OSAL_MBUF *ptr = mbuf; | 
 |  | 
 | 	do | 
 | 	{ | 
 | 	    IX_ETH_ACC_STATS_INC(ixEthAccDataStats.chainedTxDoneMBufs); | 
 | 	    ptr = IX_OSAL_MBUF_NEXT_BUFFER_IN_PKT_PTR(ptr); | 
 | 	} | 
 | 	while (ptr != NULL); | 
 |     } | 
 | #endif | 
 | } | 
 |  | 
 | /* Convert the mbuf header after NPE reception */ | 
 | PRIVATE void | 
 | ixEthAccMbufFromRxQ(IX_OSAL_MBUF *mbuf) | 
 | { | 
 |     UINT32 len; | 
 |  | 
 |     /* endianess swap for tci and flags | 
 |        note: this is done only once, even for chained buffers */ | 
 |     IX_ETHACC_NE_FLAGS(mbuf)   = IX_OSAL_SWAP_BE_SHARED_SHORT(IX_ETHACC_NE_FLAGS(mbuf)); | 
 |     IX_ETHACC_NE_VLANTCI(mbuf) = IX_OSAL_SWAP_BE_SHARED_SHORT(IX_ETHACC_NE_VLANTCI(mbuf)); | 
 |  | 
 |     /* test for unchained mbufs */ | 
 |     if (IX_ETHACC_NE_NEXT(mbuf) == 0) | 
 |     { | 
 | 	/* unchained mbufs */ | 
 | 	IX_ETH_ACC_STATS_INC(ixEthAccDataStats.unchainedRxMBufs); | 
 |  | 
 | 	/* get the frame length. it is the same than the buffer length */ | 
 | 	len = IX_OSAL_SWAP_BE_SHARED_LONG(IX_ETHACC_NE_LEN(mbuf)); | 
 | 	len &= IX_ETHNPE_ACC_PKTLENGTH_MASK; | 
 | 	IX_OSAL_MBUF_PKT_LEN(mbuf) = IX_OSAL_MBUF_MLEN(mbuf) = len; | 
 |  | 
 |         /* clears the next packet field */ | 
 | 	IX_OSAL_MBUF_NEXT_BUFFER_IN_PKT_PTR(mbuf) = NULL; | 
 |     } | 
 |     else | 
 |     { | 
 | 	IX_OSAL_MBUF *ptr = mbuf; | 
 | 	IX_OSAL_MBUF *nextPtr; | 
 | 	UINT32 frmLen; | 
 |  | 
 | 	/* convert the frame length */ | 
 | 	frmLen = IX_OSAL_SWAP_BE_SHARED_LONG(IX_ETHACC_NE_LEN(mbuf)); | 
 | 	IX_OSAL_MBUF_PKT_LEN(mbuf) = (frmLen & IX_ETHNPE_ACC_PKTLENGTH_MASK); | 
 |  | 
 |         /* chained mbufs */ | 
 | 	do | 
 | 	{ | 
 | 	    IX_ETH_ACC_STATS_INC(ixEthAccDataStats.chainedRxMBufs); | 
 |  | 
 | 	    /* convert the length */ | 
 | 	    len = IX_OSAL_SWAP_BE_SHARED_LONG(IX_ETHACC_NE_LEN(ptr)); | 
 | 	    IX_OSAL_MBUF_MLEN(ptr) = (len >> IX_ETHNPE_ACC_LENGTH_OFFSET); | 
 |  | 
 |             /* get the next pointer */ | 
 |  	    PTR_NPE2VIRT(IX_OSAL_MBUF *,IX_ETHACC_NE_NEXT(ptr), nextPtr); | 
 | 	    if (nextPtr != NULL) | 
 | 	    { | 
 | 		nextPtr = (IX_OSAL_MBUF *)((UINT8 *)nextPtr - offsetof(IX_OSAL_MBUF,ix_ne)); | 
 | 	    } | 
 | 	    /* set the next pointer */ | 
 | 	    IX_OSAL_MBUF_NEXT_BUFFER_IN_PKT_PTR(ptr) = nextPtr; | 
 |  | 
 | 	    /* move to the next buffer */ | 
 | 	    ptr = nextPtr; | 
 | 	} | 
 | 	while (ptr != NULL); | 
 |     } | 
 | } | 
 |  | 
 | /* write to qmgr if possible and report an overflow if not possible | 
 |  * Use a fast lock to protect the queue write. | 
 |  * This way, the tx feature is reentrant. | 
 |  */ | 
 | PRIVATE IX_STATUS | 
 | ixEthAccQmgrLockTxWrite(IxEthAccPortId portId, UINT32 qBuffer) | 
 | { | 
 |     IX_STATUS qStatus; | 
 |     if (ixOsalFastMutexTryLock(&txWriteMutex[portId]) == IX_SUCCESS) | 
 |     { | 
 | 	qStatus = ixQMgrQWrite( | 
 | 	       IX_ETH_ACC_PORT_TO_TX_Q_ID(portId), | 
 | 	       &qBuffer); | 
 | #ifndef NDEBUG | 
 | 	if (qStatus != IX_SUCCESS) | 
 | 	{ | 
 | 	    TX_STATS_INC(portId, txOverflow); | 
 | 	} | 
 | #endif | 
 | 	ixOsalFastMutexUnlock(&txWriteMutex[portId]); | 
 |     } | 
 |     else | 
 |     { | 
 | 	TX_STATS_INC(portId, txLock); | 
 | 	qStatus = IX_QMGR_Q_OVERFLOW; | 
 |     } | 
 |     return qStatus; | 
 | } | 
 |  | 
 | /* write to qmgr if possible and report an overflow if not possible | 
 |  * Use a fast lock to protect the queue write. | 
 |  * This way, the Rx feature is reentrant. | 
 |  */ | 
 | PRIVATE IX_STATUS | 
 | ixEthAccQmgrLockRxWrite(IxEthAccPortId portId, UINT32 qBuffer) | 
 | { | 
 |     IX_STATUS qStatus; | 
 |     if (ixOsalFastMutexTryLock(&rxWriteMutex[portId]) == IX_SUCCESS) | 
 |     { | 
 | 	qStatus = ixQMgrQWrite( | 
 | 	       IX_ETH_ACC_PORT_TO_RX_FREE_Q_ID(portId), | 
 | 	       &qBuffer); | 
 | #ifndef NDEBUG | 
 | 	if (qStatus != IX_SUCCESS) | 
 | 	{ | 
 | 	    RX_STATS_INC(portId, rxFreeOverflow); | 
 | 	} | 
 | #endif | 
 | 	ixOsalFastMutexUnlock(&rxWriteMutex[portId]); | 
 |     } | 
 |     else | 
 |     { | 
 | 	RX_STATS_INC(portId, rxFreeLock); | 
 | 	qStatus = IX_QMGR_Q_OVERFLOW; | 
 |     } | 
 |     return qStatus; | 
 | } | 
 |  | 
 | /* | 
 |  * Set the priority and write to a qmgr queue. | 
 |  */ | 
 | PRIVATE IX_STATUS | 
 | ixEthAccQmgrTxWrite(IxEthAccPortId portId, UINT32 qBuffer, UINT32 priority) | 
 | { | 
 |     /* fill the priority field */ | 
 |     qBuffer |= (priority << IX_ETHNPE_QM_Q_FIELD_PRIOR_R); | 
 |  | 
 |     return ixEthAccQmgrLockTxWrite(portId, qBuffer); | 
 | } | 
 |  | 
 | /** | 
 |  * | 
 |  * @brief This function will discover the highest priority S/W Tx Q that | 
 |  *        has entries in it | 
 |  * | 
 |  * @param portId - (in) the id of the port whose S/W Tx queues are to be searched | 
 |  *        priorityPtr - (out) the priority of the highest priority occupied q will be written | 
 |  *                      here | 
 |  * | 
 |  * @return IX_ETH_ACC_SUCCESS if an occupied Q is found | 
 |  *         IX_ETH_ACC_FAIL if no Q has entries | 
 |  * | 
 |  * @internal | 
 |  */ | 
 | PRIVATE IxEthAccStatus | 
 | ixEthAccTxSwQHighestPriorityGet(IxEthAccPortId portId, | 
 | 				IxEthAccTxPriority *priorityPtr) | 
 | { | 
 |     if (ixEthAccPortData[portId].ixEthAccTxData.schDiscipline | 
 | 	== FIFO_NO_PRIORITY) | 
 |     { | 
 | 	if(IX_ETH_ACC_DATAPLANE_IS_Q_EMPTY(ixEthAccPortData[portId]. | 
 | 	       ixEthAccTxData.txQ[IX_ETH_ACC_TX_DEFAULT_PRIORITY])) | 
 | 	{ | 
 | 	    return IX_ETH_ACC_FAIL; | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 	    *priorityPtr = IX_ETH_ACC_TX_DEFAULT_PRIORITY; | 
 | 	    TX_STATS_INC(portId,txPriority[*priorityPtr]); | 
 | 	    return IX_ETH_ACC_SUCCESS; | 
 | 	} | 
 |     } | 
 |     else | 
 |     { | 
 | 	IxEthAccTxPriority highestPriority = IX_ETH_ACC_TX_PRIORITY_7; | 
 | 	while(1) | 
 | 	{ | 
 | 	    if(!IX_ETH_ACC_DATAPLANE_IS_Q_EMPTY(ixEthAccPortData[portId]. | 
 | 	       ixEthAccTxData.txQ[highestPriority])) | 
 | 	    { | 
 |  | 
 | 		*priorityPtr = highestPriority; | 
 | 		TX_STATS_INC(portId,txPriority[highestPriority]); | 
 | 		return IX_ETH_ACC_SUCCESS; | 
 |  | 
 | 	    } | 
 | 	    if (highestPriority == IX_ETH_ACC_TX_PRIORITY_0) | 
 | 	    { | 
 | 		return IX_ETH_ACC_FAIL; | 
 | 	    } | 
 | 	    highestPriority--; | 
 | 	} | 
 |     } | 
 | } | 
 |  | 
 | /** | 
 |  * | 
 |  * @brief This function will take a buffer from a TX S/W Q and attempt | 
 |  *        to add it to the relevant TX H/W Q | 
 |  * | 
 |  * @param portId - the port whose TX queue is to be written to | 
 |  *        priority - identifies the queue from which the entry is to be read | 
 |  * | 
 |  * @internal | 
 |  */ | 
 | PRIVATE IxEthAccStatus | 
 | ixEthAccTxFromSwQ(IxEthAccPortId portId, | 
 | 		  IxEthAccTxPriority priority) | 
 | { | 
 |     IX_OSAL_MBUF        *mbuf; | 
 |     IX_STATUS	   qStatus; | 
 |  | 
 |     IX_OSAL_ENSURE((UINT32)priority <= (UINT32)7, "Invalid priority"); | 
 |  | 
 |     IX_ETH_ACC_DATAPLANE_REMOVE_MBUF_FROM_Q_HEAD( | 
 | 	ixEthAccPortData[portId].ixEthAccTxData.txQ[priority], | 
 | 	mbuf); | 
 |  | 
 |     if (mbuf != NULL) | 
 |     { | 
 | 	/* | 
 | 	 * Add the Tx buffer to the H/W Tx Q | 
 | 	 * We do not need to flush here as it is already done | 
 | 	 * in TxFrameSubmit(). | 
 | 	 */ | 
 | 	qStatus = ixEthAccQmgrTxWrite( | 
 | 	      portId, | 
 | 	      IX_OSAL_MMU_VIRT_TO_PHYS((UINT32)IX_ETHACC_NE_SHARED(mbuf)), | 
 | 	      priority); | 
 |  | 
 | 	if (qStatus == IX_SUCCESS) | 
 | 	{ | 
 | 	    TX_STATS_INC(portId,txFromSwQOK); | 
 | 	    return IX_SUCCESS; | 
 | 	} | 
 | 	else if (qStatus == IX_QMGR_Q_OVERFLOW) | 
 | 	{ | 
 | 	    /* | 
 | 	     * H/W Q overflow, need to save the buffer | 
 | 	     * back on the s/w Q. | 
 | 	     * we must put it back on the head of the q to avoid | 
 | 	     * reordering packet tx | 
 | 	     */ | 
 | 	    TX_STATS_INC(portId,txFromSwQDelayed); | 
 | 	    IX_ETH_ACC_DATAPLANE_ADD_MBUF_TO_Q_HEAD( | 
 | 		ixEthAccPortData[portId].ixEthAccTxData.txQ[priority], | 
 | 		mbuf); | 
 |  | 
 | 	    /*enable Q notification*/ | 
 | 	    qStatus = ixQMgrNotificationEnable( | 
 | 		IX_ETH_ACC_PORT_TO_TX_Q_ID(portId), | 
 | 		IX_ETH_ACC_PORT_TO_TX_Q_SOURCE(portId)); | 
 |  | 
 |             if (qStatus != IX_SUCCESS && qStatus != IX_QMGR_WARNING) | 
 |             { | 
 | 		TX_INC(portId,txUnexpectedError); | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 	            "ixEthAccTxFromSwQ:Unexpected Error: %u\n", | 
 | 	            qStatus, 0, 0, 0, 0, 0); | 
 |             } | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 	    TX_INC(portId,txUnexpectedError); | 
 |  | 
 | 	    /* recovery attempt */ | 
 | 	    IX_ETH_ACC_DATAPLANE_ADD_MBUF_TO_Q_HEAD( | 
 | 		ixEthAccPortData[portId].ixEthAccTxData.txQ[priority], | 
 | 		mbuf); | 
 |  | 
 | 	    IX_ETH_ACC_FATAL_LOG( | 
 | 		"ixEthAccTxFromSwQ:Error: unexpected QM status 0x%08X\n", | 
 | 		qStatus, 0, 0, 0, 0, 0); | 
 | 	} | 
 |     } | 
 |     else | 
 |     { | 
 | 	/* sw queue is empty */ | 
 |     } | 
 |     return IX_ETH_ACC_FAIL; | 
 | } | 
 |  | 
 | /** | 
 |  * | 
 |  * @brief This function will take a buffer from a RXfree S/W Q and attempt | 
 |  *        to add it to the relevant RxFree H/W Q | 
 |  * | 
 |  * @param portId - the port whose RXFree queue is to be written to | 
 |  * | 
 |  * @internal | 
 |  */ | 
 | PRIVATE IxEthAccStatus | 
 | ixEthAccRxFreeFromSwQ(IxEthAccPortId portId) | 
 | { | 
 |     IX_OSAL_MBUF        *mbuf; | 
 |     IX_STATUS	   qStatus = IX_SUCCESS; | 
 |  | 
 |     IX_ETH_ACC_DATAPLANE_REMOVE_MBUF_FROM_Q_HEAD( | 
 | 	  ixEthAccPortData[portId].ixEthAccRxData.freeBufferList, | 
 | 	  mbuf); | 
 |     if (mbuf != NULL) | 
 |     { | 
 | 	/* | 
 | 	 * Add The Rx Buffer to the H/W Free buffer Q if possible | 
 | 	 */ | 
 | 	qStatus = ixEthAccQmgrLockRxWrite(portId, | 
 | 		  IX_OSAL_MMU_VIRT_TO_PHYS( | 
 | 			 (UINT32)IX_ETHACC_NE_SHARED(mbuf))); | 
 |  | 
 | 	if (qStatus == IX_SUCCESS) | 
 | 	{ | 
 | 	    RX_STATS_INC(portId,rxFreeRepFromSwQOK); | 
 | 	    /* | 
 | 	     * Buffer added to h/w Q. | 
 | 	     */ | 
 | 	    return IX_SUCCESS; | 
 | 	} | 
 | 	else if (qStatus == IX_QMGR_Q_OVERFLOW) | 
 | 	{ | 
 | 	    /* | 
 | 	     * H/W Q overflow, need to save the buffer back on the s/w Q. | 
 | 	     */ | 
 | 	    RX_STATS_INC(portId,rxFreeRepFromSwQDelayed); | 
 |  | 
 | 	    IX_ETH_ACC_DATAPLANE_ADD_MBUF_TO_Q_HEAD( | 
 | 		   ixEthAccPortData[portId].ixEthAccRxData.freeBufferList, | 
 | 		   mbuf); | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 	    /* unexpected qmgr error */ | 
 | 	    RX_INC(portId,rxUnexpectedError); | 
 |  | 
 | 	    IX_ETH_ACC_DATAPLANE_ADD_MBUF_TO_Q_HEAD( | 
 | 		    ixEthAccPortData[portId].ixEthAccRxData.freeBufferList, | 
 | 		    mbuf); | 
 |  | 
 | 	    IX_ETH_ACC_FATAL_LOG("IxEthAccRxFreeFromSwQ:Error: unexpected QM status 0x%08X\n", | 
 | 				 qStatus, 0, 0, 0, 0, 0); | 
 | 	} | 
 |     } | 
 |     else | 
 |     { | 
 | 	/* sw queue is empty */ | 
 |     } | 
 |     return IX_ETH_ACC_FAIL; | 
 | } | 
 |  | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccInitDataPlane() | 
 | { | 
 |     UINT32 portId; | 
 |  | 
 |     /* | 
 |      * Initialize the service and register callback to other services. | 
 |      */ | 
 |  | 
 |     IX_ETH_ACC_MEMSET(&ixEthAccDataStats, | 
 | 		      0, | 
 | 		      sizeof(ixEthAccDataStats)); | 
 |  | 
 |     for(portId=0; portId < IX_ETH_ACC_NUMBER_OF_PORTS; portId++) | 
 |     { | 
 | 	ixOsalFastMutexInit(&txWriteMutex[portId]); | 
 | 	ixOsalFastMutexInit(&rxWriteMutex[portId]); | 
 |  | 
 | 	IX_ETH_ACC_MEMSET(&ixEthAccPortData[portId], | 
 | 			  0, | 
 | 			  sizeof(ixEthAccPortData[portId])); | 
 |  | 
 | 	ixEthAccPortData[portId].ixEthAccTxData.schDiscipline = FIFO_NO_PRIORITY; | 
 |     } | 
 |  | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccPortTxDoneCallbackRegister(IxEthAccPortId portId, | 
 | 						  IxEthAccPortTxDoneCallback | 
 | 						  txCallbackFn, | 
 | 						  UINT32 callbackTag) | 
 | { | 
 |     if (!IX_ETH_ACC_IS_SERVICE_INITIALIZED()) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_PORT); | 
 |     } | 
 |  | 
 | /* HACK: removing this code to enable NPE-A preliminary testing | 
 |  *    if (IX_ETH_ACC_SUCCESS != ixEthAccSingleEthNpeCheck(portId)) | 
 |  *    { | 
 |  *        IX_ETH_ACC_WARNING_LOG("ixEthAccPortTxDoneCallbackRegister: Unavailable Eth %d: Cannot register TxDone Callback.\n",(INT32)portId,0,0,0,0,0); | 
 |  *        return IX_ETH_ACC_SUCCESS ; | 
 |  *    } | 
 |  */ | 
 |  | 
 |     if (!IX_ETH_IS_PORT_INITIALIZED(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_PORT_UNINITIALIZED); | 
 |     } | 
 |     if (txCallbackFn == 0) | 
 | 	/* Check for null function pointer here. */ | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 |     ixEthAccPortData[portId].ixEthAccTxData.txBufferDoneCallbackFn = txCallbackFn; | 
 |     ixEthAccPortData[portId].ixEthAccTxData.txCallbackTag = callbackTag; | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccPortRxCallbackRegister(IxEthAccPortId portId, | 
 | 					      IxEthAccPortRxCallback | 
 | 					      rxCallbackFn, | 
 | 					      UINT32 callbackTag) | 
 | { | 
 |     IxEthAccPortId port; | 
 |  | 
 |     if (!IX_ETH_ACC_IS_SERVICE_INITIALIZED()) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_PORT); | 
 |     } | 
 |  | 
 |     if (IX_ETH_ACC_SUCCESS != ixEthAccSingleEthNpeCheck(portId)) | 
 |     { | 
 |         IX_ETH_ACC_WARNING_LOG("ixEthAccPortRxCallbackRegister: Unavailable Eth %d: Cannot register Rx Callback.\n",(INT32)portId,0,0,0,0,0); | 
 |         return IX_ETH_ACC_SUCCESS ; | 
 |     } | 
 |  | 
 |     if (!IX_ETH_IS_PORT_INITIALIZED(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_PORT_UNINITIALIZED); | 
 |     } | 
 |  | 
 |     /* Check for null function pointer here. */ | 
 |     if (rxCallbackFn == NULL) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 |  | 
 |     /* Check the user is not changing the callback type | 
 |      * when the port is enabled. | 
 |     */ | 
 |     if (ixEthAccMacState[portId].portDisableState == ACTIVE) | 
 |     { | 
 | 	for (port = 0; port < IX_ETH_ACC_NUMBER_OF_PORTS; port++) | 
 | 	{ | 
 | 	    if ((ixEthAccMacState[port].portDisableState == ACTIVE) | 
 | 		&& (ixEthAccPortData[port].ixEthAccRxData.rxMultiBufferCallbackInUse == TRUE)) | 
 | 	    { | 
 | 		/* one of the active ports has a different rx callback type. | 
 | 		 * Changing the callback type when the port is enabled | 
 | 		 * is not safe | 
 | 		 */ | 
 | 		return (IX_ETH_ACC_INVALID_ARG); | 
 | 	    } | 
 | 	} | 
 |     } | 
 |  | 
 |     /* update the callback pointer : this is done before | 
 |      * registering the new qmgr callback | 
 |      */ | 
 |     ixEthAccPortData[portId].ixEthAccRxData.rxCallbackFn = rxCallbackFn; | 
 |     ixEthAccPortData[portId].ixEthAccRxData.rxCallbackTag = callbackTag; | 
 |  | 
 |     /* update the qmgr callback for rx queues */ | 
 |     if (ixEthAccQMgrRxCallbacksRegister(ixEthRxFrameQMCallback) | 
 | 	!= IX_ETH_ACC_SUCCESS) | 
 |     { | 
 | 	/* unexpected qmgr error */ | 
 |         IX_ETH_ACC_FATAL_LOG("ixEthAccPortRxCallbackRegister: unexpected QMgr error, " \ | 
 |             "could not register Rx single-buffer callback\n", 0, 0, 0, 0, 0, 0); | 
 |  | 
 | 	RX_INC(portId,rxUnexpectedError); | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 |  | 
 |     ixEthAccPortData[portId].ixEthAccRxData.rxMultiBufferCallbackInUse = FALSE; | 
 |  | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccPortMultiBufferRxCallbackRegister( | 
 | 			 IxEthAccPortId portId, | 
 | 			 IxEthAccPortMultiBufferRxCallback | 
 | 			 rxCallbackFn, | 
 | 			 UINT32 callbackTag) | 
 | { | 
 |     IxEthAccPortId port; | 
 |  | 
 |     if (!IX_ETH_ACC_IS_SERVICE_INITIALIZED()) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_PORT); | 
 |     } | 
 |  | 
 |     if (IX_ETH_ACC_SUCCESS != ixEthAccSingleEthNpeCheck(portId)) | 
 |     { | 
 |         IX_ETH_ACC_WARNING_LOG("ixEthAccPortMultiBufferRxCallbackRegister: Unavailable Eth %d: Cannot register Rx Callback.\n",(INT32)portId,0,0,0,0,0); | 
 |         return IX_ETH_ACC_SUCCESS ; | 
 |     } | 
 |  | 
 |     if (!IX_ETH_IS_PORT_INITIALIZED(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_PORT_UNINITIALIZED); | 
 |     } | 
 |  | 
 |     /* Check for null function pointer here. */ | 
 |     if (rxCallbackFn == NULL) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 |  | 
 |     /* Check the user is not changing the callback type | 
 |      * when the port is enabled. | 
 |     */ | 
 |     if (ixEthAccMacState[portId].portDisableState == ACTIVE) | 
 |     { | 
 | 	for (port = 0; port < IX_ETH_ACC_NUMBER_OF_PORTS; port++) | 
 | 	{ | 
 | 	    if ((ixEthAccMacState[port].portDisableState == ACTIVE) | 
 | 		&& (ixEthAccPortData[port].ixEthAccRxData.rxMultiBufferCallbackInUse == FALSE)) | 
 | 	    { | 
 | 		/* one of the active ports has a different rx callback type. | 
 | 		 * Changing the callback type when the port is enabled | 
 | 		 * is not safe | 
 | 		 */ | 
 | 		return (IX_ETH_ACC_INVALID_ARG); | 
 | 	    } | 
 | 	} | 
 |     } | 
 |  | 
 |     /* update the callback pointer : this is done before | 
 |      * registering the new qmgr callback | 
 |      */ | 
 |     ixEthAccPortData[portId].ixEthAccRxData.rxMultiBufferCallbackFn = rxCallbackFn; | 
 |     ixEthAccPortData[portId].ixEthAccRxData.rxMultiBufferCallbackTag = callbackTag; | 
 |  | 
 |     /* update the qmgr callback for rx queues */ | 
 |     if (ixEthAccQMgrRxCallbacksRegister(ixEthRxMultiBufferQMCallback) | 
 | 	!= IX_ETH_ACC_SUCCESS) | 
 |     { | 
 | 	/* unexpected qmgr error */ | 
 | 	RX_INC(portId,rxUnexpectedError); | 
 |  | 
 |         IX_ETH_ACC_FATAL_LOG("ixEthAccPortMultiBufferRxCallbackRegister: unexpected QMgr error, " \ | 
 |             "could not register Rx multi-buffer callback\n", 0, 0, 0, 0, 0, 0); | 
 |  | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 |  | 
 |     ixEthAccPortData[portId].ixEthAccRxData.rxMultiBufferCallbackInUse = TRUE; | 
 |  | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccPortTxFrameSubmit(IxEthAccPortId portId, | 
 | 					 IX_OSAL_MBUF *buffer, | 
 | 					 IxEthAccTxPriority priority) | 
 | { | 
 |     IX_STATUS	qStatus = IX_SUCCESS; | 
 |     UINT32      qBuffer; | 
 |     IxEthAccTxPriority highestPriority; | 
 |     IxQMgrQStatus txQStatus; | 
 |  | 
 | #ifndef NDEBUG | 
 |     if (buffer == NULL) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |     if (!IX_ETH_ACC_IS_SERVICE_INITIALIZED()) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_PORT); | 
 |     } | 
 |  | 
 |     if (IX_ETH_ACC_SUCCESS != ixEthAccSingleEthNpeCheck(portId)) | 
 |     { | 
 |         IX_ETH_ACC_FATAL_LOG("ixEthAccPortTxFrameSubmit: Unavailable Eth %d: Cannot submit Tx Frame.\n", | 
 | 			     (INT32)portId,0,0,0,0,0); | 
 |         return IX_ETH_ACC_PORT_UNINITIALIZED ; | 
 |     } | 
 |  | 
 |     if (!IX_ETH_IS_PORT_INITIALIZED(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_PORT_UNINITIALIZED); | 
 |     } | 
 |     if ((UINT32)priority > (UINT32)IX_ETH_ACC_TX_PRIORITY_7) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 | #endif | 
 |  | 
 |     /* | 
 |      * Need to Flush the MBUF and its contents (data) as it may be | 
 |      * read from the NPE. Convert virtual addresses to physical addresses also. | 
 |      */ | 
 |     qBuffer = ixEthAccMbufTxQPrepare(buffer); | 
 |  | 
 |     /* | 
 |      * If no fifo priority set on Xscale ... | 
 |      */ | 
 |     if (ixEthAccPortData[portId].ixEthAccTxData.schDiscipline == | 
 | 	FIFO_NO_PRIORITY) | 
 |     { | 
 | 	/* | 
 | 	 * Add The Tx Buffer to the H/W Tx Q if possible | 
 | 	 * (the priority is passed to the NPE, because | 
 | 	 * the NPE is able to reorder the frames | 
 | 	 * before transmission to the underlying hardware) | 
 | 	 */ | 
 | 	qStatus = ixEthAccQmgrTxWrite(portId, | 
 | 				      qBuffer, | 
 | 				      IX_ETH_ACC_TX_DEFAULT_PRIORITY); | 
 |  | 
 | 	if (qStatus == IX_SUCCESS) | 
 | 	{ | 
 | 	    TX_STATS_INC(portId,txQOK); | 
 |  | 
 | 	    /* | 
 | 	     * "best case" scenario : Buffer added to h/w Q. | 
 | 	     */ | 
 | 	    return (IX_SUCCESS); | 
 | 	} | 
 | 	else if (qStatus == IX_QMGR_Q_OVERFLOW) | 
 | 	{ | 
 | 	    /* | 
 | 	     * We were unable to write the buffer to the | 
 | 	     * appropriate H/W Q,  Save it in the sw Q. | 
 | 	     * (use the default priority queue regardless of | 
 | 	     * input parameter) | 
 | 	     */ | 
 | 	    priority = IX_ETH_ACC_TX_DEFAULT_PRIORITY; | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 	    /* unexpected qmgr error */ | 
 | 	    TX_INC(portId,txUnexpectedError); | 
 | 	    IX_ETH_ACC_FATAL_LOG( | 
 | 		"ixEthAccPortTxFrameSubmit:Error: qStatus = %u\n", | 
 | 		(UINT32)qStatus, 0, 0, 0, 0, 0); | 
 | 	    return (IX_ETH_ACC_FAIL); | 
 | 	} | 
 |     } | 
 |     else if (ixEthAccPortData[portId].ixEthAccTxData.schDiscipline == | 
 | 	     FIFO_PRIORITY) | 
 |     { | 
 |  | 
 | 	/* | 
 | 	 * For priority transmission, put the frame directly on the H/W queue | 
 | 	 * if the H/W queue is empty, otherwise, put it in a S/W Q | 
 | 	 */ | 
 | 	ixQMgrQStatusGet(IX_ETH_ACC_PORT_TO_TX_Q_ID(portId), &txQStatus); | 
 | 	if((txQStatus & IX_QMGR_Q_STATUS_E_BIT_MASK) != 0) | 
 | 	{ | 
 | 	    /*The tx queue is empty, check whether there are buffers on the s/w queues*/ | 
 | 	    if(ixEthAccTxSwQHighestPriorityGet(portId,  &highestPriority) | 
 | 	       !=IX_ETH_ACC_FAIL) | 
 | 	    { | 
 | 		/*there are buffers on the s/w queues, submit them*/ | 
 | 		ixEthAccTxFromSwQ(portId, highestPriority); | 
 |  | 
 | 		/* the queue was empty, 1 buffer is already supplied | 
 | 		 * but is likely to be immediately transmitted and the | 
 | 		 * hw queue is likely to be empty again, so submit | 
 | 		 * more from the sw queues | 
 | 		 */ | 
 | 		if(ixEthAccTxSwQHighestPriorityGet(portId,  &highestPriority) | 
 | 		   !=IX_ETH_ACC_FAIL) | 
 | 		{ | 
 | 		    ixEthAccTxFromSwQ(portId, highestPriority); | 
 | 		    /* | 
 | 		     * and force the buffer supplied to be placed | 
 | 		     * on a priority queue | 
 | 		     */ | 
 | 		    qStatus = IX_QMGR_Q_OVERFLOW; | 
 | 		} | 
 | 		else | 
 | 		{ | 
 | 		    /*there are no buffers in the s/w queues, submit directly*/ | 
 | 		    qStatus = ixEthAccQmgrTxWrite(portId, qBuffer, priority); | 
 | 		} | 
 | 	    } | 
 | 	    else | 
 | 	    { | 
 | 		/*there are no buffers in the s/w queues, submit directly*/ | 
 | 		qStatus = ixEthAccQmgrTxWrite(portId, qBuffer, priority); | 
 | 	    } | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 	    qStatus = IX_QMGR_Q_OVERFLOW; | 
 | 	} | 
 |     } | 
 |     else | 
 |     { | 
 | 	TX_INC(portId,txUnexpectedError); | 
 | 	IX_ETH_ACC_FATAL_LOG( | 
 | 	    "ixEthAccPortTxFrameSubmit:Error: wrong schedule discipline setup\n", | 
 | 	    0, 0, 0, 0, 0, 0); | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |  | 
 |     if(qStatus == IX_SUCCESS ) | 
 |     { | 
 | 	TX_STATS_INC(portId,txQOK); | 
 | 	return IX_ETH_ACC_SUCCESS; | 
 |     } | 
 |     else if(qStatus == IX_QMGR_Q_OVERFLOW) | 
 |     { | 
 | 	TX_STATS_INC(portId,txQDelayed); | 
 | 	/* | 
 | 	 * We were unable to write the buffer to the | 
 | 	 * appropriate H/W Q,  Save it in a s/w Q. | 
 | 	 */ | 
 | 	IX_ETH_ACC_DATAPLANE_ADD_MBUF_TO_Q_TAIL( | 
 | 		ixEthAccPortData[portId]. | 
 | 		ixEthAccTxData.txQ[priority], | 
 | 		buffer); | 
 |  | 
 | 	qStatus = ixQMgrNotificationEnable( | 
 | 		IX_ETH_ACC_PORT_TO_TX_Q_ID(portId), | 
 | 		IX_ETH_ACC_PORT_TO_TX_Q_SOURCE(portId)); | 
 |  | 
 |         if (qStatus != IX_SUCCESS) | 
 | 	{ | 
 | 	    if (qStatus == IX_QMGR_WARNING) | 
 | 	    { | 
 | 		/* notification is enabled for a queue | 
 | 		 * which is already empty (the condition is already met) | 
 | 		 * and there will be no more queue event to drain the sw queue | 
 | 		 */ | 
 | 		TX_STATS_INC(portId,txLateNotificationEnabled); | 
 |  | 
 | 		/* pull a buffer from the sw queue */ | 
 | 		if(ixEthAccTxSwQHighestPriorityGet(portId,  &highestPriority) | 
 | 		   !=IX_ETH_ACC_FAIL) | 
 | 		{ | 
 | 		    /*there are buffers on the s/w queues, submit from them*/ | 
 | 		    ixEthAccTxFromSwQ(portId, highestPriority); | 
 | 		} | 
 | 	    } | 
 | 	    else | 
 | 	    { | 
 | 		TX_INC(portId,txUnexpectedError); | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 		     "ixEthAccPortTxFrameSubmit: unexpected Error: %u\n", | 
 | 		     qStatus, 0, 0, 0, 0, 0); | 
 | 	    } | 
 |         } | 
 |     } | 
 |     else | 
 |     { | 
 | 	TX_INC(portId,txUnexpectedError); | 
 | 	IX_ETH_ACC_FATAL_LOG( | 
 | 	     "ixEthAccPortTxFrameSubmit: unexpected Error: %u\n", | 
 | 	     qStatus, 0, 0, 0, 0, 0); | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |  | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 |  | 
 | /** | 
 |  * | 
 |  * @brief replenish: convert a chain of mbufs to the format | 
 |  *        expected by the NPE | 
 |  * | 
 |   */ | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccPortRxFreeReplenish(IxEthAccPortId portId, | 
 | 					   IX_OSAL_MBUF *buffer) | 
 | { | 
 |     IX_STATUS	qStatus = IX_SUCCESS; | 
 |     UINT32      qBuffer; | 
 |  | 
 |     /* | 
 |      * Check buffer is valid. | 
 |      */ | 
 |  | 
 | #ifndef NDEBUG | 
 |     /* check parameter value */ | 
 |     if (buffer == 0) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |     if (!IX_ETH_ACC_IS_SERVICE_INITIALIZED()) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_PORT); | 
 |     } | 
 |  | 
 |     /* check initialisation is done */ | 
 |     if (IX_ETH_ACC_SUCCESS != ixEthAccSingleEthNpeCheck(portId)) | 
 |     { | 
 |         IX_ETH_ACC_FATAL_LOG(" ixEthAccPortRxFreeReplenish: Unavailable Eth %d: Cannot replenish Rx Free Q.\n",(INT32)portId,0,0,0,0,0); | 
 |         return IX_ETH_ACC_PORT_UNINITIALIZED ; | 
 |     } | 
 |  | 
 |     if (!IX_ETH_IS_PORT_INITIALIZED(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_PORT_UNINITIALIZED); | 
 |     } | 
 |     /* check boundaries and constraints */ | 
 |     if (IX_OSAL_MBUF_MLEN(buffer) < IX_ETHNPE_ACC_RXFREE_BUFFER_LENGTH_MIN) | 
 |     { | 
 | 	return (IX_ETH_ACC_FAIL); | 
 |     } | 
 | #endif | 
 |  | 
 |     qBuffer = ixEthAccMbufRxQPrepare(buffer); | 
 |  | 
 |     /* | 
 |      * Add The Rx Buffer to the H/W Free buffer Q if possible | 
 |      */ | 
 |     qStatus = ixEthAccQmgrLockRxWrite(portId, qBuffer); | 
 |  | 
 |     if (qStatus == IX_SUCCESS) | 
 |     { | 
 | 	RX_STATS_INC(portId,rxFreeRepOK); | 
 | 	/* | 
 | 	 * Buffer added to h/w Q. | 
 | 	 */ | 
 | 	return (IX_SUCCESS); | 
 |     } | 
 |     else if (qStatus == IX_QMGR_Q_OVERFLOW) | 
 |     { | 
 | 	RX_STATS_INC(portId,rxFreeRepDelayed); | 
 | 	/* | 
 | 	 * We were unable to write the buffer to the approprate H/W Q, | 
 | 	 * Save it in a s/w Q. | 
 | 	 */ | 
 | 	IX_ETH_ACC_DATAPLANE_ADD_MBUF_TO_Q_TAIL( | 
 | 	    ixEthAccPortData[portId].ixEthAccRxData.freeBufferList, | 
 | 	    buffer); | 
 |  | 
 | 	qStatus = ixQMgrNotificationEnable( | 
 | 	    IX_ETH_ACC_PORT_TO_RX_FREE_Q_ID(portId), | 
 | 	    IX_ETH_ACC_PORT_TO_RX_FREE_Q_SOURCE(portId)); | 
 |  | 
 |         if (qStatus != IX_SUCCESS) | 
 | 	{ | 
 | 	    if (qStatus == IX_QMGR_WARNING) | 
 | 	    { | 
 | 		/* notification is enabled for a queue | 
 | 		 * which is already empty (the condition is already met) | 
 | 		 * and there will be no more queue event to drain the sw queue | 
 | 		 * move an entry from the sw queue to the hw queue */ | 
 | 		RX_STATS_INC(portId,rxFreeLateNotificationEnabled); | 
 | 		ixEthAccRxFreeFromSwQ(portId); | 
 | 	    } | 
 | 	    else | 
 | 	    { | 
 | 		RX_INC(portId,rxUnexpectedError); | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 		     "ixEthAccRxPortFreeReplenish:Error: %u\n", | 
 | 		     qStatus, 0, 0, 0, 0, 0); | 
 | 	    } | 
 |         } | 
 |     } | 
 |     else | 
 |     { | 
 | 	RX_INC(portId,rxUnexpectedError); | 
 | 	IX_ETH_ACC_FATAL_LOG( | 
 | 	    "ixEthAccRxPortFreeReplenish:Error: qStatus = %u\n", | 
 | 	    (UINT32)qStatus, 0, 0, 0, 0, 0); | 
 |         return(IX_ETH_ACC_FAIL); | 
 |     } | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccTxSchedulingDisciplineSetPriv(IxEthAccPortId portId, | 
 | 						 IxEthAccSchedulerDiscipline | 
 | 						 sched) | 
 | { | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_PORT); | 
 |     } | 
 |  | 
 |     if (IX_ETH_ACC_SUCCESS != ixEthAccSingleEthNpeCheck(portId)) | 
 |     { | 
 |         IX_ETH_ACC_WARNING_LOG("ixEthAccTxSchedulingDisciplineSet: Unavailable Eth %d: Cannot set Tx Scheduling Discipline.\n",(INT32)portId,0,0,0,0,0); | 
 |         return IX_ETH_ACC_SUCCESS ; | 
 |     } | 
 |  | 
 |     if (!IX_ETH_IS_PORT_INITIALIZED(portId)) | 
 |     { | 
 | 	return (IX_ETH_ACC_PORT_UNINITIALIZED); | 
 |     } | 
 |  | 
 |     if (sched != FIFO_PRIORITY && sched != FIFO_NO_PRIORITY) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 |  | 
 |     ixEthAccPortData[portId].ixEthAccTxData.schDiscipline = sched; | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | IxEthAccStatus ixEthAccRxSchedulingDisciplineSetPriv(IxEthAccSchedulerDiscipline | 
 | 						 sched) | 
 | { | 
 |     if (sched != FIFO_PRIORITY && sched != FIFO_NO_PRIORITY) | 
 |     { | 
 | 	return (IX_ETH_ACC_INVALID_ARG); | 
 |     } | 
 |  | 
 |     ixEthAccDataInfo.schDiscipline = sched; | 
 |  | 
 |     return (IX_ETH_ACC_SUCCESS); | 
 | } | 
 |  | 
 |  | 
 | /** | 
 |  * @fn ixEthRxFrameProcess(IxEthAccPortId portId, IX_OSAL_MBUF *mbufPtr) | 
 |  * | 
 |  * @brief process incoming frame : | 
 |  * | 
 |  * @param @ref IxQMgrCallback IxQMgrMultiBufferCallback | 
 |  * | 
 |  * @return none | 
 |  * | 
 |  * @internal | 
 |  * | 
 |  */ | 
 | IX_ETH_ACC_PRIVATE BOOL | 
 | ixEthRxFrameProcess(IxEthAccPortId portId, IX_OSAL_MBUF *mbufPtr) | 
 | { | 
 |     UINT32 flags; | 
 |     IxEthDBStatus result; | 
 |  | 
 | #ifndef NDEBUG | 
 |     /* Prudent to at least check the port is within range */ | 
 |     if (portId >= IX_ETH_ACC_NUMBER_OF_PORTS) | 
 |     { | 
 | 	ixEthAccDataStats.unexpectedError++; | 
 | 	IX_ETH_ACC_FATAL_LOG( | 
 | 	     "ixEthRxFrameProcess: Illegal port: %u\n", | 
 | 	     (UINT32)portId, 0, 0, 0, 0, 0); | 
 | 	return FALSE; | 
 |     } | 
 | #endif | 
 |  | 
 |     /* convert fields from mbuf header */ | 
 |     ixEthAccMbufFromRxQ(mbufPtr); | 
 |  | 
 |     /* check about any special processing for this frame */ | 
 |     flags = IX_ETHACC_NE_FLAGS(mbufPtr); | 
 |     if ((flags & (IX_ETHACC_NE_FILTERMASK | IX_ETHACC_NE_NEWSRCMASK)) == 0) | 
 |     { | 
 | 	/* "best case" scenario : nothing special to do for this frame */ | 
 | 	return TRUE; | 
 |     } | 
 |  | 
 | #ifdef CONFIG_IXP425_COMPONENT_ETHDB | 
 |     /* if a new source MAC address is detected by the NPE, | 
 |      * update IxEthDB with the portId and the MAC address. | 
 |      */ | 
 |     if ((flags & IX_ETHACC_NE_NEWSRCMASK & ixEthAccNewSrcMask) != 0) | 
 |     { | 
 |         result = ixEthDBFilteringDynamicEntryProvision(portId, | 
 | 			  (IxEthDBMacAddr *) IX_ETHACC_NE_SOURCEMAC(mbufPtr)); | 
 |  | 
 | 	if (result != IX_ETH_DB_SUCCESS && result != IX_ETH_DB_FEATURE_UNAVAILABLE) | 
 | 	{ | 
 |             if ((ixEthAccMacState[portId].portDisableState == ACTIVE) && (result != IX_ETH_DB_BUSY)) | 
 |             { | 
 | 	        RX_STATS_INC(portId, rxUnexpectedError); | 
 |                 IX_ETH_ACC_FATAL_LOG("ixEthRxFrameProcess: Failed to add source MAC \ | 
 |                                     to the Learning/Filtering database\n", 0, 0, 0, 0, 0, 0); | 
 |             } | 
 |             else | 
 |             { | 
 |                 /* we expect this to fail during PortDisable, as EthDB is disabled for | 
 |                  * that port and will refuse to learn new addresses | 
 | 		 */ | 
 |             } | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 	    RX_STATS_INC(portId, rxUnlearnedMacAddress); | 
 | 	} | 
 |     } | 
 | #endif | 
 |  | 
 |     /* check if this frame should have been filtered | 
 |      * by the NPE and take the appropriate action | 
 |      */ | 
 |     if (((flags & IX_ETHACC_NE_FILTERMASK) != 0) | 
 |         && (ixEthAccMacState[portId].portDisableState == ACTIVE)) | 
 |     { | 
 |         /* If the mbuf was allocated with a small data size, or the current data pointer is not | 
 |          * within the allocated data area, then the buffer is non-standard and has to be | 
 |          * replenished with the minimum size only | 
 |          */ | 
 |         if( (IX_OSAL_MBUF_ALLOCATED_BUFF_LEN(mbufPtr) < IX_ETHNPE_ACC_RXFREE_BUFFER_LENGTH_MIN) | 
 |            || ((UINT8 *)IX_OSAL_MBUF_ALLOCATED_BUFF_DATA(mbufPtr) > IX_OSAL_MBUF_MDATA(mbufPtr)) | 
 |            || ((UINT8 *)(IX_OSAL_MBUF_ALLOCATED_BUFF_DATA(mbufPtr) + | 
 |               IX_OSAL_MBUF_ALLOCATED_BUFF_LEN(mbufPtr)) | 
 |                < IX_OSAL_MBUF_MDATA(mbufPtr)) ) | 
 |         { | 
 |             /* set to minimum length */ | 
 |             IX_OSAL_MBUF_MLEN(mbufPtr) = IX_OSAL_MBUF_PKT_LEN(mbufPtr) = | 
 |                 IX_ETHNPE_ACC_RXFREE_BUFFER_LENGTH_MIN; | 
 |         } | 
 |         else | 
 |         { | 
 |             /* restore original length */ | 
 |             IX_OSAL_MBUF_MLEN(mbufPtr) = IX_OSAL_MBUF_PKT_LEN(mbufPtr) = | 
 |                 ( IX_OSAL_MBUF_ALLOCATED_BUFF_LEN(mbufPtr) - | 
 |                  (IX_OSAL_MBUF_MDATA(mbufPtr) - (UINT8 *)IX_OSAL_MBUF_ALLOCATED_BUFF_DATA(mbufPtr)) ); | 
 |         } | 
 |  | 
 |         /* replenish from here */ | 
 |         if (ixEthAccPortRxFreeReplenish(portId, mbufPtr) != IX_ETH_ACC_SUCCESS) | 
 |         { | 
 |                 IX_ETH_ACC_FATAL_LOG("ixEthRxFrameProcess: Failed to replenish with filtered frame\ | 
 |                                       on port %d\n", portId, 0, 0, 0, 0, 0); | 
 |         } | 
 |  | 
 |         RX_STATS_INC(portId, rxFiltered); | 
 |  | 
 |         /* indicate that frame should not be subjected to further processing */ | 
 |         return FALSE; | 
 |     } | 
 |  | 
 |     return TRUE; | 
 | } | 
 |  | 
 |  | 
 | /** | 
 |  * @fn ixEthRxFrameQMCallback | 
 |  * | 
 |  * @brief receive callback for Frame receive Q from NPE | 
 |  * | 
 |  * Frames are passed one-at-a-time to the user | 
 |  * | 
 |  * @param @ref IxQMgrCallback | 
 |  * | 
 |  * @return none | 
 |  * | 
 |  * @internal | 
 |  * | 
 |  * Design note : while processing the entry X, entry X+1 is preloaded | 
 |  * into memory to reduce the number of stall cycles | 
 |  * | 
 |  */ | 
 | void ixEthRxFrameQMCallback(IxQMgrQId qId, IxQMgrCallbackId callbackId) | 
 | { | 
 |     IX_OSAL_MBUF    *mbufPtr; | 
 |     IX_OSAL_MBUF    *nextMbufPtr; | 
 |     UINT32     qEntry; | 
 |     UINT32     nextQEntry; | 
 |     UINT32     *qEntryPtr; | 
 |     UINT32     portId; | 
 |     UINT32     destPortId; | 
 |     UINT32     npeId; | 
 |     UINT32     rxQReadStatus; | 
 |  | 
 |     /* | 
 |      * Design note : entries are read in a buffer, This buffer contains | 
 |      * an extra zeroed entry so the loop will | 
 |      * always terminate on a null entry, whatever the result of Burst read is. | 
 |      */ | 
 |     UINT32 rxQEntry[IX_ETH_ACC_MAX_RX_FRAME_CONSUME_PER_CALLBACK + 1]; | 
 |  | 
 |     /* | 
 |      * Indication of the number of times the callback is used. | 
 |      */ | 
 |     IX_ETH_ACC_STATS_INC(ixEthAccDataStats.rxCallbackCounter); | 
 |  | 
 |     do | 
 |     { | 
 | 	/* | 
 | 	 * Indication of the number of times the queue is drained | 
 | 	 */ | 
 | 	IX_ETH_ACC_STATS_INC(ixEthAccDataStats.rxCallbackBurstRead); | 
 |  | 
 | 	/* ensure the last entry of the array contains a zeroed value */ | 
 | 	qEntryPtr = rxQEntry; | 
 | 	qEntryPtr[IX_ETH_ACC_MAX_RX_FRAME_CONSUME_PER_CALLBACK] = 0; | 
 |  | 
 | 	rxQReadStatus = ixQMgrQBurstRead(qId, | 
 | 		 IX_ETH_ACC_MAX_RX_FRAME_CONSUME_PER_CALLBACK, | 
 | 		 qEntryPtr); | 
 |  | 
 | #ifndef NDEBUG | 
 | 	if ((rxQReadStatus != IX_QMGR_Q_UNDERFLOW) | 
 | 	    && (rxQReadStatus != IX_SUCCESS)) | 
 | 	{ | 
 | 	    ixEthAccDataStats.unexpectedError++; | 
 | 	    /*major error*/ | 
 | 	    IX_ETH_ACC_FATAL_LOG( | 
 | 		"ixEthRxFrameQMCallback:Error: %u\n", | 
 | 		(UINT32)rxQReadStatus, 0, 0, 0, 0, 0); | 
 | 	    return; | 
 | 	} | 
 | #endif | 
 |  | 
 | 	/* convert and preload the next entry | 
 | 	 * (the conversion function takes care about null pointers which | 
 | 	 * are used to mark the end of the loop) | 
 | 	 */ | 
 | 	nextQEntry = *qEntryPtr; | 
 | 	nextMbufPtr = ixEthAccEntryFromQConvert(nextQEntry, | 
 | 			  IX_ETHNPE_QM_Q_RXENET_ADDR_MASK); | 
 |  | 
 | 	while(nextQEntry != 0) | 
 | 	{ | 
 | 	    /* get the next entry */ | 
 | 	    qEntry = nextQEntry; | 
 | 	    mbufPtr = nextMbufPtr; | 
 |  | 
 | #ifndef NDEBUG | 
 | 	    if (mbufPtr == NULL) | 
 | 	    { | 
 | 		ixEthAccDataStats.unexpectedError++; | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 		    "ixEthRxFrameQMCallback: Null Mbuf Ptr\n", | 
 | 		    0, 0, 0, 0, 0, 0); | 
 | 		return; | 
 | 	    } | 
 | #endif | 
 |  | 
 | 	    /* convert the next entry | 
 | 	     * (the conversion function takes care about null pointers which | 
 | 	     * are used to mark the end of the loop) | 
 | 	     */ | 
 | 	    nextQEntry = *(++qEntryPtr); | 
 | 	    nextMbufPtr = ixEthAccEntryFromQConvert(nextQEntry, | 
 | 			      IX_ETHNPE_QM_Q_RXENET_ADDR_MASK); | 
 |  | 
 | 	    /* | 
 | 	     * Get Port and Npe ID from message. | 
 | 	     */ | 
 | 	    npeId = ((IX_ETHNPE_QM_Q_RXENET_NPEID_MASK & | 
 | 		      qEntry) >> IX_ETHNPE_QM_Q_FIELD_NPEID_R); | 
 | 	    portId = IX_ETH_ACC_NPE_TO_PORT_ID(npeId); | 
 |  | 
 | 	    /* process frame, check the return code and skip the remaining of | 
 | 	     * the loop if the frame is to be filtered out | 
 | 	     */ | 
 |             if (ixEthRxFrameProcess(portId, mbufPtr)) | 
 |             { | 
 | 	        /* destination portId for this packet */ | 
 | 	        destPortId = IX_ETHACC_NE_DESTPORTID(mbufPtr); | 
 |  | 
 |                 if (destPortId != IX_ETH_DB_UNKNOWN_PORT) | 
 |                 { | 
 |                     destPortId = IX_ETH_DB_NPE_LOGICAL_ID_TO_PORT_ID(destPortId); | 
 |                 } | 
 |  | 
 | 	        /* test if QoS is enabled in ethAcc | 
 | 	        */ | 
 | 	        if (ixEthAccDataInfo.schDiscipline == FIFO_PRIORITY) | 
 | 	        { | 
 | 		    /* check if there is a higher priority queue | 
 | 		    * which may require processing and then process it. | 
 | 		    */ | 
 | 		    if (ixEthAccDataInfo.higherPriorityQueue[qId] < IX_QMGR_MAX_NUM_QUEUES) | 
 | 		    { | 
 | 		        ixEthRxFrameQMCallback(ixEthAccDataInfo.higherPriorityQueue[qId], | 
 | 					    callbackId); | 
 | 		    } | 
 | 	        } | 
 |  | 
 | 	        /* | 
 | 	        * increment priority stats | 
 | 	        */ | 
 | 	        RX_STATS_INC(portId,rxPriority[IX_ETHACC_NE_QOS(mbufPtr)]); | 
 |  | 
 | 	        /* | 
 | 	        * increment callback count stats | 
 | 	        */ | 
 | 	        RX_STATS_INC(portId,rxFrameClientCallback); | 
 |  | 
 | 	        /* | 
 | 	        * Call user level callback. | 
 | 	        */ | 
 | 	        ixEthAccPortData[portId].ixEthAccRxData.rxCallbackFn( | 
 | 		    ixEthAccPortData[portId].ixEthAccRxData.rxCallbackTag, | 
 | 		    mbufPtr, | 
 | 		    destPortId); | 
 |             } | 
 | 	} | 
 |     } while (rxQReadStatus == IX_SUCCESS); | 
 | } | 
 |  | 
 | /** | 
 |  * @fn ixEthRxMultiBufferQMCallback | 
 |  * | 
 |  * @brief receive callback for Frame receive Q from NPE | 
 |  * | 
 |  * Frames are passed as an array to the user | 
 |  * | 
 |  * @param @ref IxQMgrCallback | 
 |  * | 
 |  * @return none | 
 |  * | 
 |  * @internal | 
 |  * | 
 |  * Design note : while processing the entry X, entry X+1 is preloaded | 
 |  * into memory to reduce the number of stall cycles | 
 |  * | 
 |  */ | 
 | void ixEthRxMultiBufferQMCallback(IxQMgrQId qId, IxQMgrCallbackId callbackId) | 
 | { | 
 |     IX_OSAL_MBUF    *mbufPtr; | 
 |     IX_OSAL_MBUF    *nextMbufPtr; | 
 |     UINT32     qEntry; | 
 |     UINT32     nextQEntry; | 
 |     UINT32     *qEntryPtr; | 
 |     UINT32     portId; | 
 |     UINT32     npeId; | 
 |     UINT32     rxQReadStatus; | 
 |     /* | 
 |      * Design note : entries are read in a static buffer, This buffer contains | 
 |      * an extra zeroed entry so the loop will | 
 |      * always terminate on a null entry, whatever the result of Burst read is. | 
 |      */ | 
 |     static UINT32 rxQEntry[IX_ETH_ACC_MAX_RX_FRAME_CONSUME_PER_CALLBACK + 1]; | 
 |     static IX_OSAL_MBUF *rxMbufPortArray[IX_ETH_ACC_NUMBER_OF_PORTS][IX_ETH_ACC_MAX_RX_FRAME_CONSUME_PER_CALLBACK + 1]; | 
 |     IX_OSAL_MBUF **rxMbufPtr[IX_ETH_ACC_NUMBER_OF_PORTS]; | 
 |  | 
 |     for (portId = 0; portId < IX_ETH_ACC_NUMBER_OF_PORTS; portId++) | 
 |     { | 
 | 	rxMbufPtr[portId] = rxMbufPortArray[portId]; | 
 |     } | 
 |  | 
 |     /* | 
 |      * Indication of the number of times the callback is used. | 
 |      */ | 
 |     IX_ETH_ACC_STATS_INC(ixEthAccDataStats.rxCallbackCounter); | 
 |  | 
 |     do | 
 |     { | 
 | 	/* | 
 | 	 * Indication of the number of times the queue is drained | 
 | 	 */ | 
 | 	IX_ETH_ACC_STATS_INC(ixEthAccDataStats.rxCallbackBurstRead); | 
 |  | 
 | 	/* ensure the last entry of the array contains a zeroed value */ | 
 | 	qEntryPtr = rxQEntry; | 
 | 	qEntryPtr[IX_ETH_ACC_MAX_RX_FRAME_CONSUME_PER_CALLBACK] = 0; | 
 |  | 
 | 	rxQReadStatus = ixQMgrQBurstRead(qId, | 
 | 		 IX_ETH_ACC_MAX_RX_FRAME_CONSUME_PER_CALLBACK, | 
 | 		 qEntryPtr); | 
 |  | 
 | #ifndef NDEBUG | 
 | 	if ((rxQReadStatus != IX_QMGR_Q_UNDERFLOW) | 
 | 	    && (rxQReadStatus != IX_SUCCESS)) | 
 | 	{ | 
 | 	    ixEthAccDataStats.unexpectedError++; | 
 | 	    /*major error*/ | 
 | 	    IX_ETH_ACC_FATAL_LOG( | 
 | 		"ixEthRxFrameMultiBufferQMCallback:Error: %u\n", | 
 | 		(UINT32)rxQReadStatus, 0, 0, 0, 0, 0); | 
 | 	    return; | 
 | 	} | 
 | #endif | 
 |  | 
 | 	/* convert and preload the next entry | 
 | 	 * (the conversion function takes care about null pointers which | 
 | 	 * are used to mark the end of the loop) | 
 | 	 */ | 
 | 	nextQEntry = *qEntryPtr; | 
 | 	nextMbufPtr = ixEthAccEntryFromQConvert(nextQEntry, | 
 | 			  IX_ETHNPE_QM_Q_RXENET_ADDR_MASK); | 
 |  | 
 | 	while(nextQEntry != 0) | 
 | 	{ | 
 | 	    /* get the next entry */ | 
 | 	    qEntry = nextQEntry; | 
 | 	    mbufPtr = nextMbufPtr; | 
 |  | 
 | #ifndef NDEBUG | 
 | 	    if (mbufPtr == NULL) | 
 | 	    { | 
 | 		ixEthAccDataStats.unexpectedError++; | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 		    "ixEthRxFrameMultiBufferQMCallback:Error: Null Mbuf Ptr\n", | 
 | 		    0, 0, 0, 0, 0, 0); | 
 | 		return; | 
 | 	    } | 
 | #endif | 
 |  | 
 | 	    /* convert the next entry | 
 | 	     * (the conversion function takes care about null pointers which | 
 | 	     * are used to mark the end of the loop) | 
 | 	     */ | 
 | 	    nextQEntry = *(++qEntryPtr); | 
 | 	    nextMbufPtr = ixEthAccEntryFromQConvert(nextQEntry, | 
 | 			      IX_ETHNPE_QM_Q_RXENET_ADDR_MASK); | 
 |  | 
 | 	    /* | 
 | 	     * Get Port and Npe ID from message. | 
 | 	     */ | 
 | 	    npeId = ((IX_ETHNPE_QM_Q_RXENET_NPEID_MASK & | 
 | 		      qEntry) >> | 
 | 		     IX_ETHNPE_QM_Q_FIELD_NPEID_R); | 
 | 	    portId = IX_ETH_ACC_NPE_TO_PORT_ID(npeId); | 
 |  | 
 | 	    /* skip the remaining of the loop if the frame is | 
 | 	     * to be filtered out | 
 | 	     */ | 
 | 	    if (ixEthRxFrameProcess(portId, mbufPtr)) | 
 | 	    { | 
 | 		/* store a mbuf pointer in an array */ | 
 | 		*rxMbufPtr[portId]++ = mbufPtr; | 
 |  | 
 | 		/* | 
 | 		 * increment priority stats | 
 | 		 */ | 
 | 		RX_STATS_INC(portId,rxPriority[IX_ETHACC_NE_QOS(mbufPtr)]); | 
 | 	    } | 
 |  | 
 | 	    /* test for QoS enabled in ethAcc */ | 
 | 	    if (ixEthAccDataInfo.schDiscipline == FIFO_PRIORITY) | 
 | 	    { | 
 | 		/* check if there is a higher priority queue | 
 | 		 * which may require processing and then process it. | 
 | 		 */ | 
 | 		if (ixEthAccDataInfo.higherPriorityQueue[qId] < IX_QMGR_MAX_NUM_QUEUES) | 
 | 		{ | 
 | 		    ixEthRxMultiBufferQMCallback(ixEthAccDataInfo.higherPriorityQueue[qId], | 
 | 						 callbackId); | 
 | 		} | 
 | 	    } | 
 | 	} | 
 |  | 
 | 	/* check if any of the the arrays contains any entry */ | 
 | 	for (portId = 0; portId < IX_ETH_ACC_NUMBER_OF_PORTS; portId++) | 
 | 	{ | 
 | 	    if (rxMbufPtr[portId] != rxMbufPortArray[portId]) | 
 | 	    { | 
 | 		/* add a last NULL pointer at the end of the | 
 | 		 * array of mbuf pointers | 
 | 		 */ | 
 | 		*rxMbufPtr[portId] = NULL; | 
 |  | 
 | 		/* | 
 | 		 * increment callback count stats | 
 | 		 */ | 
 | 		RX_STATS_INC(portId,rxFrameClientCallback); | 
 |  | 
 | 		/* | 
 | 		 * Call user level callback with an array of | 
 | 		 * buffers (NULL terminated) | 
 | 		 */ | 
 | 		ixEthAccPortData[portId].ixEthAccRxData. | 
 | 		    rxMultiBufferCallbackFn( | 
 | 			    ixEthAccPortData[portId].ixEthAccRxData. | 
 | 			           rxMultiBufferCallbackTag, | 
 | 			    rxMbufPortArray[portId]); | 
 |  | 
 | 		/* reset the buffer pointer to the beginning of | 
 | 		 * the array | 
 | 		 */ | 
 | 		rxMbufPtr[portId] = rxMbufPortArray[portId]; | 
 | 	    } | 
 | 	} | 
 |  | 
 |     } while (rxQReadStatus == IX_SUCCESS); | 
 | } | 
 |  | 
 |  | 
 | /** | 
 |  * @brief  rxFree low event handler | 
 |  * | 
 |  */ | 
 | void ixEthRxFreeQMCallback(IxQMgrQId qId, IxQMgrCallbackId callbackId) | 
 | { | 
 |     IxEthAccPortId	portId = (IxEthAccPortId) callbackId; | 
 |     int		        lockVal; | 
 |     UINT32		maxQWritesToPerform = IX_ETH_ACC_MAX_RX_FREE_BUFFERS_LOAD; | 
 |     IX_STATUS	        qStatus = IX_SUCCESS; | 
 |  | 
 |     /* | 
 |      * We have reached a low threshold on one of the Rx Free Qs | 
 |      */ | 
 |  | 
 |     /*note that due to the fact that we are working off an Empty threshold, this callback | 
 |       need only write a single entry to the Rx Free queue in order to re-arm the notification | 
 |     */ | 
 |  | 
 |     RX_STATS_INC(portId,rxFreeLowCallback); | 
 |  | 
 |     /* | 
 |      * Get buffers from approprite S/W Rx freeBufferList Q. | 
 |      */ | 
 |  | 
 | #ifndef NDEBUG | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	ixEthAccDataStats.unexpectedError++; | 
 | 	IX_ETH_ACC_FATAL_LOG( | 
 | 	    "ixEthRxFreeQMCallback:Error: Invalid Port 0x%08X\n", | 
 | 	    portId, 0, 0, 0, 0, 0); | 
 | 	return; | 
 |     } | 
 | #endif | 
 |     IX_ETH_ACC_DATA_PLANE_LOCK(lockVal); | 
 |     if (IX_ETH_ACC_DATAPLANE_IS_Q_EMPTY(ixEthAccPortData[portId]. | 
 | 					ixEthAccRxData.freeBufferList)) | 
 |     { | 
 | 	/* | 
 | 	 * Turn off Q callback notification for Q in Question. | 
 | 	 */ | 
 | 	qStatus = ixQMgrNotificationDisable( | 
 | 	    IX_ETH_ACC_PORT_TO_RX_FREE_Q_ID(portId)); | 
 |  | 
 |  | 
 | 	IX_ETH_ACC_DATA_PLANE_UNLOCK(lockVal); | 
 |  | 
 | 	if (qStatus != IX_SUCCESS) | 
 | 	{ | 
 | 	    RX_INC(portId,rxUnexpectedError); | 
 | 	    IX_ETH_ACC_FATAL_LOG( | 
 | 		"ixEthRxFreeQMCallback:Error: unexpected QM status 0x%08X\n", | 
 | 		qStatus, 0, 0, 0, 0, 0); | 
 | 	    return; | 
 | 	} | 
 |     } | 
 |     else | 
 |     { | 
 | 	IX_ETH_ACC_DATA_PLANE_UNLOCK(lockVal); | 
 | 	/* | 
 | 	 * Load the H/W Q with buffers from the s/w Q. | 
 | 	 */ | 
 |  | 
 | 	do | 
 | 	{ | 
 | 	    /* | 
 | 	     * Consume Q entries. - Note Q contains Physical addresss, | 
 | 	     * and have already been flushed to memory, | 
 | 	     * And endianess converted if required. | 
 | 	     */ | 
 | 	    if (ixEthAccRxFreeFromSwQ(portId) != IX_SUCCESS) | 
 | 	    { | 
 | 		/* | 
 | 		 * No more entries in s/w Q. | 
 | 		 * Turn off Q callback indication | 
 | 		 */ | 
 |  | 
 | 		IX_ETH_ACC_DATA_PLANE_LOCK(lockVal); | 
 | 		if (IX_ETH_ACC_DATAPLANE_IS_Q_EMPTY(ixEthAccPortData[portId]. | 
 | 		    ixEthAccRxData.freeBufferList)) | 
 | 		{ | 
 | 		    qStatus = ixQMgrNotificationDisable( | 
 | 			IX_ETH_ACC_PORT_TO_RX_FREE_Q_ID(portId)); | 
 | 		} | 
 | 		IX_ETH_ACC_DATA_PLANE_UNLOCK(lockVal); | 
 | 		break; | 
 | 	    } | 
 | 	} | 
 | 	while (--maxQWritesToPerform); | 
 |     } | 
 | } | 
 | /** | 
 |  * @fn Tx queue low event handler | 
 |  * | 
 |  */ | 
 | void | 
 | ixEthTxFrameQMCallback(IxQMgrQId qId, IxQMgrCallbackId callbackId) | 
 | { | 
 |     IxEthAccPortId portId = (IxEthAccPortId) callbackId; | 
 |     int		   lockVal; | 
 |     UINT32	   maxQWritesToPerform = IX_ETH_ACC_MAX_TX_FRAME_TX_CONSUME_PER_CALLBACK; | 
 |     IX_STATUS	   qStatus = IX_SUCCESS; | 
 |     IxEthAccTxPriority highestPriority; | 
 |  | 
 |  | 
 |     /* | 
 |      * We have reached a low threshold on the Tx Q, and are being asked to | 
 |      * supply a buffer for transmission from our S/W TX queues | 
 |      */ | 
 |     TX_STATS_INC(portId,txLowThreshCallback); | 
 |  | 
 |     /* | 
 |      * Get buffers from approprite Q. | 
 |      */ | 
 |  | 
 | #ifndef NDEBUG | 
 |     if (!IX_ETH_ACC_IS_PORT_VALID(portId)) | 
 |     { | 
 | 	ixEthAccDataStats.unexpectedError++; | 
 | 	IX_ETH_ACC_FATAL_LOG( | 
 | 	    "ixEthTxFrameQMCallback:Error: Invalid Port 0x%08X\n", | 
 | 	    portId, 0, 0, 0, 0, 0); | 
 | 	return; | 
 |     } | 
 | #endif | 
 |  | 
 |     do | 
 |     { | 
 | 	/* | 
 | 	 * Consume Q entries. - Note Q contains Physical addresss, | 
 | 	 * and have already been flushed to memory, | 
 | 	 * and endianess already sone if required. | 
 | 	 */ | 
 |  | 
 | 	IX_ETH_ACC_DATA_PLANE_LOCK(lockVal); | 
 |  | 
 | 	if(ixEthAccTxSwQHighestPriorityGet(portId, &highestPriority) == | 
 | 	   IX_ETH_ACC_FAIL) | 
 | 	{ | 
 | 	    /* | 
 | 	     * No more entries in s/w Q. | 
 | 	     * Turn off Q callback indication | 
 | 	     */ | 
 | 	    qStatus = ixQMgrNotificationDisable( | 
 | 		IX_ETH_ACC_PORT_TO_TX_Q_ID(portId)); | 
 |  | 
 | 	    IX_ETH_ACC_DATA_PLANE_UNLOCK(lockVal); | 
 |  | 
 | 	    if (qStatus != IX_SUCCESS) | 
 | 	    { | 
 | 		ixEthAccDataStats.unexpectedError++; | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 		    "ixEthTxFrameQMCallback:Error: unexpected QM status 0x%08X\n", | 
 | 		    qStatus, 0, 0, 0, 0, 0); | 
 | 	    } | 
 |  | 
 | 	    return; | 
 | 	} | 
 | 	else | 
 | 	{ | 
 | 	    IX_ETH_ACC_DATA_PLANE_UNLOCK(lockVal); | 
 | 	    if (ixEthAccTxFromSwQ(portId,highestPriority)!=IX_SUCCESS) | 
 | 	    { | 
 |                 /* nothing left in the sw queue or the hw queues are | 
 |                 * full. There is no point to continue to drain the | 
 |                 * sw queues | 
 |                 */ | 
 | 		return; | 
 | 	    } | 
 | 	} | 
 |     } | 
 |     while (--maxQWritesToPerform); | 
 | } | 
 |  | 
 | /** | 
 |  * @brief TxDone event handler | 
 |  * | 
 |  * Design note : while processing the entry X, entry X+1 is preloaded | 
 |  * into memory to reduce the number of stall cycles | 
 |  * | 
 |  */ | 
 |  | 
 | void | 
 | ixEthTxFrameDoneQMCallback(IxQMgrQId qId, IxQMgrCallbackId callbackId) | 
 | { | 
 |     IX_OSAL_MBUF    *mbufPtr; | 
 |     UINT32     qEntry; | 
 |     UINT32     *qEntryPtr; | 
 |     UINT32     txDoneQReadStatus; | 
 |     UINT32     portId; | 
 |     UINT32     npeId; | 
 |  | 
 |     /* | 
 |      * Design note : entries are read in a static buffer, This buffer contains | 
 |      * an extra entyry (which is zeroed by the compiler), so the loop will | 
 |      * always terminate on a null entry, whatever the result of Burst read is. | 
 |      */ | 
 |     static UINT32 txDoneQEntry[IX_ETH_ACC_MAX_TX_FRAME_DONE_CONSUME_PER_CALLBACK + 1]; | 
 |  | 
 |     /* | 
 |      * Indication that Tx frames have been transmitted from the NPE. | 
 |      */ | 
 |  | 
 |     IX_ETH_ACC_STATS_INC(ixEthAccDataStats.txDoneCallbackCounter); | 
 |  | 
 |     do{ | 
 | 	qEntryPtr = txDoneQEntry; | 
 | 	txDoneQReadStatus = ixQMgrQBurstRead(IX_ETH_ACC_TX_FRAME_DONE_ETH_Q, | 
 | 		     IX_ETH_ACC_MAX_TX_FRAME_DONE_CONSUME_PER_CALLBACK, | 
 | 		     qEntryPtr); | 
 |  | 
 | #ifndef NDEBUG | 
 | 	if (txDoneQReadStatus != IX_QMGR_Q_UNDERFLOW | 
 | 	    && (txDoneQReadStatus != IX_SUCCESS)) | 
 | 	{ | 
 | 	    /*major error*/ | 
 | 	    ixEthAccDataStats.unexpectedError++; | 
 | 	    IX_ETH_ACC_FATAL_LOG( | 
 | 		"ixEthTxFrameDoneQMCallback:Error: %u\n", | 
 | 		(UINT32)txDoneQReadStatus, 0, 0, 0, 0, 0); | 
 | 	    return; | 
 | 	} | 
 | #endif | 
 |  | 
 | 	qEntry = *qEntryPtr; | 
 |  | 
 | 	while(qEntry != 0) | 
 | 	{ | 
 | 	    mbufPtr = ixEthAccEntryFromQConvert(qEntry, | 
 | 		      IX_ETHNPE_QM_Q_TXENET_ADDR_MASK); | 
 |  | 
 | #ifndef NDEBUG | 
 | 	    if (mbufPtr == NULL) | 
 | 	    { | 
 | 		ixEthAccDataStats.unexpectedError++; | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 		    "ixEthTxFrameDoneQMCallback:Error: Null Mbuf Ptr\n", | 
 | 		    0, 0, 0, 0, 0, 0); | 
 | 		return; | 
 | 	    } | 
 | #endif | 
 |  | 
 | 	    /* endianness conversions and stats updates */ | 
 | 	    ixEthAccMbufFromTxQ(mbufPtr); | 
 |  | 
 | 	    /* | 
 | 	     * Get NPE id from message, then convert to portId. | 
 | 	     */ | 
 | 	    npeId = ((IX_ETHNPE_QM_Q_TXENETDONE_NPEID_MASK & | 
 | 		       qEntry) >> | 
 | 		      IX_ETHNPE_QM_Q_FIELD_NPEID_R); | 
 | 	    portId = IX_ETH_ACC_NPE_TO_PORT_ID(npeId); | 
 |  | 
 | #ifndef NDEBUG | 
 | 	    /* Prudent to at least check the port is within range */ | 
 | 	    if (portId >= IX_ETH_ACC_NUMBER_OF_PORTS) | 
 | 	    { | 
 | 		ixEthAccDataStats.unexpectedError++; | 
 | 		IX_ETH_ACC_FATAL_LOG( | 
 | 		    "ixEthTxFrameDoneQMCallback: Illegal port: %u\n", | 
 | 		    (UINT32)portId, 0, 0, 0, 0, 0); | 
 | 		return; | 
 | 	    } | 
 | #endif | 
 |  | 
 | 	    TX_STATS_INC(portId,txDoneClientCallback); | 
 |  | 
 | 	    /* | 
 | 	     * Call user level callback. | 
 | 	     */ | 
 | 	    ixEthAccPortData[portId].ixEthAccTxData.txBufferDoneCallbackFn( | 
 | 		ixEthAccPortData[portId].ixEthAccTxData.txCallbackTag, | 
 | 		mbufPtr); | 
 |  | 
 | 	    /* move to next queue entry */ | 
 | 	    qEntry = *(++qEntryPtr); | 
 |  | 
 | 	} | 
 |     } while( txDoneQReadStatus == IX_SUCCESS ); | 
 | } | 
 |  | 
 | IX_ETH_ACC_PUBLIC | 
 | void ixEthAccDataPlaneShow(void) | 
 | { | 
 |     UINT32 numTx0Entries; | 
 |     UINT32 numTx1Entries; | 
 |     UINT32 numTxDoneEntries; | 
 |     UINT32 numRxEntries; | 
 |     UINT32 numRxFree0Entries; | 
 |     UINT32 numRxFree1Entries; | 
 |     UINT32 portId; | 
 | #ifdef __ixp46X | 
 |     UINT32 numTx2Entries; | 
 |     UINT32 numRxFree2Entries; | 
 | #endif | 
 | #ifndef NDEBUG | 
 |     UINT32 priority; | 
 |     UINT32 numBuffersInRx=0; | 
 |     UINT32 numBuffersInTx=0; | 
 |     UINT32 numBuffersInSwQ=0; | 
 |     UINT32 totalBuffers=0; | 
 |     UINT32 rxFreeCallbackCounter = 0; | 
 |     UINT32 txCallbackCounter = 0; | 
 | #endif | 
 |     UINT32 key; | 
 |  | 
 |     /* snapshot of stats */ | 
 |     IxEthAccTxDataStats tx[IX_ETH_ACC_NUMBER_OF_PORTS]; | 
 |     IxEthAccRxDataStats rx[IX_ETH_ACC_NUMBER_OF_PORTS]; | 
 |     IxEthAccDataPlaneStats stats; | 
 |  | 
 |     if (!IX_ETH_ACC_IS_SERVICE_INITIALIZED()) | 
 |     { | 
 | 	return; | 
 |     } | 
 |  | 
 |     /* get a reliable snapshot */ | 
 |     key = ixOsalIrqLock(); | 
 |  | 
 |     numTx0Entries = 0; | 
 |     ixQMgrQNumEntriesGet(IX_ETH_ACC_TX_FRAME_ENET0_Q, &numTx0Entries); | 
 |     numTx1Entries = 0; | 
 |     ixQMgrQNumEntriesGet(IX_ETH_ACC_TX_FRAME_ENET1_Q, &numTx1Entries); | 
 |     numTxDoneEntries = 0; | 
 |     ixQMgrQNumEntriesGet( IX_ETH_ACC_TX_FRAME_DONE_ETH_Q, &numTxDoneEntries); | 
 |     numRxEntries = 0; | 
 |     ixEthAccQMgrRxQEntryGet(&numRxEntries); | 
 |     numRxFree0Entries = 0; | 
 |     ixQMgrQNumEntriesGet(IX_ETH_ACC_RX_FREE_BUFF_ENET0_Q, &numRxFree0Entries); | 
 |     numRxFree1Entries = 0; | 
 |     ixQMgrQNumEntriesGet(IX_ETH_ACC_RX_FREE_BUFF_ENET1_Q, &numRxFree1Entries); | 
 |  | 
 | #ifdef __ixp46X | 
 |     numTx2Entries = 0; | 
 |     ixQMgrQNumEntriesGet(IX_ETH_ACC_TX_FRAME_ENET2_Q, &numTx2Entries); | 
 |     numRxFree2Entries = 0; | 
 |     ixQMgrQNumEntriesGet(IX_ETH_ACC_RX_FREE_BUFF_ENET2_Q, &numRxFree2Entries); | 
 | #endif | 
 |  | 
 |     for(portId=IX_ETH_PORT_1; portId < IX_ETH_ACC_NUMBER_OF_PORTS; portId++) | 
 |     { | 
 | 	memcpy(&tx[portId], | 
 | 	       &ixEthAccPortData[portId].ixEthAccTxData.stats, | 
 | 	       sizeof(tx[portId])); | 
 | 	memcpy(&rx[portId], | 
 | 	       &ixEthAccPortData[portId].ixEthAccRxData.stats, | 
 | 	       sizeof(rx[portId])); | 
 |     } | 
 |     memcpy(&stats, &ixEthAccDataStats, sizeof(stats)); | 
 |  | 
 |     ixOsalIrqUnlock(key); | 
 |  | 
 | #ifdef NDEBUG | 
 |     printf("Detailed statistics collection not supported in this load\n"); | 
 | #endif | 
 |  | 
 |     /* print snapshot */ | 
 |     for(portId=0; portId < IX_ETH_ACC_NUMBER_OF_PORTS; portId++) | 
 |     { | 
 |         /* If not IXP42X A0 stepping, proceed to check for existence of coprocessors */ | 
 |         if ((IX_FEATURE_CTRL_SILICON_TYPE_A0 != | 
 | 	     (ixFeatureCtrlProductIdRead() & IX_FEATURE_CTRL_SILICON_STEPPING_MASK)) | 
 | 	    || (IX_FEATURE_CTRL_DEVICE_TYPE_IXP42X != ixFeatureCtrlDeviceRead ())) | 
 |         { | 
 |                 if ((IX_ETH_PORT_1 == portId) && | 
 |                     (ixFeatureCtrlComponentCheck(IX_FEATURECTRL_ETH0) == | 
 |                      IX_FEATURE_CTRL_COMPONENT_DISABLED)) | 
 |                 { | 
 |                    continue ; | 
 |                 } | 
 |                 if ((IX_ETH_PORT_2 == portId) && | 
 |                     (ixFeatureCtrlComponentCheck(IX_FEATURECTRL_ETH1) == | 
 |                      IX_FEATURE_CTRL_COMPONENT_DISABLED)) | 
 |                 { | 
 |                     continue ; | 
 |                 } | 
 |                 if ((IX_ETH_PORT_3 == portId) && | 
 |                     (ixFeatureCtrlComponentCheck(IX_FEATURECTRL_NPEA_ETH) == | 
 |                      IX_FEATURE_CTRL_COMPONENT_DISABLED)) | 
 |                 { | 
 |                     continue ; | 
 |                 } | 
 |         } | 
 |  | 
 | 	printf("PORT %u --------------------------------\n", | 
 | 	       portId); | 
 | #ifndef NDEBUG | 
 | 	printf("Tx Done Frames                : %u\n", | 
 | 	       tx[portId].txDoneClientCallback + | 
 | 	       tx[portId].txDoneSwQDuringDisable + | 
 | 	       tx[portId].txDoneDuringDisable); | 
 | 	printf("Tx Frames                     : %u\n", | 
 | 	       tx[portId].txQOK + tx[portId].txQDelayed); | 
 | 	printf("Tx H/W Q Added OK             : %u\n", | 
 | 	       tx[portId].txQOK); | 
 | 	printf("Tx H/W Q Delayed              : %u\n", | 
 | 	       tx[portId].txQDelayed); | 
 | 	printf("Tx From S/W Q Added OK        : %u\n", | 
 | 	       tx[portId].txFromSwQOK); | 
 | 	printf("Tx From S/W Q Delayed         : %u\n", | 
 | 	       tx[portId].txFromSwQDelayed); | 
 | 	printf("Tx Overflow                   : %u\n", | 
 | 	       tx[portId].txOverflow); | 
 | 	printf("Tx Mutual Lock                : %u\n", | 
 | 	       tx[portId].txLock); | 
 | 	printf("Tx Late Ntf Enabled           : %u\n", | 
 | 	       tx[portId].txLateNotificationEnabled); | 
 | 	printf("Tx Low Thresh CB              : %u\n", | 
 | 	       tx[portId].txLowThreshCallback); | 
 | 	printf("Tx Done from H/W Q (Disable)  : %u\n", | 
 | 	       tx[portId].txDoneDuringDisable); | 
 | 	printf("Tx Done from S/W Q (Disable)  : %u\n", | 
 | 	       tx[portId].txDoneSwQDuringDisable); | 
 | 	for (priority = IX_ETH_ACC_TX_PRIORITY_0; | 
 | 	     priority <= IX_ETH_ACC_TX_PRIORITY_7; | 
 | 	     priority++) | 
 | 	{ | 
 | 	    if (tx[portId].txPriority[priority]) | 
 | 	    { | 
 | 		printf("Tx Priority %u                 : %u\n", | 
 | 		       priority, | 
 | 		       tx[portId].txPriority[priority]); | 
 | 	    } | 
 | 	} | 
 | #endif | 
 | 	printf("Tx unexpected errors          : %u (should be 0)\n", | 
 | 	       tx[portId].txUnexpectedError); | 
 |  | 
 | #ifndef NDEBUG | 
 | 	printf("Rx Frames                     : %u\n", | 
 | 	       rx[portId].rxFrameClientCallback + | 
 | 	       rx[portId].rxSwQDuringDisable+ | 
 | 	       rx[portId].rxDuringDisable); | 
 | 	printf("Rx Free Replenish             : %u\n", | 
 | 	       rx[portId].rxFreeRepOK + rx[portId].rxFreeRepDelayed); | 
 | 	printf("Rx Free H/W Q Added OK        : %u\n", | 
 | 	       rx[portId].rxFreeRepOK); | 
 | 	printf("Rx Free H/W Q Delayed         : %u\n", | 
 | 	       rx[portId].rxFreeRepDelayed); | 
 | 	printf("Rx Free From S/W Q Added OK   : %u\n", | 
 | 	       rx[portId].rxFreeRepFromSwQOK); | 
 | 	printf("Rx Free From S/W Q Delayed    : %u\n", | 
 | 	       rx[portId].rxFreeRepFromSwQDelayed); | 
 | 	printf("Rx Free Overflow              : %u\n", | 
 | 	       rx[portId].rxFreeOverflow); | 
 | 	printf("Rx Free Mutual Lock           : %u\n", | 
 | 	       rx[portId].rxFreeLock); | 
 | 	printf("Rx Free Late Ntf Enabled      : %u\n", | 
 | 	       rx[portId].rxFreeLateNotificationEnabled); | 
 | 	printf("Rx Free Low CB                : %u\n", | 
 | 	       rx[portId].rxFreeLowCallback); | 
 | 	printf("Rx From H/W Q (Disable)       : %u\n", | 
 | 	       rx[portId].rxDuringDisable); | 
 | 	printf("Rx From S/W Q (Disable)       : %u\n", | 
 | 	       rx[portId].rxSwQDuringDisable); | 
 | 	printf("Rx unlearned Mac Address      : %u\n", | 
 | 	       rx[portId].rxUnlearnedMacAddress); | 
 |         printf("Rx Filtered (Rx => RxFree)    : %u\n", | 
 |             rx[portId].rxFiltered); | 
 |  | 
 | 	for (priority = IX_ETH_ACC_TX_PRIORITY_0; | 
 | 	     priority <= IX_ETH_ACC_TX_PRIORITY_7; | 
 | 	     priority++) | 
 | 	{ | 
 | 	    if (rx[portId].rxPriority[priority]) | 
 | 	    { | 
 | 		printf("Rx Priority %u                 : %u\n", | 
 | 		       priority, | 
 | 		       rx[portId].rxPriority[priority]); | 
 | 	    } | 
 | 	} | 
 | #endif | 
 | 	printf("Rx unexpected errors          : %u (should be 0)\n", | 
 | 	       rx[portId].rxUnexpectedError); | 
 |  | 
 | #ifndef NDEBUG | 
 | 	numBuffersInTx = tx[portId].txQOK + | 
 | 	    tx[portId].txQDelayed - | 
 | 	    tx[portId].txDoneClientCallback - | 
 | 	    tx[portId].txDoneSwQDuringDisable - | 
 | 	    tx[portId].txDoneDuringDisable; | 
 |  | 
 | 	printf("# Tx Buffers currently for transmission : %u\n", | 
 | 	       numBuffersInTx); | 
 |  | 
 | 	numBuffersInRx = rx[portId].rxFreeRepOK + | 
 | 	    rx[portId].rxFreeRepDelayed - | 
 | 	    rx[portId].rxFrameClientCallback - | 
 | 	    rx[portId].rxSwQDuringDisable - | 
 | 	    rx[portId].rxDuringDisable; | 
 |  | 
 | 	printf("# Rx Buffers currently for reception    : %u\n", | 
 | 	       numBuffersInRx); | 
 |  | 
 | 	totalBuffers += numBuffersInRx + numBuffersInTx; | 
 | #endif | 
 |     } | 
 |  | 
 |     printf("---------------------------------------\n"); | 
 |  | 
 | #ifndef NDEBUG | 
 |     printf("\n"); | 
 |     printf("Mbufs :\n"); | 
 |     printf("Tx Unchained mbufs            : %u\n", | 
 | 	   stats.unchainedTxMBufs); | 
 |     printf("Tx Chained bufs               : %u\n", | 
 | 	   stats.chainedTxMBufs); | 
 |     printf("TxDone Unchained mbufs        : %u\n", | 
 | 	   stats.unchainedTxDoneMBufs); | 
 |     printf("TxDone Chained bufs           : %u\n", | 
 | 	   stats.chainedTxDoneMBufs); | 
 |     printf("RxFree Unchained mbufs        : %u\n", | 
 | 	   stats.unchainedRxFreeMBufs); | 
 |     printf("RxFree Chained bufs           : %u\n", | 
 | 	   stats.chainedRxFreeMBufs); | 
 |     printf("Rx Unchained mbufs            : %u\n", | 
 | 	   stats.unchainedRxMBufs); | 
 |     printf("Rx Chained bufs               : %u\n", | 
 | 	   stats.chainedRxMBufs); | 
 |  | 
 |     printf("\n"); | 
 |     printf("Software queue usage :\n"); | 
 |     printf("Buffers added to S/W Q        : %u\n", | 
 | 	   stats.addToSwQ); | 
 |     printf("Buffers removed from S/W Q    : %u\n", | 
 | 	   stats.removeFromSwQ); | 
 |  | 
 |     printf("\n"); | 
 |     printf("Hardware queues callbacks :\n"); | 
 |  | 
 |     for(portId=0; portId < IX_ETH_ACC_NUMBER_OF_PORTS; portId++) | 
 |     { | 
 | 	rxFreeCallbackCounter += rx[portId].rxFreeLowCallback; | 
 | 	txCallbackCounter += tx[portId].txLowThreshCallback; | 
 |     } | 
 |     printf("Tx Done QM Callback invoked   : %u\n", | 
 | 	   stats.txDoneCallbackCounter); | 
 |     printf("Tx QM Callback invoked        : %u\n", | 
 | 	   txCallbackCounter); | 
 |     printf("Rx QM Callback invoked        : %u\n", | 
 | 	   stats.rxCallbackCounter); | 
 |     printf("Rx QM Callback burst read     : %u\n", | 
 | 	   stats.rxCallbackBurstRead); | 
 |     printf("Rx Free QM Callback invoked   : %u\n", | 
 | 	   rxFreeCallbackCounter); | 
 | #endif | 
 |     printf("Unexpected errors in CB       : %u (should be 0)\n", | 
 | 	   stats.unexpectedError); | 
 |     printf("\n"); | 
 |  | 
 |     printf("Hardware queues levels :\n"); | 
 |     printf("Transmit Port 1 Q             : %u \n",numTx0Entries); | 
 |     printf("Transmit Port 2 Q             : %u \n",numTx1Entries); | 
 | #ifdef __ixp46X | 
 |     printf("Transmit Port 3 Q             : %u \n",numTx2Entries); | 
 | #endif | 
 |     printf("Transmit Done Q               : %u \n",numTxDoneEntries); | 
 |     printf("Receive Q                     : %u \n",numRxEntries); | 
 |     printf("Receive Free Port 1 Q         : %u \n",numRxFree0Entries); | 
 |     printf("Receive Free Port 2 Q         : %u \n",numRxFree1Entries); | 
 | #ifdef __ixp46X | 
 |     printf("Receive Free Port 3 Q         : %u \n",numRxFree2Entries); | 
 | #endif | 
 |  | 
 | #ifndef NDEBUG | 
 |     printf("\n"); | 
 |     printf("# Total Buffers accounted for : %u\n", | 
 | 	   totalBuffers); | 
 |  | 
 |     numBuffersInSwQ = ixEthAccDataStats.addToSwQ - | 
 | 	ixEthAccDataStats.removeFromSwQ; | 
 |  | 
 |     printf("    Buffers in S/W Qs         : %u\n", | 
 | 	   numBuffersInSwQ); | 
 |     printf("    Buffers in H/W Qs or NPEs : %u\n", | 
 | 	   totalBuffers - numBuffersInSwQ); | 
 | #endif | 
 |  | 
 |     printf("Rx QoS Discipline             : %s\n", | 
 | 	   (ixEthAccDataInfo.schDiscipline == | 
 | 	    FIFO_PRIORITY ) ? "Enabled" : "Disabled"); | 
 |  | 
 |     for(portId=0; portId < IX_ETH_ACC_NUMBER_OF_PORTS; portId++) | 
 |     { | 
 | 	printf("Tx QoS Discipline port %u      : %s\n", | 
 | 	       portId, | 
 | 	       (ixEthAccPortData[portId].ixEthAccTxData.schDiscipline == | 
 | 		FIFO_PRIORITY ) ? "Enabled" : "Disabled"); | 
 |     } | 
 |     printf("\n"); | 
 | } | 
 |  | 
 |  | 
 |  | 
 |  | 
 |  |