/* * 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 #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" /* Some files from the Atmel Software Framework */ /*_RB_ The SAM4E portable layer has three different header files called gmac.h! */ #include "instance/gmac.h" #include #include #ifndef BMSR_LINK_STATUS #define BMSR_LINK_STATUS 0x0004 //!< Link status #endif #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 /* Interrupt events to process. Currently only the Rx event is 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 ) #define ETHERNET_CONF_PHY_ADDR BOARD_GMAC_PHY_ADDR #define HZ_PER_MHZ ( 1000000UL ) #ifndef EMAC_MAX_BLOCK_TIME_MS #define EMAC_MAX_BLOCK_TIME_MS 100ul #endif #if !defined( GMAC_USES_TX_CALLBACK ) || ( GMAC_USES_TX_CALLBACK != 1 ) #error Please define GMAC_USES_TX_CALLBACK as 1 #endif #if( ipconfigZERO_COPY_RX_DRIVER != 0 ) #warning The EMAC of SAM4E has fixed-size RX buffers so ZERO_COPY_RX is not possible #endif /* Default the size of the stack used by the EMAC deferred handler task to 4x the size of the stack used by the idle task - but allow this to be overridden in FreeRTOSConfig.h as configMINIMAL_STACK_SIZE is a user definable constant. */ #ifndef configEMAC_TASK_STACK_SIZE #define configEMAC_TASK_STACK_SIZE ( 4 * configMINIMAL_STACK_SIZE ) #endif /*-----------------------------------------------------------*/ /* * Wait a fixed time for the link status to indicate the network is up. */ static BaseType_t xGMACWaitLS( TickType_t xMaxTime ); #if( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 1 ) && ( ipconfigHAS_TX_CRC_OFFLOADING == 0 ) void vGMACGenerateChecksum( uint8_t *apBuffer ); #endif /* * Called from the ASF GMAC driver. */ static void prvRxCallback( uint32_t ulStatus ); static void prvTxCallback( uint32_t ulStatus, uint8_t *puc_buffer ); /* * A deferred interrupt handler task that processes GMAC interrupts. */ static void prvEMACHandlerTask( void *pvParameters ); /* * Initialise the ASF GMAC driver. */ static BaseType_t prvGMACInit( void ); /* * Try to obtain an Rx packet from the hardware. */ static uint32_t prvEMACRxPoll( void ); /*-----------------------------------------------------------*/ /* Bit map of outstanding ETH interrupt events for processing. Currently only the Rx 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; static volatile BaseType_t xGMACSwitchRequired; /* ethernet_phy_addr: the address of the PHY in use. Atmel was a bit ambiguous about it so the address will be stored in this variable, see ethernet_phy.c */ extern int ethernet_phy_addr; /* LLMNR multicast address. */ static const uint8_t llmnr_mac_address[] = { 0x01, 0x00, 0x5E, 0x00, 0x00, 0xFC }; /* The GMAC object as defined by the ASF drivers. */ static gmac_device_t gs_gmac_dev; /* MAC address to use. */ extern const uint8_t ucMACAddress[ 6 ]; /* Holds the handle of the task used as a deferred interrupt processor. The handle is used so direct notifications can be sent to the task for all EMAC/DMA related interrupts. */ TaskHandle_t xEMACTaskHandle = NULL; static QueueHandle_t xTxBufferQueue; int tx_release_count[ 4 ]; /* xTXDescriptorSemaphore is a counting semaphore with a maximum count of GMAC_TX_BUFFERS, which is the number of DMA TX descriptors. */ static SemaphoreHandle_t xTXDescriptorSemaphore = NULL; /*-----------------------------------------------------------*/ /* * GMAC interrupt handler. */ void GMAC_Handler(void) { xGMACSwitchRequired = pdFALSE; /* gmac_handler() may call prvRxCallback() which may change the value of xGMACSwitchRequired. */ gmac_handler( &gs_gmac_dev ); if( xGMACSwitchRequired != pdFALSE ) { portEND_SWITCHING_ISR( xGMACSwitchRequired ); } } /*-----------------------------------------------------------*/ static void prvRxCallback( uint32_t ulStatus ) { if( ( ( ulStatus & GMAC_RSR_REC ) != 0 ) && ( xEMACTaskHandle != NULL ) ) { /* let the prvEMACHandlerTask know that there was an RX event. */ ulISREvents |= EMAC_IF_RX_EVENT; /* Only an RX interrupt can wakeup prvEMACHandlerTask. */ vTaskNotifyGiveFromISR( xEMACTaskHandle, ( BaseType_t * ) &xGMACSwitchRequired ); } } /*-----------------------------------------------------------*/ static void prvTxCallback( uint32_t ulStatus, uint8_t *puc_buffer ) { if( ( xTxBufferQueue != NULL ) && ( xEMACTaskHandle != NULL ) ) { /* let the prvEMACHandlerTask know that there was an RX event. */ ulISREvents |= EMAC_IF_TX_EVENT; vTaskNotifyGiveFromISR( xEMACTaskHandle, ( BaseType_t * ) &xGMACSwitchRequired ); xQueueSendFromISR( xTxBufferQueue, &puc_buffer, ( BaseType_t * ) &xGMACSwitchRequired ); tx_release_count[ 2 ]++; } } /*-----------------------------------------------------------*/ BaseType_t xNetworkInterfaceInitialise( void ) { const TickType_t x5_Seconds = 5000UL; if( xEMACTaskHandle == NULL ) { prvGMACInit(); /* Wait at most 5 seconds for a Link Status in the PHY. */ xGMACWaitLS( pdMS_TO_TICKS( x5_Seconds ) ); /* The handler task is created at the highest possible priority to ensure the interrupt handler can return directly to it. */ xTaskCreate( prvEMACHandlerTask, "EMAC", configEMAC_TASK_STACK_SIZE, NULL, configMAX_PRIORITIES - 1, &xEMACTaskHandle ); configASSERT( xEMACTaskHandle ); } if( xTxBufferQueue == NULL ) { xTxBufferQueue = xQueueCreate( GMAC_TX_BUFFERS, sizeof( void * ) ); configASSERT( xTxBufferQueue ); } if( xTXDescriptorSemaphore == NULL ) { xTXDescriptorSemaphore = xSemaphoreCreateCounting( ( UBaseType_t ) GMAC_TX_BUFFERS, ( UBaseType_t ) GMAC_TX_BUFFERS ); configASSERT( xTXDescriptorSemaphore ); } /* When returning non-zero, the stack will become active and start DHCP (in configured) */ return ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0; } /*-----------------------------------------------------------*/ BaseType_t xGetPhyLinkStatus( void ) { BaseType_t xResult; /* This function returns true if the Link Status in the PHY is high. */ if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 ) { xResult = pdTRUE; } else { xResult = pdFALSE; } return xResult; } /*-----------------------------------------------------------*/ BaseType_t xNetworkInterfaceOutput( NetworkBufferDescriptor_t * const pxDescriptor, BaseType_t bReleaseAfterSend ) { /* Do not wait too long for a free TX DMA buffer. */ const TickType_t xBlockTimeTicks = pdMS_TO_TICKS( 50u ); do { if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) == 0 ) { /* Do not attempt to send packets as long as the Link Status is low. */ break; } if( xTXDescriptorSemaphore == NULL ) { /* Semaphore has not been created yet? */ break; } if( xSemaphoreTake( xTXDescriptorSemaphore, xBlockTimeTicks ) != pdPASS ) { /* Time-out waiting for a free TX descriptor. */ tx_release_count[ 3 ]++; break; } #if( ipconfigZERO_COPY_TX_DRIVER != 0 ) { /* Confirm that the pxDescriptor may be kept by the driver. */ configASSERT( bReleaseAfterSend != pdFALSE ); } #endif /* ipconfigZERO_COPY_TX_DRIVER */ gmac_dev_write( &gs_gmac_dev, (void *)pxDescriptor->pucEthernetBuffer, pxDescriptor->xDataLength, prvTxCallback ); #if( ipconfigZERO_COPY_TX_DRIVER != 0 ) { /* Confirm that the pxDescriptor may be kept by the driver. */ bReleaseAfterSend = pdFALSE; } #endif /* ipconfigZERO_COPY_TX_DRIVER */ /* Not interested in a call-back after TX. */ iptraceNETWORK_INTERFACE_TRANSMIT(); } while( 0 ); if( bReleaseAfterSend != pdFALSE ) { vReleaseNetworkBufferAndDescriptor( pxDescriptor ); } return pdTRUE; } /*-----------------------------------------------------------*/ static BaseType_t prvGMACInit( void ) { uint32_t ncfgr; gmac_options_t gmac_option; memset( &gmac_option, '\0', sizeof( gmac_option ) ); gmac_option.uc_copy_all_frame = 0; gmac_option.uc_no_boardcast = 0; memcpy( gmac_option.uc_mac_addr, ucMACAddress, sizeof( gmac_option.uc_mac_addr ) ); gs_gmac_dev.p_hw = GMAC; gmac_dev_init( GMAC, &gs_gmac_dev, &gmac_option ); NVIC_SetPriority( GMAC_IRQn, configMAC_INTERRUPT_PRIORITY ); NVIC_EnableIRQ( GMAC_IRQn ); /* Contact the Ethernet PHY and store it's address in 'ethernet_phy_addr' */ ethernet_phy_init( GMAC, ETHERNET_CONF_PHY_ADDR, sysclk_get_cpu_hz() ); ethernet_phy_auto_negotiate( GMAC, ethernet_phy_addr ); ethernet_phy_set_link( GMAC, ethernet_phy_addr, 1 ); /* The GMAC driver will call a hook prvRxCallback(), which in turn will wake-up the task by calling vTaskNotifyGiveFromISR() */ gmac_dev_set_rx_callback( &gs_gmac_dev, prvRxCallback ); gmac_set_address( GMAC, 1, (uint8_t*)llmnr_mac_address ); ncfgr = GMAC_NCFGR_SPD | GMAC_NCFGR_FD; GMAC->GMAC_NCFGR = ( GMAC->GMAC_NCFGR & ~( GMAC_NCFGR_SPD | GMAC_NCFGR_FD ) ) | ncfgr; return 1; } /*-----------------------------------------------------------*/ static inline unsigned long ulReadMDIO( unsigned /*short*/ usAddress ) { uint32_t ulValue, ulReturn; int rc; gmac_enable_management( GMAC, 1 ); rc = gmac_phy_read( GMAC, ethernet_phy_addr, usAddress, &ulValue ); gmac_enable_management( GMAC, 0 ); if( rc == GMAC_OK ) { ulReturn = ulValue; } else { ulReturn = 0UL; } return ulReturn; } /*-----------------------------------------------------------*/ static BaseType_t xGMACWaitLS( TickType_t xMaxTime ) { TickType_t xStartTime = xTaskGetTickCount(); TickType_t xEndTime; BaseType_t xReturn; const TickType_t xShortTime = pdMS_TO_TICKS( 100UL ); for( ;; ) { xEndTime = xTaskGetTickCount(); if( ( xEndTime - xStartTime ) > xMaxTime ) { /* Wated more than xMaxTime, return. */ xReturn = pdFALSE; break; } /* Check the link status again. */ ulPHYLinkStatus = ulReadMDIO( PHY_REG_01_BMSR ); if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 ) { /* Link is up - return. */ xReturn = pdTRUE; break; } /* Link is down - wait in the Blocked state for a short while (to allow other tasks to execute) before checking again. */ vTaskDelay( xShortTime ); } FreeRTOS_printf( ( "xGMACWaitLS: %ld (PHY %d) freq %lu Mz\n", xReturn, ethernet_phy_addr, sysclk_get_cpu_hz() / HZ_PER_MHZ ) ); return xReturn; } /*-----------------------------------------------------------*/ //#if( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 1 ) && ( ipconfigHAS_TX_CRC_OFFLOADING == 0 ) void vGMACGenerateChecksum( uint8_t *apBuffer ) { ProtocolPacket_t *xProtPacket = (ProtocolPacket_t *)apBuffer; if ( xProtPacket->xTCPPacket.xEthernetHeader.usFrameType == ipIPv4_FRAME_TYPE ) { IPHeader_t *pxIPHeader = &( xProtPacket->xTCPPacket.xIPHeader ); /* Calculate the IP header checksum. */ pxIPHeader->usHeaderChecksum = 0x00; pxIPHeader->usHeaderChecksum = usGenerateChecksum( 0u, ( uint8_t * ) &( pxIPHeader->ucVersionHeaderLength ), ipSIZE_OF_IPv4_HEADER ); pxIPHeader->usHeaderChecksum = ~FreeRTOS_htons( pxIPHeader->usHeaderChecksum ); /* Calculate the TCP checksum for an outgoing packet. */ usGenerateProtocolChecksum( ( uint8_t * ) apBuffer, pdTRUE ); } } //#endif /*-----------------------------------------------------------*/ static uint32_t prvEMACRxPoll( void ) { unsigned char *pucUseBuffer; uint32_t ulReceiveCount, ulResult, ulReturnValue = 0; static NetworkBufferDescriptor_t *pxNextNetworkBufferDescriptor = NULL; const UBaseType_t xMinDescriptorsToLeave = 2UL; const TickType_t xBlockTime = pdMS_TO_TICKS( 100UL ); static IPStackEvent_t xRxEvent = { eNetworkRxEvent, NULL }; for( ;; ) { /* If pxNextNetworkBufferDescriptor was not left pointing at a valid descriptor then allocate one now. */ if( ( pxNextNetworkBufferDescriptor == NULL ) && ( uxGetNumberOfFreeNetworkBuffers() > xMinDescriptorsToLeave ) ) { pxNextNetworkBufferDescriptor = pxGetNetworkBufferWithDescriptor( ipTOTAL_ETHERNET_FRAME_SIZE, xBlockTime ); } if( pxNextNetworkBufferDescriptor != NULL ) { /* Point pucUseBuffer to the buffer pointed to by the descriptor. */ pucUseBuffer = ( unsigned char* ) ( pxNextNetworkBufferDescriptor->pucEthernetBuffer - ipconfigPACKET_FILLER_SIZE ); } else { /* As long as pxNextNetworkBufferDescriptor is NULL, the incoming messages will be flushed and ignored. */ pucUseBuffer = NULL; } /* Read the next packet from the hardware into pucUseBuffer. */ ulResult = gmac_dev_read( &gs_gmac_dev, pucUseBuffer, ipTOTAL_ETHERNET_FRAME_SIZE, &ulReceiveCount ); if( ( ulResult != GMAC_OK ) || ( ulReceiveCount == 0 ) ) { /* No data from the hardware. */ break; } if( pxNextNetworkBufferDescriptor == NULL ) { /* Data was read from the hardware, but no descriptor was available for it, so it will be dropped. */ iptraceETHERNET_RX_EVENT_LOST(); continue; } iptraceNETWORK_INTERFACE_RECEIVE(); pxNextNetworkBufferDescriptor->xDataLength = ( size_t ) ulReceiveCount; xRxEvent.pvData = ( void * ) pxNextNetworkBufferDescriptor; /* Send the descriptor to the IP task for processing. */ if( xSendEventStructToIPTask( &xRxEvent, xBlockTime ) != pdTRUE ) { /* The buffer could not be sent to the stack so must be released again. */ vReleaseNetworkBufferAndDescriptor( pxNextNetworkBufferDescriptor ); iptraceETHERNET_RX_EVENT_LOST(); FreeRTOS_printf( ( "prvEMACRxPoll: Can not queue return packet!\n" ) ); } /* Now the buffer has either been passed to the IP-task, or it has been released in the code above. */ pxNextNetworkBufferDescriptor = NULL; ulReturnValue++; } return ulReturnValue; } /*-----------------------------------------------------------*/ void vCheckBuffersAndQueue( void ) { static UBaseType_t uxLastMinBufferCount = 0; #if( ipconfigCHECK_IP_QUEUE_SPACE != 0 ) static UBaseType_t uxLastMinQueueSpace; #endif static UBaseType_t uxCurrentCount; #if( ipconfigCHECK_IP_QUEUE_SPACE != 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 */ 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 ) ); } } static void prvEMACHandlerTask( void *pvParameters ) { TimeOut_t xPhyTime; TickType_t xPhyRemTime; UBaseType_t uxCount; #if( ipconfigZERO_COPY_TX_DRIVER != 0 ) NetworkBufferDescriptor_t *pxBuffer; #endif uint8_t *pucBuffer; BaseType_t xResult = 0; uint32_t xStatus; const TickType_t ulMaxBlockTime = pdMS_TO_TICKS( EMAC_MAX_BLOCK_TIME_MS ); /* Remove compiler warnings about unused parameters. */ ( void ) pvParameters; configASSERT( xEMACTaskHandle ); vTaskSetTimeOutState( &xPhyTime ); xPhyRemTime = pdMS_TO_TICKS( PHY_LS_LOW_CHECK_TIME_MS ); for( ;; ) { vCheckBuffersAndQueue(); if( ( ulISREvents & EMAC_IF_ALL_EVENT ) == 0 ) { /* No events to process now, wait for the next. */ ulTaskNotifyTake( pdFALSE, ulMaxBlockTime ); } if( ( ulISREvents & EMAC_IF_RX_EVENT ) != 0 ) { ulISREvents &= ~EMAC_IF_RX_EVENT; /* Wait for the EMAC interrupt to indicate that another packet has been received. */ xResult = prvEMACRxPoll(); } if( ( ulISREvents & EMAC_IF_TX_EVENT ) != 0 ) { /* Future extension: code to release TX buffers if zero-copy is used. */ ulISREvents &= ~EMAC_IF_TX_EVENT; while( xQueueReceive( xTxBufferQueue, &pucBuffer, 0 ) != pdFALSE ) { #if( ipconfigZERO_COPY_TX_DRIVER != 0 ) { pxBuffer = pxPacketBuffer_to_NetworkBuffer( pucBuffer ); if( pxBuffer != NULL ) { vReleaseNetworkBufferAndDescriptor( pxBuffer ); tx_release_count[ 0 ]++; } else { tx_release_count[ 1 ]++; } } #else { tx_release_count[ 0 ]++; } #endif uxCount = uxQueueMessagesWaiting( ( QueueHandle_t ) xTXDescriptorSemaphore ); if( uxCount < GMAC_TX_BUFFERS ) { /* Tell the counting semaphore that one more TX descriptor is available. */ xSemaphoreGive( xTXDescriptorSemaphore ); } } } if( ( ulISREvents & EMAC_IF_ERR_EVENT ) != 0 ) { /* Future extension: logging about errors that occurred. */ ulISREvents &= ~EMAC_IF_ERR_EVENT; } 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 ) { /* Check the link status again. */ xStatus = ulReadMDIO( PHY_REG_01_BMSR ); if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != ( xStatus & BMSR_LINK_STATUS ) ) { ulPHYLinkStatus = xStatus; FreeRTOS_printf( ( "prvEMACHandlerTask: PHY LS now %d\n", ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 ) ); } vTaskSetTimeOutState( &xPhyTime ); if( ( ulPHYLinkStatus & BMSR_LINK_STATUS ) != 0 ) { xPhyRemTime = pdMS_TO_TICKS( PHY_LS_HIGH_CHECK_TIME_MS ); } else { xPhyRemTime = pdMS_TO_TICKS( PHY_LS_LOW_CHECK_TIME_MS ); } } } } /*-----------------------------------------------------------*/