/* | |
FreeRTOS+TCP V2.0.11 | |
Copyright (C) 2017 Amazon.com, Inc. or its affiliates. All Rights Reserved. | |
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 <stdint.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
/* 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 | |
#ifndef PHY_LS_HIGH_CHECK_TIME_MS | |
/* Check if the LinkSStatus in the PHY is still high after 15 seconds of not | |
receiving packets. */ | |
#define PHY_LS_HIGH_CHECK_TIME_MS 15000 | |
#endif | |
#ifndef PHY_LS_LOW_CHECK_TIME_MS | |
/* Check if the LinkSStatus in the PHY is still low every second. */ | |
#define PHY_LS_LOW_CHECK_TIME_MS 1000 | |
#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( 0 ) | |
#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 ) | |
#warning It is adviced to enable both macros | |
#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; | |
/* Must be defined externally - the demo applications define this in main.c. */ | |
extern uint8_t ucMACAddress[ 6 ]; | |
/* 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, ucMACAddress ); | |
/* 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 | |
{ | |
#warning This path has not been tested. | |
if( lpc_phy_init( pdFALSE, prvDelay ) != SUCCESS ) | |
{ | |
xReturn = pdFAIL; | |
} | |
} | |
#endif | |
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 ); | |
} | |
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 ); | |
} | |
/* 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 | |
{ | |
/* 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 | |
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 ); | |
} | |
#endif | |
/* 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 ); | |
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-endial 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( 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 | |
{ | |
/* 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( PHY_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( PHY_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( PHY_LS_HIGH_CHECK_TIME_MS ); | |
} | |
else | |
{ | |
xPhyRemTime = pdMS_TO_TICKS( PHY_LS_LOW_CHECK_TIME_MS ); | |
} | |
} | |
} | |
} | |
/*-----------------------------------------------------------*/ |