/* * FreeRTOS+TCP * Copyright (C) 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. * * SPDX-License-Identifier: MIT * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * http://aws.amazon.com/freertos * http://www.FreeRTOS.org */ /* Standard includes. */ #include #include #include /* FreeRTOS includes. */ #include "FreeRTOS.h" #include "task.h" #include "queue.h" #include "semphr.h" /* FreeRTOS+TCP includes. */ #include "FreeRTOS_IP.h" #include "FreeRTOS_Sockets.h" #include "FreeRTOS_IP_Private.h" #include "NetworkBufferManagement.h" #include "NetworkInterface.h" /* LPCOpen includes. */ #include "chip.h" #include "lpc_phy.h" /* The size of the stack allocated to the task that handles Rx packets. */ #define nwRX_TASK_STACK_SIZE 140 #if defined( PHY_LS_HIGH_CHECK_TIME_MS ) || defined( PHY_LS_LOW_CHECK_TIME_MS ) #error please use the new defines with 'ipconfig' prefix #endif #ifndef ipconfigPHY_LS_HIGH_CHECK_TIME_MS /* Check if the LinkStatus in the PHY is still high after 15 seconds of not * receiving packets. */ #define ipconfigPHY_LS_HIGH_CHECK_TIME_MS 15000U #endif #ifndef ipconfigPHY_LS_LOW_CHECK_TIME_MS /* Check if the LinkStatus in the PHY is still low every second. */ #define ipconfigPHY_LS_LOW_CHECK_TIME_MS 1000U #endif #ifndef configUSE_RMII #define configUSE_RMII 1 #endif #ifndef configNUM_RX_DESCRIPTORS #error please define configNUM_RX_DESCRIPTORS in your FreeRTOSIPConfig.h #endif #ifndef configNUM_TX_DESCRIPTORS #error please define configNUM_TX_DESCRIPTORS in your FreeRTOSIPConfig.h #endif #ifndef NETWORK_IRQHandler #error NETWORK_IRQHandler must be defined to the name of the function that is installed in the interrupt vector table to handle Ethernet interrupts. #endif #if !defined( MAC_FF_HMC ) /* Hash for multicast. */ #define MAC_FF_HMC ( 1UL << 2UL ) #endif #ifndef iptraceEMAC_TASK_STARTING #define iptraceEMAC_TASK_STARTING() do {} while( ipFALSE_BOOL ) #endif /* Define the bits of .STATUS that indicate a reception error. */ #define nwRX_STATUS_ERROR_BITS \ ( RDES_CE /* CRC Error */ | \ RDES_RE /* Receive Error */ | \ RDES_DE /* Descriptor Error */ | \ RDES_RWT /* Receive Watchdog Timeout */ | \ RDES_LC /* Late Collision */ | \ RDES_OE /* Overflow Error */ | \ RDES_SAF /* Source Address Filter Fail */ | \ RDES_AFM /* Destination Address Filter Fail */ | \ RDES_LE /* Length Error */ ) /* Define the EMAC status bits that should trigger an interrupt. */ #define nwDMA_INTERRUPT_MASK \ ( DMA_IE_TIE /* Transmit interrupt enable */ | \ DMA_IE_TSE /* Transmit stopped enable */ | \ DMA_IE_OVE /* Overflow interrupt enable */ | \ DMA_IE_RIE /* Receive interrupt enable */ | \ DMA_IE_NIE /* Normal interrupt summary enable */ | \ DMA_IE_AIE /* Abnormal interrupt summary enable */ | \ DMA_IE_RUE /* Receive buffer unavailable enable */ | \ DMA_IE_UNE /* Underflow interrupt enable. */ | \ DMA_IE_TJE /* Transmit jabber timeout enable */ | \ DMA_IE_RSE /* Received stopped enable */ | \ DMA_IE_RWE /* Receive watchdog timeout enable */ | \ DMA_IE_FBE ) /* Fatal bus error enable */ /* Interrupt events to process. Currently only the RX/TX events are processed * although code for other events is included to allow for possible future * expansion. */ #define EMAC_IF_RX_EVENT 1UL #define EMAC_IF_TX_EVENT 2UL #define EMAC_IF_ERR_EVENT 4UL #define EMAC_IF_ALL_EVENT ( EMAC_IF_RX_EVENT | EMAC_IF_TX_EVENT | EMAC_IF_ERR_EVENT ) /* If ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES is set to 1, then the Ethernet * driver will filter incoming packets and only pass the stack those packets it * considers need processing. */ #if ( ipconfigETHERNET_DRIVER_FILTERS_FRAME_TYPES == 0 ) #define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer ) eProcessBuffer #else #define ipCONSIDER_FRAME_FOR_PROCESSING( pucEthernetBuffer ) eConsiderFrameForProcessing( ( pucEthernetBuffer ) ) #endif #if ( ipconfigZERO_COPY_RX_DRIVER == 0 ) || ( ipconfigZERO_COPY_TX_DRIVER == 0 ) #if ( ipconfigPORT_SUPPRESS_WARNING == 0 ) #warning It is adviced to enable both macros #endif #endif #ifndef configPLACE_IN_SECTION_RAM #define configPLACE_IN_SECTION_RAM /* #define configPLACE_IN_SECTION_RAM __attribute__ ((section(".ramfunc"))) */ #endif /*-----------------------------------------------------------*/ /* * Delay function passed into the library. The implementation uses FreeRTOS * calls so the scheduler must be started before the driver can be used. */ static void prvDelay( uint32_t ulMilliSeconds ); /* * Initialises the Tx and Rx descriptors respectively. */ static void prvSetupTxDescriptors( void ); static void prvSetupRxDescriptors( void ); /* * A task that processes received frames. */ static void prvEMACHandlerTask( void * pvParameters ); /* * Sets up the MAC with the results of an auto-negotiation. */ static BaseType_t prvSetLinkSpeed( void ); /* * Generates a CRC for a MAC address that is then used to generate a hash index. */ static uint32_t prvGenerateCRC32( const uint8_t * ucAddress ); /* * Generates a hash index when setting a filter to permit a MAC address. */ static uint32_t prvGetHashIndex( const uint8_t * ucAddress ); /* * Update the hash table to allow a MAC address. */ static void prvAddMACAddress( const uint8_t * ucMacAddress ); /* * Sometimes the DMA will report received data as being longer than the actual * received from length. This function checks the reported length and corrects * if if necessary. */ static void prvRemoveTrailingBytes( NetworkBufferDescriptor_t * pxDescriptor ); /*-----------------------------------------------------------*/ /* Bit map of outstanding ETH interrupt events for processing. Currently only * the Rx and Tx interrupt is handled, although code is included for other events * to enable future expansion. */ static volatile uint32_t ulISREvents; /* A copy of PHY register 1: 'PHY_REG_01_BMSR' */ static uint32_t ulPHYLinkStatus = 0; /* Tx descriptors and index. */ static ENET_ENHTXDESC_T xDMATxDescriptors[ configNUM_TX_DESCRIPTORS ]; /* ulNextFreeTxDescriptor is declared volatile, because it is accessed from * to different tasks. */ static volatile uint32_t ulNextFreeTxDescriptor; static uint32_t ulTxDescriptorToClear; /* Rx descriptors and index. */ static ENET_ENHRXDESC_T xDMARxDescriptors[ configNUM_RX_DESCRIPTORS ]; static uint32_t ulNextRxDescriptorToProcess; /* The handle of the task that processes Rx packets. The handle is required so * the task can be notified when new packets arrive. */ static TaskHandle_t xRxHanderTask = NULL; #if ( ipconfigUSE_LLMNR == 1 ) static const uint8_t xLLMNR_MACAddress[] = { '\x01', '\x00', '\x5E', '\x00', '\x00', '\xFC' }; #endif /* ipconfigUSE_LLMNR == 1 */ /* xTXDescriptorSemaphore is a counting semaphore with * a maximum count of ETH_TXBUFNB, which is the number of * DMA TX descriptors. */ static SemaphoreHandle_t xTXDescriptorSemaphore = NULL; /*-----------------------------------------------------------*/ BaseType_t xNetworkInterfaceInitialise( void ) { BaseType_t xReturn = pdPASS; /* The interrupt will be turned on when a link is established. */ NVIC_DisableIRQ( ETHERNET_IRQn ); /* Disable receive and transmit DMA processes. */ LPC_ETHERNET->DMA_OP_MODE &= ~( DMA_OM_ST | DMA_OM_SR ); /* Disable packet reception. */ LPC_ETHERNET->MAC_CONFIG &= ~( MAC_CFG_RE | MAC_CFG_TE ); /* Call the LPCOpen function to initialise the hardware. */ Chip_ENET_Init( LPC_ETHERNET ); /* Save MAC address. */ Chip_ENET_SetADDR( LPC_ETHERNET, ipLOCAL_MAC_ADDRESS ); /* Clear all MAC address hash entries. */ LPC_ETHERNET->MAC_HASHTABLE_HIGH = 0; LPC_ETHERNET->MAC_HASHTABLE_LOW = 0; #if ( ipconfigUSE_LLMNR == 1 ) { prvAddMACAddress( xLLMNR_MACAddress ); } #endif /* ipconfigUSE_LLMNR == 1 */ /* Promiscuous flag (PR) and Receive All flag (RA) set to zero. The * registers MAC_HASHTABLE_[LOW|HIGH] will be loaded to allow certain * multi-cast addresses. */ LPC_ETHERNET->MAC_FRAME_FILTER = MAC_FF_HMC; #if ( configUSE_RMII == 1 ) { if( lpc_phy_init( pdTRUE, prvDelay ) != SUCCESS ) { xReturn = pdFAIL; } } #else { #if ( ipconfigPORT_SUPPRESS_WARNING == 0 ) { #warning This path has not been tested. } #endif if( lpc_phy_init( pdFALSE, prvDelay ) != SUCCESS ) { xReturn = pdFAIL; } } #endif /* if ( configUSE_RMII == 1 ) */ if( xReturn == pdPASS ) { /* Guard against the task being created more than once and the * descriptors being initialised more than once. */ if( xRxHanderTask == NULL ) { xReturn = xTaskCreate( prvEMACHandlerTask, "EMAC", nwRX_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xRxHanderTask ); configASSERT( xReturn != NULL ); } if( xTXDescriptorSemaphore == NULL ) { /* Create a counting semaphore, with a value of 'configNUM_TX_DESCRIPTORS' * and a maximum of 'configNUM_TX_DESCRIPTORS'. */ xTXDescriptorSemaphore = xSemaphoreCreateCounting( ( UBaseType_t ) configNUM_TX_DESCRIPTORS, ( UBaseType_t ) configNUM_TX_DESCRIPTORS ); configASSERT( xTXDescriptorSemaphore != NULL ); } /* Enable MAC interrupts. */ LPC_ETHERNET->DMA_INT_EN = nwDMA_INTERRUPT_MASK; } if( xReturn != pdFAIL ) { /* Auto-negotiate was already started. Wait for it to complete. */ xReturn = prvSetLinkSpeed(); if( xReturn == pdPASS ) { /* Initialise the descriptors. */ prvSetupTxDescriptors(); prvSetupRxDescriptors(); /* Clear all interrupts. */ LPC_ETHERNET->DMA_STAT = DMA_ST_ALL; /* Enable receive and transmit DMA processes. */ LPC_ETHERNET->DMA_OP_MODE |= DMA_OM_ST | DMA_OM_SR; /* Set Receiver / Transmitter Enable. */ LPC_ETHERNET->MAC_CONFIG |= MAC_CFG_RE | MAC_CFG_TE; /* Start receive polling. */ LPC_ETHERNET->DMA_REC_POLL_DEMAND = 1; /* Enable interrupts in the NVIC. */ NVIC_SetPriority( ETHERNET_IRQn, configMAC_INTERRUPT_PRIORITY ); NVIC_EnableIRQ( ETHERNET_IRQn ); } } return xReturn; } /*-----------------------------------------------------------*/ #define niBUFFER_1_PACKET_SIZE 1536 static __attribute__( ( section( "._ramAHB32" ) ) ) uint8_t ucNetworkPackets[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS * niBUFFER_1_PACKET_SIZE ] __attribute__( ( aligned( 32 ) ) ); void vNetworkInterfaceAllocateRAMToBuffers( NetworkBufferDescriptor_t pxNetworkBuffers[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS ] ) { uint8_t * ucRAMBuffer = ucNetworkPackets; uint32_t ul; for( ul = 0; ul < ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS; ul++ ) { pxNetworkBuffers[ ul ].pucEthernetBuffer = ucRAMBuffer + ipBUFFER_PADDING; *( ( unsigned * ) ucRAMBuffer ) = ( unsigned ) ( &( pxNetworkBuffers[ ul ] ) ); ucRAMBuffer += niBUFFER_1_PACKET_SIZE; } } /*-----------------------------------------------------------*/ configPLACE_IN_SECTION_RAM static void vClearTXBuffers() { uint32_t ulLastDescriptor = ulNextFreeTxDescriptor; size_t uxCount = ( ( size_t ) configNUM_TX_DESCRIPTORS ) - uxSemaphoreGetCount( xTXDescriptorSemaphore ); #if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) NetworkBufferDescriptor_t * pxNetworkBuffer; uint8_t * ucPayLoad; #endif /* This function is called after a TX-completion interrupt. * It will release each Network Buffer used in xNetworkInterfaceOutput(). * 'uxCount' represents the number of descriptors given to DMA for transmission. * After sending a packet, the DMA will clear the 'TDES_OWN' bit. */ while( ( uxCount > ( size_t ) 0u ) && ( ( xDMATxDescriptors[ ulTxDescriptorToClear ].CTRLSTAT & TDES_OWN ) == 0 ) ) { if( ( ulTxDescriptorToClear == ulLastDescriptor ) && ( uxCount != ( size_t ) configNUM_TX_DESCRIPTORS ) ) { break; } #if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) { ucPayLoad = ( uint8_t * ) xDMATxDescriptors[ ulTxDescriptorToClear ].B1ADD; if( ucPayLoad != NULL ) { /* B1ADD points to a pucEthernetBuffer of a Network Buffer descriptor. */ pxNetworkBuffer = pxPacketBuffer_to_NetworkBuffer( ucPayLoad ); configASSERT( pxNetworkBuffer != NULL ); vReleaseNetworkBufferAndDescriptor( pxNetworkBuffer ); xDMATxDescriptors[ ulTxDescriptorToClear ].B1ADD = ( uint32_t ) 0u; } } #endif /* ipconfigZERO_COPY_TX_DRIVER */ /* Move onto the next descriptor, wrapping if necessary. */ ulTxDescriptorToClear++; if( ulTxDescriptorToClear >= configNUM_TX_DESCRIPTORS ) { ulTxDescriptorToClear = 0; } uxCount--; /* Tell the counting semaphore that one more TX descriptor is available. */ xSemaphoreGive( xTXDescriptorSemaphore ); } } /*-----------------------------------------------------------*/ configPLACE_IN_SECTION_RAM BaseType_t xNetworkInterfaceOutput( NetworkBufferDescriptor_t * const pxDescriptor, BaseType_t bReleaseAfterSend ) { BaseType_t xReturn = pdFAIL; const TickType_t xBlockTimeTicks = pdMS_TO_TICKS( 50 ); /* Attempt to obtain access to a Tx descriptor. */ do { if( xTXDescriptorSemaphore == NULL ) { break; } if( xSemaphoreTake( xTXDescriptorSemaphore, xBlockTimeTicks ) != pdPASS ) { /* Time-out waiting for a free TX descriptor. */ break; } /* If the descriptor is still owned by the DMA it can't be used. */ if( ( xDMATxDescriptors[ ulNextFreeTxDescriptor ].CTRLSTAT & TDES_OWN ) != 0 ) { /* The semaphore was taken, the TX DMA-descriptor is still not available. * Actually that should not occur, the 'TDES_OWN' was already confirmed low in vClearTXBuffers(). */ xSemaphoreGive( xTXDescriptorSemaphore ); } else { #if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) { /* bReleaseAfterSend should always be set when using the zero * copy driver. */ configASSERT( bReleaseAfterSend != pdFALSE ); /* The DMA's descriptor to point directly to the data in the * network buffer descriptor. The data is not copied. */ xDMATxDescriptors[ ulNextFreeTxDescriptor ].B1ADD = ( uint32_t ) pxDescriptor->pucEthernetBuffer; /* The DMA descriptor will 'own' this Network Buffer, * until it has been sent. So don't release it now. */ bReleaseAfterSend = false; } #else /* if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) */ { /* The data is copied from the network buffer descriptor into * the DMA's descriptor. */ memcpy( ( void * ) xDMATxDescriptors[ ulNextFreeTxDescriptor ].B1ADD, ( void * ) pxDescriptor->pucEthernetBuffer, pxDescriptor->xDataLength ); } #endif /* if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) */ xDMATxDescriptors[ ulNextFreeTxDescriptor ].BSIZE = ( uint32_t ) TDES_ENH_BS1( pxDescriptor->xDataLength ); /* This descriptor is given back to the DMA. */ xDMATxDescriptors[ ulNextFreeTxDescriptor ].CTRLSTAT |= TDES_OWN; /* Ensure the DMA is polling Tx descriptors. */ LPC_ETHERNET->DMA_TRANS_POLL_DEMAND = 1; iptraceNETWORK_INTERFACE_TRANSMIT(); /* Move onto the next descriptor, wrapping if necessary. */ ulNextFreeTxDescriptor++; if( ulNextFreeTxDescriptor >= configNUM_TX_DESCRIPTORS ) { ulNextFreeTxDescriptor = 0; } /* The Tx has been initiated. */ xReturn = pdPASS; } } while( 0 ); /* The buffer has been sent so can be released. */ if( bReleaseAfterSend != pdFALSE ) { vReleaseNetworkBufferAndDescriptor( pxDescriptor ); } return xReturn; } /*-----------------------------------------------------------*/ static void prvDelay( uint32_t ulMilliSeconds ) { /* Ensure the scheduler was started before attempting to use the scheduler to * create a delay. */ configASSERT( xTaskGetSchedulerState() == taskSCHEDULER_RUNNING ); vTaskDelay( pdMS_TO_TICKS( ulMilliSeconds ) ); } /*-----------------------------------------------------------*/ static void prvSetupTxDescriptors( void ) { BaseType_t x; /* Start with Tx descriptors clear. */ memset( ( void * ) xDMATxDescriptors, 0, sizeof( xDMATxDescriptors ) ); /* Index to the next Tx descriptor to use. */ ulNextFreeTxDescriptor = 0ul; /* Index to the next Tx descriptor to clear ( after transmission ). */ ulTxDescriptorToClear = 0ul; for( x = 0; x < configNUM_TX_DESCRIPTORS; x++ ) { #if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) { /* Nothing to do, B1ADD will be set when data is ready to transmit. * Currently the memset above will have set it to NULL. */ } #else { /* Allocate a buffer to the Tx descriptor. This is the most basic * way of creating a driver as the data is then copied into the * buffer. */ xDMATxDescriptors[ x ].B1ADD = ( uint32_t ) pvPortMalloc( ipTOTAL_ETHERNET_FRAME_SIZE ); /* Use an assert to check the allocation as +TCP applications will * often not use a malloc() failed hook as the TCP stack will recover * from allocation failures. */ configASSERT( xDMATxDescriptors[ x ].B1ADD != 0U ); } #endif /* if ( ipconfigZERO_COPY_TX_DRIVER != 0 ) */ /* Buffers hold an entire frame so all buffers are both the start and * end of a frame. */ /* TDES_ENH_TCH Second Address Chained. */ /* TDES_ENH_CIC(n) Checksum Insertion Control, tried but it does not work for the LPC18xx... */ /* TDES_ENH_FS First Segment. */ /* TDES_ENH_LS Last Segment. */ /* TDES_ENH_IC Interrupt on Completion. */ xDMATxDescriptors[ x ].CTRLSTAT = TDES_ENH_TCH | TDES_ENH_CIC( 3 ) | TDES_ENH_FS | TDES_ENH_LS | TDES_ENH_IC; xDMATxDescriptors[ x ].B2ADD = ( uint32_t ) &xDMATxDescriptors[ x + 1 ]; } xDMATxDescriptors[ configNUM_TX_DESCRIPTORS - 1 ].CTRLSTAT |= TDES_ENH_TER; xDMATxDescriptors[ configNUM_TX_DESCRIPTORS - 1 ].B2ADD = ( uint32_t ) &xDMATxDescriptors[ 0 ]; /* Point the DMA to the base of the descriptor list. */ LPC_ETHERNET->DMA_TRANS_DES_ADDR = ( uint32_t ) xDMATxDescriptors; } /*-----------------------------------------------------------*/ static void prvSetupRxDescriptors( void ) { BaseType_t x; #if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) NetworkBufferDescriptor_t * pxNetworkBuffer; #endif /* Index to the next Rx descriptor to use. */ ulNextRxDescriptorToProcess = 0; /* Clear RX descriptor list. */ memset( ( void * ) xDMARxDescriptors, 0, sizeof( xDMARxDescriptors ) ); for( x = 0; x < configNUM_RX_DESCRIPTORS; x++ ) { /* Allocate a buffer of the largest possible frame size as it is not * known what size received frames will be. */ #if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) { pxNetworkBuffer = pxGetNetworkBufferWithDescriptor( ipTOTAL_ETHERNET_FRAME_SIZE, 0 ); /* During start-up there should be enough Network Buffers available, * so it is safe to use configASSERT(). * In case this assert fails, please check: configNUM_RX_DESCRIPTORS, * ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS, and in case BufferAllocation_2.c * is included, check the amount of available heap. */ configASSERT( pxNetworkBuffer != NULL ); /* Pass the actual buffer to DMA. */ xDMARxDescriptors[ x ].B1ADD = ( uint32_t ) pxNetworkBuffer->pucEthernetBuffer; } #else { /* All DMA descriptors are populated with permanent memory blocks. * Their contents will be copy to Network Buffers. */ xDMARxDescriptors[ x ].B1ADD = ( uint32_t ) pvPortMalloc( ipTOTAL_ETHERNET_FRAME_SIZE ); } #endif /* ipconfigZERO_COPY_RX_DRIVER */ /* Use an assert to check the allocation as +TCP applications will often * not use a malloc failed hook as the TCP stack will recover from * allocation failures. */ configASSERT( xDMARxDescriptors[ x ].B1ADD != 0U ); xDMARxDescriptors[ x ].B2ADD = ( uint32_t ) &( xDMARxDescriptors[ x + 1 ] ); xDMARxDescriptors[ x ].CTRL = ( uint32_t ) RDES_ENH_BS1( ipTOTAL_ETHERNET_FRAME_SIZE ) | RDES_ENH_RCH; /* The descriptor is available for use by the DMA. */ xDMARxDescriptors[ x ].STATUS = RDES_OWN; } /* RDES_ENH_RER Receive End of Ring. */ xDMARxDescriptors[ ( configNUM_RX_DESCRIPTORS - 1 ) ].CTRL |= RDES_ENH_RER; xDMARxDescriptors[ configNUM_RX_DESCRIPTORS - 1 ].B2ADD = ( uint32_t ) &( xDMARxDescriptors[ 0 ] ); /* Point the DMA to the base of the descriptor list. */ LPC_ETHERNET->DMA_REC_DES_ADDR = ( uint32_t ) xDMARxDescriptors; } /*-----------------------------------------------------------*/ configPLACE_IN_SECTION_RAM static void prvRemoveTrailingBytes( NetworkBufferDescriptor_t * pxDescriptor ) { size_t xExpectedLength; IPPacket_t * pxIPPacket; pxIPPacket = ( IPPacket_t * ) pxDescriptor->pucEthernetBuffer; /* Look at the actual length of the packet, translate it to a host-endian notation. */ xExpectedLength = sizeof( EthernetHeader_t ) + ( size_t ) FreeRTOS_htons( pxIPPacket->xIPHeader.usLength ); if( xExpectedLength == ( pxDescriptor->xDataLength + 4 ) ) { pxDescriptor->xDataLength -= 4; } else { if( pxDescriptor->xDataLength > xExpectedLength ) { pxDescriptor->xDataLength = ( size_t ) xExpectedLength; } } } /*-----------------------------------------------------------*/ configPLACE_IN_SECTION_RAM BaseType_t xGetPhyLinkStatus( void ) { BaseType_t xReturn; if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) == 0 ) { xReturn = pdFALSE; } else { xReturn = pdTRUE; } return xReturn; } /*-----------------------------------------------------------*/ uint32_t ulDataAvailable; configPLACE_IN_SECTION_RAM static BaseType_t prvNetworkInterfaceInput() { BaseType_t xResult = pdFALSE; uint32_t ulStatus; eFrameProcessingResult_t eResult; const TickType_t xDescriptorWaitTime = pdMS_TO_TICKS( 250 ); const UBaseType_t uxMinimumBuffersRemaining = 3UL; uint16_t usLength; NetworkBufferDescriptor_t * pxDescriptor; #if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) NetworkBufferDescriptor_t * pxNewDescriptor; #endif /* ipconfigZERO_COPY_RX_DRIVER */ #if ( ipconfigUSE_LINKED_RX_MESSAGES == 0 ) IPStackEvent_t xRxEvent = { eNetworkRxEvent, NULL }; #endif /* Process each descriptor that is not still in use by the DMA. */ ulStatus = xDMARxDescriptors[ ulNextRxDescriptorToProcess ].STATUS; if( ( ulStatus & RDES_OWN ) == 0 ) { /* Check packet for errors */ if( ( ulStatus & nwRX_STATUS_ERROR_BITS ) != 0 ) { /* There is some reception error. */ intCount[ 3 ]++; /* Clear error bits. */ ulStatus &= ~( ( uint32_t ) nwRX_STATUS_ERROR_BITS ); } else { xResult++; eResult = ipCONSIDER_FRAME_FOR_PROCESSING( ( const uint8_t * const ) ( xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD ) ); if( eResult == eProcessBuffer ) { if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) == 0 ) { ulPHYLinkStatus |= PHY_LINK_CONNECTED; FreeRTOS_printf( ( "prvEMACHandlerTask: PHY LS now %d (message received)\n", ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != 0 ) ); } #if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) if( uxGetNumberOfFreeNetworkBuffers() > uxMinimumBuffersRemaining ) { pxNewDescriptor = pxGetNetworkBufferWithDescriptor( ipTOTAL_ETHERNET_FRAME_SIZE, xDescriptorWaitTime ); } else { /* Too risky to allocate a new Network Buffer. */ pxNewDescriptor = NULL; } if( pxNewDescriptor != NULL ) #else /* if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) */ if( uxGetNumberOfFreeNetworkBuffers() > uxMinimumBuffersRemaining ) #endif /* ipconfigZERO_COPY_RX_DRIVER */ { #if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) const uint8_t * pucBuffer; #endif /* Get the actual length. */ usLength = RDES_FLMSK( ulStatus ); #if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) { /* Replace the character buffer 'B1ADD'. */ pucBuffer = ( const uint8_t * const ) ( xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD ); xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD = ( uint32_t ) pxNewDescriptor->pucEthernetBuffer; /* 'B1ADD' contained the address of a 'pucEthernetBuffer' that * belongs to a Network Buffer. Find the original Network Buffer. */ pxDescriptor = pxPacketBuffer_to_NetworkBuffer( pucBuffer ); /* This zero-copy driver makes sure that every 'xDMARxDescriptors' contains * a reference to a Network Buffer at any time. * In case it runs out of Network Buffers, a DMA buffer won't be replaced, * and the received messages is dropped. */ configASSERT( pxDescriptor != NULL ); } #else /* if ( ipconfigZERO_COPY_RX_DRIVER != 0 ) */ { /* Create a buffer of exactly the required length. */ pxDescriptor = pxGetNetworkBufferWithDescriptor( usLength, xDescriptorWaitTime ); } #endif /* ipconfigZERO_COPY_RX_DRIVER */ if( pxDescriptor != NULL ) { pxDescriptor->xDataLength = ( size_t ) usLength; #if ( ipconfigZERO_COPY_RX_DRIVER == 0 ) { /* Copy the data into the allocated buffer. */ memcpy( ( void * ) pxDescriptor->pucEthernetBuffer, ( void * ) xDMARxDescriptors[ ulNextRxDescriptorToProcess ].B1ADD, usLength ); } #endif /* ipconfigZERO_COPY_RX_DRIVER */ /* It is possible that more data was copied than * actually makes up the frame. If this is the case * adjust the length to remove any trailing bytes. */ prvRemoveTrailingBytes( pxDescriptor ); /* Pass the data to the TCP/IP task for processing. */ xRxEvent.pvData = ( void * ) pxDescriptor; if( xSendEventStructToIPTask( &xRxEvent, xDescriptorWaitTime ) == pdFALSE ) { /* Could not send the descriptor into the TCP/IP * stack, it must be released. */ vReleaseNetworkBufferAndDescriptor( pxDescriptor ); } else { iptraceNETWORK_INTERFACE_RECEIVE(); /* The data that was available at the top of this * loop has been sent, so is no longer available. */ ulDataAvailable = pdFALSE; } } } } else { /* The packet is discarded as uninteresting. */ ulDataAvailable = pdFALSE; } /* Got here because received data was sent to the IP task or the * data contained an error and was discarded. Give the descriptor * back to the DMA. */ xDMARxDescriptors[ ulNextRxDescriptorToProcess ].STATUS = ulStatus | RDES_OWN; /* Move onto the next descriptor. */ ulNextRxDescriptorToProcess++; if( ulNextRxDescriptorToProcess >= configNUM_RX_DESCRIPTORS ) { ulNextRxDescriptorToProcess = 0; } ulStatus = xDMARxDescriptors[ ulNextRxDescriptorToProcess ].STATUS; } /* if( ( ulStatus & nwRX_STATUS_ERROR_BITS ) != 0 ) */ } /* if( ( ulStatus & RDES_OWN ) == 0 ) */ /* Restart receive polling. */ LPC_ETHERNET->DMA_REC_POLL_DEMAND = 1; return xResult; } /*-----------------------------------------------------------*/ configPLACE_IN_SECTION_RAM void NETWORK_IRQHandler( void ) { BaseType_t xHigherPriorityTaskWoken = pdFALSE; uint32_t ulDMAStatus; const uint32_t ulRxInterruptMask = DMA_ST_RI | /* Receive interrupt */ DMA_ST_RU; /* Receive buffer unavailable */ const uint32_t ulTxInterruptMask = DMA_ST_TI | /* Transmit interrupt */ DMA_ST_TPS; /* Transmit process stopped */ configASSERT( xRxHanderTask ); /* Get pending interrupts. */ ulDMAStatus = LPC_ETHERNET->DMA_STAT; /* RX group interrupt(s). */ if( ( ulDMAStatus & ulRxInterruptMask ) != 0x00 ) { /* Remember that an RX event has happened. */ ulISREvents |= EMAC_IF_RX_EVENT; vTaskNotifyGiveFromISR( xRxHanderTask, &xHigherPriorityTaskWoken ); intCount[ 0 ]++; } /* TX group interrupt(s). */ if( ( ulDMAStatus & ulTxInterruptMask ) != 0x00 ) { /* Remember that a TX event has happened. */ ulISREvents |= EMAC_IF_TX_EVENT; vTaskNotifyGiveFromISR( xRxHanderTask, &xHigherPriorityTaskWoken ); intCount[ 1 ]++; } /* Test for 'Abnormal interrupt summary'. */ if( ( ulDMAStatus & DMA_ST_AIE ) != 0x00 ) { /* The trace macro must be written such that it can be called from * an interrupt. */ iptraceETHERNET_RX_EVENT_LOST(); } /* Clear pending interrupts */ LPC_ETHERNET->DMA_STAT = ulDMAStatus; /* Context switch needed? */ portYIELD_FROM_ISR( xHigherPriorityTaskWoken ); } /*-----------------------------------------------------------*/ static BaseType_t prvSetLinkSpeed( void ) { BaseType_t xReturn = pdFAIL; TickType_t xTimeOnEntering; uint32_t ulPhyStatus; const TickType_t xAutoNegotiateDelay = pdMS_TO_TICKS( 5000UL ); /* Ensure polling does not starve lower priority tasks by temporarily * setting the priority of this task to that of the idle task. */ vTaskPrioritySet( NULL, tskIDLE_PRIORITY ); xTimeOnEntering = xTaskGetTickCount(); do { ulPhyStatus = lpcPHYStsPoll(); if( ( ulPhyStatus & PHY_LINK_CONNECTED ) != 0x00 ) { /* Set interface speed and duplex. */ if( ( ulPhyStatus & PHY_LINK_SPEED100 ) != 0x00 ) { Chip_ENET_SetSpeed( LPC_ETHERNET, 1 ); } else { Chip_ENET_SetSpeed( LPC_ETHERNET, 0 ); } if( ( ulPhyStatus & PHY_LINK_FULLDUPLX ) != 0x00 ) { Chip_ENET_SetDuplex( LPC_ETHERNET, true ); } else { Chip_ENET_SetDuplex( LPC_ETHERNET, false ); } xReturn = pdPASS; break; } } while( ( xTaskGetTickCount() - xTimeOnEntering ) < xAutoNegotiateDelay ); /* Reset the priority of this task back to its original value. */ vTaskPrioritySet( NULL, ipconfigIP_TASK_PRIORITY ); return xReturn; } /*-----------------------------------------------------------*/ static uint32_t prvGenerateCRC32( const uint8_t * ucAddress ) { unsigned int j; const uint32_t Polynomial = 0xEDB88320; uint32_t crc = ~0ul; const uint8_t * pucCurrent = ( const uint8_t * ) ucAddress; const uint8_t * pucLast = pucCurrent + 6; /* Calculate normal CRC32 */ while( pucCurrent < pucLast ) { crc ^= *( pucCurrent++ ); for( j = 0; j < 8; j++ ) { if( ( crc & 1 ) != 0 ) { crc = ( crc >> 1 ) ^ Polynomial; } else { crc >>= 1; } } } return ~crc; } /*-----------------------------------------------------------*/ static uint32_t prvGetHashIndex( const uint8_t * ucAddress ) { uint32_t ulCrc = prvGenerateCRC32( ucAddress ); uint32_t ulIndex = 0ul; BaseType_t xCount = 6; /* Take the lowest 6 bits of the CRC32 and reverse them */ while( xCount-- ) { ulIndex <<= 1; ulIndex |= ( ulCrc & 1 ); ulCrc >>= 1; } /* This is the has value of 'ucAddress' */ return ulIndex; } /*-----------------------------------------------------------*/ static void prvAddMACAddress( const uint8_t * ucMacAddress ) { BaseType_t xIndex; xIndex = prvGetHashIndex( ucMacAddress ); if( xIndex >= 32 ) { LPC_ETHERNET->MAC_HASHTABLE_HIGH |= ( 1u << ( xIndex - 32 ) ); } else { LPC_ETHERNET->MAC_HASHTABLE_LOW |= ( 1u << xIndex ); } } /*-----------------------------------------------------------*/ configPLACE_IN_SECTION_RAM static void prvEMACHandlerTask( void * pvParameters ) { TimeOut_t xPhyTime; TickType_t xPhyRemTime; UBaseType_t uxLastMinBufferCount = 0; UBaseType_t uxCurrentCount; BaseType_t xResult = 0; uint32_t ulStatus; const TickType_t xBlockTime = pdMS_TO_TICKS( 5000ul ); /* Remove compiler warning about unused parameter. */ ( void ) pvParameters; /* A possibility to set some additional task properties. */ iptraceEMAC_TASK_STARTING(); vTaskSetTimeOutState( &xPhyTime ); xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_LOW_CHECK_TIME_MS ); for( ; ; ) { uxCurrentCount = uxGetMinimumFreeNetworkBuffers(); if( uxLastMinBufferCount != uxCurrentCount ) { /* The logging produced below may be helpful * while tuning +TCP: see how many buffers are in use. */ uxLastMinBufferCount = uxCurrentCount; FreeRTOS_printf( ( "Network buffers: %lu lowest %lu\n", uxGetNumberOfFreeNetworkBuffers(), uxCurrentCount ) ); } #if ( ipconfigCHECK_IP_QUEUE_SPACE != 0 ) { static UBaseType_t uxLastMinQueueSpace = 0; uxCurrentCount = uxGetMinimumIPQueueSpace(); if( uxLastMinQueueSpace != uxCurrentCount ) { /* The logging produced below may be helpful * while tuning +TCP: see how many buffers are in use. */ uxLastMinQueueSpace = uxCurrentCount; FreeRTOS_printf( ( "Queue space: lowest %lu\n", uxCurrentCount ) ); } } #endif /* ipconfigCHECK_IP_QUEUE_SPACE */ ulTaskNotifyTake( pdTRUE, xBlockTime ); xResult = ( BaseType_t ) 0; if( ( ulISREvents & EMAC_IF_TX_EVENT ) != 0 ) { /* Code to release TX buffers if zero-copy is used. */ ulISREvents &= ~EMAC_IF_TX_EVENT; { /* Check if DMA packets have been delivered. */ vClearTXBuffers(); } } if( ( ulISREvents & EMAC_IF_RX_EVENT ) != 0 ) { ulISREvents &= ~EMAC_IF_RX_EVENT; xResult = prvNetworkInterfaceInput(); if( xResult > 0 ) { while( prvNetworkInterfaceInput() > 0 ) { } } } if( xResult > 0 ) { /* A packet was received. No need to check for the PHY status now, * but set a timer to check it later on. */ vTaskSetTimeOutState( &xPhyTime ); xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_HIGH_CHECK_TIME_MS ); xResult = 0; } else if( xTaskCheckForTimeOut( &xPhyTime, &xPhyRemTime ) != pdFALSE ) { ulStatus = lpcPHYStsPoll(); if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != ( ulStatus & PHY_LINK_CONNECTED ) ) { ulPHYLinkStatus = ulStatus; FreeRTOS_printf( ( "prvEMACHandlerTask: PHY LS now %d (polled PHY)\n", ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != 0 ) ); } vTaskSetTimeOutState( &xPhyTime ); if( ( ulPHYLinkStatus & PHY_LINK_CONNECTED ) != 0 ) { xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_HIGH_CHECK_TIME_MS ); } else { xPhyRemTime = pdMS_TO_TICKS( ipconfigPHY_LS_LOW_CHECK_TIME_MS ); } } } } /*-----------------------------------------------------------*/