FreeRTOS+TCP Adding the combined driver for SAM4E and SAME70 v2 (#78)

* Adding a combined +TCP driver for SAM4E and SAME70

* Changes after review from Aniruddha

Co-authored-by: Hein Tibosch <hein@htibosch.net>
Co-authored-by: Aniruddha Kanhere <60444055+AniruddhaKanhere@users.noreply.github.com>
This commit is contained in:
Hein Tibosch
2020-07-14 05:35:44 +08:00
committed by GitHub
parent 4237049b12
commit c720c18ada
3 changed files with 3270 additions and 0 deletions

View File

@ -0,0 +1,928 @@
/*
FreeRTOS+TCP V2.2.1
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 "FreeRTOS_ARP.h"
#include "NetworkBufferManagement.h"
#include "NetworkInterface.h"
/* Some files from the Atmel Software Framework */
/* gmac_SAM.[ch] is a combination of the gmac.[ch] for both SAM4E and SAME70. */
#include "gmac_SAM.h"
#include <sysclk.h>
#include "phyhandling.h"
/* This file is included to see if 'CONF_BOARD_ENABLE_CACHE' is defined. */
#include "conf_board.h"
/* 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 )
/* 1536 bytes is more than needed, 1524 would be enough.
But 1536 is a multiple of 32, which gives a great alignment for
cached memories. */
#define NETWORK_BUFFER_SIZE 1536
#ifndef EMAC_MAX_BLOCK_TIME_MS
/* The task 'prvEMACHandlerTask()' will wake-up every 100 ms, to see
if something has to be done, mostly checking if the PHY has a
change in Link Status. */
#define EMAC_MAX_BLOCK_TIME_MS 100ul
#endif
#if( ipconfigZERO_COPY_RX_DRIVER == 0 )
#error This driver works optimal if ipconfigZERO_COPY_RX_DRIVER is defined as 1
#endif
#if( ipconfigZERO_COPY_TX_DRIVER == 0 )
#error This driver works optimal if ipconfigZERO_COPY_TX_DRIVER is defined as 1
#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
#ifndef niEMAC_HANDLER_TASK_PRIORITY
#define niEMAC_HANDLER_TASK_PRIORITY configMAX_PRIORITIES - 1
#endif
#if( __DCACHE_PRESENT != 0 ) && defined( CONF_BOARD_ENABLE_CACHE )
#include "core_cm7.h"
#warning This driver assumes the presence of DCACHE
#define NETWORK_BUFFERS_CACHED 1
#define CACHE_LINE_SIZE 32
#define NETWORK_BUFFER_HEADER_SIZE ( ipconfigPACKET_FILLER_SIZE + 8 )
static void cache_clean_invalidate()
{
/* If you application crashes here, make sure that SCB_EnableDCache() has been called. */
SCB_CleanInvalidateDCache();
}
/*-----------------------------------------------------------*/
static void cache_clean_invalidate_by_addr(uint32_t addr, uint32_t size)
{
/* SAME70 does not have clean/invalidate per area. */
/* SCB_CleanInvalidateDCache_by_Addr( ( uint32_t * )addr, size); */
SCB_CleanInvalidateDCache();
}
/*-----------------------------------------------------------*/
static void cache_invalidate_by_addr(addr, size) \
{
/* SAME70 does not have clean/invalidate per area. */
/* SCB_InvalidateDCache_by_Addr( ( uint32_t * )addr, size); */
SCB_InvalidateDCache();
}
/*-----------------------------------------------------------*/
#else
#warning Sure there is no caching?
#define cache_clean_invalidate() do {} while( 0 )
#define cache_clean_invalidate_by_addr(addr, size) do {} while( 0 )
#define cache_invalidate_by_addr(addr, size) do {} while( 0 )
#endif
/*-----------------------------------------------------------*/
/*
* Update settings in GMAC for speed and duplex.
*/
static void prvEthernetUpdateConfig( BaseType_t xForce );
/*
* Access functions to the PHY's: read() and write() to be used by
* phyHandling.c.
*/
static BaseType_t xPHY_Read( BaseType_t xAddress, BaseType_t xRegister, uint32_t *pulValue );
static BaseType_t xPHY_Write( BaseType_t xAddress, BaseType_t xRegister, uint32_t ulValue );
#if( ipconfigDRIVER_INCLUDED_TX_IP_CHECKSUM == 1 ) && ( ipconfigHAS_TX_CRC_OFFLOADING == 0 )
void vGMACGenerateChecksum( uint8_t *apBuffer, size_t uxLength );
#endif
/*
* Called from the ASF GMAC driver.
*/
void xRxCallback( uint32_t ulStatus );
void xTxCallback( 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 );
/*
* Handle transmission errors.
*/
static void hand_tx_errors( 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 volatile BaseType_t xGMACSwitchRequired;
/* 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;
/* For local use only: describe the PHY's properties: */
const PhyProperties_t xPHYProperties =
{
#if( ipconfigETHERNET_AN_ENABLE != 0 )
.ucSpeed = PHY_SPEED_AUTO,
.ucDuplex = PHY_DUPLEX_AUTO,
#else
#if( ipconfigETHERNET_USE_100MB != 0 )
.ucSpeed = PHY_SPEED_100,
#else
.ucSpeed = PHY_SPEED_10,
#endif
#if( ipconfigETHERNET_USE_FULL_DUPLEX != 0 )
.ucDuplex = PHY_DUPLEX_FULL,
#else
.ucDuplex = PHY_DUPLEX_HALF,
#endif
#endif
#if( ipconfigETHERNET_AN_ENABLE != 0 ) && ( ipconfigETHERNET_AUTO_CROSS_ENABLE != 0 )
.ucMDI_X = PHY_MDIX_AUTO,
#elif( ipconfigETHERNET_CROSSED_LINK != 0 )
.ucMDI_X = PHY_MDIX_CROSSED,
#else
.ucMDI_X = PHY_MDIX_DIRECT,
#endif
};
/* All PHY handling code has now been separated from the NetworkInterface.c,
see "../Common/phyHandling.c". */
static EthernetPhy_t xPhyObject;
/*-----------------------------------------------------------*/
/*
* GMAC interrupt handler.
*/
void GMAC_Handler(void)
{
xGMACSwitchRequired = pdFALSE;
/* gmac_handler() may call xRxCallback() which may change
the value of xGMACSwitchRequired. */
gmac_handler( &gs_gmac_dev );
if( xGMACSwitchRequired != pdFALSE )
{
portEND_SWITCHING_ISR( xGMACSwitchRequired );
}
}
/*-----------------------------------------------------------*/
void xRxCallback( 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 );
}
}
/*-----------------------------------------------------------*/
void returnTxBuffer(uint8_t *puc_buffer)
{
/* Called from a non-ISR context. */
if( xTxBufferQueue != NULL )
{
xQueueSend( xTxBufferQueue, &puc_buffer, 0 );
xTaskNotifyGive( xEMACTaskHandle );
ulISREvents |= EMAC_IF_TX_EVENT;
}
}
void xTxCallback( uint32_t ulStatus, uint8_t *puc_buffer )
{
if( ( xTxBufferQueue != NULL ) && ( xEMACTaskHandle != NULL ) )
{
/* let the prvEMACHandlerTask know that there was an TX event. */
ulISREvents |= EMAC_IF_TX_EVENT;
/* Wakeup prvEMACHandlerTask. */
vTaskNotifyGiveFromISR( xEMACTaskHandle, ( BaseType_t * ) &xGMACSwitchRequired );
xQueueSendFromISR( xTxBufferQueue, &puc_buffer, ( BaseType_t * ) &xGMACSwitchRequired );
tx_release_count[ 2 ]++;
}
}
/*-----------------------------------------------------------*/
/*
The two standard defines 'GMAC_MAN_RW_TYPE' and 'GMAC_MAN_READ_ONLY'
are incorrect.
Therefore, use the following:
*/
#define GMAC_MAINTENANCE_READ_ACCESS (2)
#define GMAC_MAINTENANCE_WRITE_ACCESS (1)
static BaseType_t xPHY_Read( BaseType_t xAddress, BaseType_t xRegister, uint32_t *pulValue )
{
BaseType_t xReturn;
UBaseType_t uxWasEnabled;
/* Wait until bus idle */
while ((GMAC->GMAC_NSR & GMAC_NSR_IDLE) == 0);
/* Write maintain register */
/*
* OP: Operation: 10 is read. 01 is write.
*/
uxWasEnabled = ( GMAC->GMAC_NCR & GMAC_NCR_MPE ) != 0u;
if( uxWasEnabled == 0u )
{
/* Enable further GMAC maintenance. */
GMAC->GMAC_NCR |= GMAC_NCR_MPE;
}
GMAC->GMAC_MAN = GMAC_MAN_WTN(GMAC_MAN_CODE_VALUE)
| GMAC_MAN_CLTTO
| GMAC_MAN_PHYA(xAddress)
| GMAC_MAN_REGA(xRegister)
| GMAC_MAN_OP(GMAC_MAINTENANCE_READ_ACCESS)
| GMAC_MAN_DATA( (uint16_t)0u );
if (gmac_wait_phy(GMAC, MAC_PHY_RETRY_MAX) == GMAC_TIMEOUT)
{
*pulValue = (uint32_t)0xffffu;
xReturn = -1;
}
else
{
/* Wait until bus idle */
while ((GMAC->GMAC_NSR & GMAC_NSR_IDLE) == 0);
/* Return data */
*pulValue = (uint32_t)(GMAC->GMAC_MAN & GMAC_MAN_DATA_Msk);
xReturn = 0;
}
if( uxWasEnabled == 0u )
{
/* Disable further GMAC maintenance. */
GMAC->GMAC_NCR &= ~GMAC_NCR_MPE;
}
return xReturn;
}
/*-----------------------------------------------------------*/
static BaseType_t xPHY_Write( BaseType_t xAddress, BaseType_t xRegister, uint32_t ulValue )
{
BaseType_t xReturn;
UBaseType_t uxWasEnabled;
/* Wait until bus idle */
while ((GMAC->GMAC_NSR & GMAC_NSR_IDLE) == 0);
/* Write maintain register */
uxWasEnabled = ( GMAC->GMAC_NCR & GMAC_NCR_MPE ) != 0u;
if( uxWasEnabled == 0u )
{
/* Enable further GMAC maintenance. */
GMAC->GMAC_NCR |= GMAC_NCR_MPE;
}
GMAC->GMAC_MAN = GMAC_MAN_WTN(GMAC_MAN_CODE_VALUE)
| GMAC_MAN_CLTTO
| GMAC_MAN_PHYA(xAddress)
| GMAC_MAN_REGA(xRegister)
| GMAC_MAN_OP(GMAC_MAINTENANCE_WRITE_ACCESS)
| GMAC_MAN_DATA( (uint16_t)ulValue );
if (gmac_wait_phy(GMAC, MAC_PHY_RETRY_MAX) == GMAC_TIMEOUT )
{
xReturn = -1;
}
else
{
xReturn = 0;
}
if( uxWasEnabled == 0u )
{
/* Disable further GMAC maintenance. */
GMAC->GMAC_NCR &= ~GMAC_NCR_MPE;
}
return xReturn;
}
/*-----------------------------------------------------------*/
BaseType_t xNetworkInterfaceInitialise( void )
{
const TickType_t x5_Seconds = 5000UL;
if( xEMACTaskHandle == NULL )
{
prvGMACInit();
cache_clean_invalidate();
/* 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, niEMAC_HANDLER_TASK_PRIORITY, &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 xGetPhyLinkStatus();
}
/*-----------------------------------------------------------*/
BaseType_t xGetPhyLinkStatus( void )
{
BaseType_t xReturn;
if( xPhyObject.ulLinkStatusMask != 0 )
{
xReturn = pdPASS;
}
else
{
xReturn = pdFAIL;
}
return xReturn;
}
/*-----------------------------------------------------------*/
/** The GMAC TX errors to handle */
#define GMAC_TX_ERRORS (GMAC_TSR_TFC | GMAC_TSR_HRESP)
static void hand_tx_errors( void )
{
/* Handle GMAC underrun or AHB errors. */
if (gmac_get_tx_status(GMAC) & GMAC_TX_ERRORS) {
gmac_enable_transmit(GMAC, false);
/* Reinit TX descriptors. */
// gmac_tx_init(ps_gmac_dev);
gmac_reset_tx_mem(&gs_gmac_dev);
/* Clear error status. */
gmac_clear_tx_status(GMAC, GMAC_TX_ERRORS);
gmac_enable_transmit(GMAC, true);
}
}
volatile IPPacket_t *pxSendPacket;
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 );
uint32_t ulTransmitSize;
ulTransmitSize = pxDescriptor->xDataLength;
pxSendPacket = (IPPacket_t *)pxDescriptor->pucEthernetBuffer;
if( ulTransmitSize > NETWORK_BUFFER_SIZE )
{
ulTransmitSize = NETWORK_BUFFER_SIZE;
}
/* A do{}while(0) loop is introduced to allow the use of multiple break
statement. */
do {
if( xPhyObject.ulLinkStatusMask == 0ul )
{
/* 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;
}
hand_tx_errors();
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 */
#if( NETWORK_BUFFERS_CACHED != 0 )
{
uint32_t xlength = CACHE_LINE_SIZE * ( ( ulTransmitSize + NETWORK_BUFFER_HEADER_SIZE + CACHE_LINE_SIZE - 1 ) / CACHE_LINE_SIZE );
uint32_t xAddress = ( uint32_t )( pxDescriptor->pucEthernetBuffer - NETWORK_BUFFER_HEADER_SIZE );
cache_clean_invalidate_by_addr( xAddress, xlength );
}
#endif
gmac_dev_write( &gs_gmac_dev, (void *)pxDescriptor->pucEthernetBuffer, pxDescriptor->xDataLength );
#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( ipFALSE_BOOL );
if( bReleaseAfterSend != pdFALSE )
{
vReleaseNetworkBufferAndDescriptor( pxDescriptor );
}
return pdTRUE;
}
/*-----------------------------------------------------------*/
static BaseType_t prvGMACInit( void )
{
uint32_t ncfgr;
gmac_options_t gmac_option;
gmac_enable_management(GMAC, true);
/* Enable further GMAC maintenance. */
GMAC->GMAC_NCR |= GMAC_NCR_MPE;
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 );
{
/* Set MDC clock divider. */
gmac_set_mdc_clock( GMAC, sysclk_get_cpu_hz() );
vPhyInitialise( &xPhyObject, xPHY_Read, xPHY_Write );
xPhyDiscover( &xPhyObject );
xPhyConfigure( &xPhyObject, &xPHYProperties );
/* For a reset / reconfigure of the EMAC. */
prvEthernetUpdateConfig( pdTRUE );
/* Select Media Independent Interface type */
#if( SAME70 != 0 )
{
/* Selecting RMII mode. */
GMAC->GMAC_UR &= ~GMAC_UR_RMII;
}
#else
{
gmac_select_mii_mode(GMAC, ETH_PHY_MODE);
}
#endif
gmac_enable_transmit(GMAC, true);
gmac_enable_receive(GMAC, true);
}
gmac_enable_management(GMAC, true);
gmac_set_address( GMAC, 1, (uint8_t*)llmnr_mac_address );
gmac_enable_management(GMAC, false);
/* Disable further GMAC maintenance. */
GMAC->GMAC_NCR &= ~GMAC_NCR_MPE;
return 1;
}
/*-----------------------------------------------------------*/
static void prvEthernetUpdateConfig( BaseType_t xForce )
{
FreeRTOS_printf( ( "prvEthernetUpdateConfig: LS mask %02lX Force %d\n",
xPhyObject.ulLinkStatusMask,
( int )xForce ) );
if( ( xForce != pdFALSE ) || ( xPhyObject.ulLinkStatusMask != 0 ) )
{
#if( ipconfigETHERNET_AN_ENABLE != 0 )
{
UBaseType_t uxWasEnabled;
/* Restart the auto-negotiation. */
uxWasEnabled = ( GMAC->GMAC_NCR & GMAC_NCR_MPE ) != 0u;
if( uxWasEnabled == 0u )
{
/* Enable further GMAC maintenance. */
GMAC->GMAC_NCR |= GMAC_NCR_MPE;
}
xPhyStartAutoNegotiation( &xPhyObject, xPhyGetMask( &xPhyObject ) );
/* Configure the MAC with the Duplex Mode fixed by the
auto-negotiation process. */
if( xPhyObject.xPhyProperties.ucDuplex == PHY_DUPLEX_FULL )
{
gmac_enable_full_duplex(GMAC, pdTRUE);
}
else
{
gmac_enable_full_duplex(GMAC, pdFALSE);
}
/* Configure the MAC with the speed fixed by the
auto-negotiation process. */
if( xPhyObject.xPhyProperties.ucSpeed == PHY_SPEED_10 )
{
gmac_set_speed(GMAC, pdFALSE);
}
else
{
gmac_set_speed(GMAC, pdTRUE);
}
if( uxWasEnabled == 0u )
{
/* Enable further GMAC maintenance. */
GMAC->GMAC_NCR &= ~GMAC_NCR_MPE;
}
}
#else
{
if( xPHYProperties.ucDuplex == PHY_DUPLEX_FULL )
{
xPhyObject.xPhyPreferences.ucDuplex = PHY_DUPLEX_FULL;
gmac_enable_full_duplex(GMAC, pdTRUE);
}
else
{
xPhyObject.xPhyPreferences.ucDuplex = PHY_DUPLEX_HALF;
gmac_enable_full_duplex(GMAC, pdFALSE);
}
if( xPHYProperties.ucSpeed == PHY_SPEED_100 )
{
xPhyObject.xPhyPreferences.ucSpeed = PHY_SPEED_100;
gmac_set_speed(GMAC, pdTRUE);
}
else
{
xPhyObject.xPhyPreferences.ucSpeed = PHY_SPEED_10;
gmac_set_speed(GMAC, pdFALSE);
}
xPhyObject.xPhyPreferences.ucMDI_X = PHY_MDIX_AUTO;
/* Use predefined (fixed) configuration. */
xPhyFixedValue( &xPhyObject, xPhyGetMask( &xPhyObject ) );
}
#endif
}
}
/*-----------------------------------------------------------*/
void vGMACGenerateChecksum( uint8_t *pucBuffer, size_t uxLength )
{
ProtocolPacket_t *xProtPacket = ( ProtocolPacket_t * ) pucBuffer;
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( pucBuffer, uxLength, pdTRUE );
}
}
/*-----------------------------------------------------------*/
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 };
uint8_t *pucDMABuffer = 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, &pucDMABuffer );
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();
#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
{
pxNextNetworkBufferDescriptor = pxPacketBuffer_to_NetworkBuffer( pucDMABuffer );
if( pxNextNetworkBufferDescriptor == NULL )
{
/* STrange: can not translate from a DMA buffer to a Network Buffer. */
break;
}
}
#endif /* ipconfigZERO_COPY_RX_DRIVER */
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;
}
/*-----------------------------------------------------------*/
volatile UBaseType_t uxLastMinBufferCount = 0;
#if( ipconfigCHECK_IP_QUEUE_SPACE != 0 )
volatile UBaseType_t uxLastMinQueueSpace;
#endif
volatile UBaseType_t uxCurrentSemCount;
volatile UBaseType_t uxLowestSemCount;
void vCheckBuffersAndQueue( void )
{
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 ) );
}
if( xTXDescriptorSemaphore != NULL )
{
uxCurrentSemCount = uxSemaphoreGetCount( xTXDescriptorSemaphore );
if( uxLowestSemCount > uxCurrentSemCount )
{
uxLowestSemCount = uxCurrentSemCount;
FreeRTOS_printf( ( "TX DMA buffers: lowest %lu\n", uxLowestSemCount ) );
}
}
}
/*-----------------------------------------------------------*/
extern uint8_t ucNetworkPackets[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS * NETWORK_BUFFER_SIZE ];
void vNetworkInterfaceAllocateRAMToBuffers( NetworkBufferDescriptor_t pxNetworkBuffers[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS ] )
{
uint8_t *ucRAMBuffer = ucNetworkPackets;
uint32_t ulIndex;
for( ulIndex = 0; ulIndex < ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS; ulIndex++ )
{
pxNetworkBuffers[ ulIndex ].pucEthernetBuffer = ucRAMBuffer + ipBUFFER_PADDING;
*( ( unsigned * ) ucRAMBuffer ) = ( unsigned ) ( &( pxNetworkBuffers[ ulIndex ] ) );
ucRAMBuffer += NETWORK_BUFFER_SIZE;
}
cache_clean_invalidate();
}
/*-----------------------------------------------------------*/
static void prvEMACHandlerTask( void *pvParameters )
{
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 );
for( ;; )
{
xResult = 0;
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;
}
gmac_enable_management(GMAC, true);
if( xPhyCheckLinkStatus( &xPhyObject, xResult ) != 0 )
{
/* Something has changed to a Link Status, need re-check. */
prvEthernetUpdateConfig( pdFALSE );
}
gmac_enable_management(GMAC, false);
}
}
/*-----------------------------------------------------------*/

View File

@ -0,0 +1,924 @@
/**
* \file
*
* \brief GMAC (Ethernet MAC) driver for SAM.
*
* Copyright (c) 2015-2016 Atmel Corporation. All rights reserved.
*
* \asf_license_start
*
* \page License
*
* 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. The name of Atmel may not be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* 4. This software may only be redistributed and used in connection with an
* Atmel microcontroller product.
*
* THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* EXPRESSLY AND SPECIFICALLY DISCLAIMED. IN NO EVENT SHALL ATMEL 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.
*
* \asf_license_stop
*
*/
/*
* Support and FAQ: visit <a href="http://www.atmel.com/design-support/">Atmel Support</a>
*/
/* Standard includes. */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "semphr.h"
#include "FreeRTOSIPConfig.h"
/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "FreeRTOS_IP_Private.h"
#include "FreeRTOS_ARP.h"
#include "NetworkBufferManagement.h"
#include "NetworkInterface.h"
#include "compiler.h"
#include "gmac_SAM.h"
#if( SAME70 != 0 )
/* This file is included to see if 'CONF_BOARD_ENABLE_CACHE' is defined. */
#include "conf_board.h"
#include "core_cm7.h"
#endif
/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
extern "C" {
#endif
/**INDENT-ON**/
/// @endcond
#ifndef ARRAY_SIZE
#define ARRAY_SIZE(x) (int)( sizeof(x) / sizeof(x)[0] )
#endif
#if( GMAC_RX_BUFFERS <= 1 )
#error Configuration error
#endif
#if( GMAC_TX_BUFFERS <= 1 )
#error Configuration error
#endif
/**
* \defgroup gmac_group Ethernet Media Access Controller
*
* See \ref gmac_quickstart.
*
* Driver for the GMAC (Ethernet Media Access Controller).
* This file contains basic functions for the GMAC, with support for all modes, settings
* and clock speeds.
*
* \section dependencies Dependencies
* This driver does not depend on other modules.
*
* @{
*/
#define NETWORK_BUFFER_SIZE 1536
__attribute__ ( ( aligned( 32 ) ) )
__attribute__ ( ( section( ".first_data" ) ) )
uint8_t ucNetworkPackets[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS * NETWORK_BUFFER_SIZE ];
/** TX descriptor lists */
__attribute__ ((section(".first_data")))
COMPILER_ALIGNED(8)
static gmac_tx_descriptor_t gs_tx_desc[ GMAC_TX_BUFFERS ];
#if( SAME70 != 0 )
__attribute__ ((section(".first_data")))
COMPILER_ALIGNED(8)
static gmac_tx_descriptor_t gs_tx_desc_null;
#endif
/** RX descriptors lists */
__attribute__ ((section(".first_data")))
COMPILER_ALIGNED(8)
static gmac_rx_descriptor_t gs_rx_desc[ GMAC_RX_BUFFERS ];
#if( ipconfigZERO_COPY_TX_DRIVER == 0 )
/** Send Buffer. Section 3.6 of AMBA 2.0 spec states that burst should not cross the
* 1K Boundaries. Receive buffer manager write operations are burst of 2 words => 3 lsb bits
* of the address shall be set to 0.
*/
__attribute__ ((section(".first_data")))
COMPILER_ALIGNED(8)
static uint8_t gs_uc_tx_buffer[ GMAC_TX_BUFFERS * GMAC_TX_UNITSIZE ];
#endif /* ipconfigZERO_COPY_TX_DRIVER */
#if( ipconfigZERO_COPY_RX_DRIVER == 0 )
/** Receive Buffer */
__attribute__ ((section(".first_data")))
COMPILER_ALIGNED(8)
static uint8_t gs_uc_rx_buffer[ GMAC_RX_BUFFERS * GMAC_RX_UNITSIZE ];
#endif /* ipconfigZERO_COPY_RX_DRIVER */
/** Return count in buffer */
#define CIRC_CNT( head, tail, size ) ( ( ( head ) - ( tail ) ) % ( size ) )
/*
* Return space available, from 0 to size-1.
* Always leave one free char as a completely full buffer that has (head == tail),
* which is the same as empty.
*/
#define CIRC_SPACE( head, tail, size ) CIRC_CNT( ( tail ), ( ( head ) + 1 ), ( size ) )
/** Circular buffer is empty ? */
#define CIRC_EMPTY( head, tail ) ( ( head ) == ( tail ) )
/** Clear circular buffer */
#define CIRC_CLEAR( head, tail ) do { ( head ) = 0; ( tail ) = 0; } while( 0 )
/* Two call-back functions that should be defined in NetworkInterface.c */
extern void xRxCallback( uint32_t ulStatus );
extern void xTxCallback( uint32_t ulStatus, uint8_t *puc_buffer );
extern void returnTxBuffer(uint8_t *puc_buffer);
/** Increment head or tail */
static __inline void circ_inc32( int32_t *lHeadOrTail, uint32_t ulSize )
{
( *lHeadOrTail ) ++;
if( ( *lHeadOrTail ) >= ( int32_t )ulSize )
{
( *lHeadOrTail ) = 0;
}
}
/**
* \brief Wait PHY operation to be completed.
*
* \param p_gmac HW controller address.
* \param ul_retry The retry times, 0 to wait forever until completeness.
*
* Return GMAC_OK if the operation is completed successfully.
*/
uint8_t gmac_wait_phy(Gmac* p_gmac, const uint32_t ul_retry)
{
volatile uint32_t ul_retry_count = 0;
const uint32_t xPHYPollDelay = pdMS_TO_TICKS( 1ul );
while (!gmac_is_phy_idle(p_gmac)) {
if (ul_retry == 0) {
continue;
}
ul_retry_count++;
if (ul_retry_count >= ul_retry) {
return GMAC_TIMEOUT;
}
/* Block the task to allow other tasks to execute while the PHY
is not connected. */
vTaskDelay( xPHYPollDelay );
}
return GMAC_OK;
}
/**
* \brief Disable transfer, reset registers and descriptor lists.
*
* \param p_dev Pointer to GMAC driver instance.
*
*/
void gmac_reset_tx_mem(gmac_device_t* p_dev)
{
Gmac *p_hw = p_dev->p_hw;
uint32_t ul_index;
uint32_t ul_address;
/* Disable TX */
gmac_enable_transmit(p_hw, 0);
{
for( ul_index = 0; ul_index < ARRAY_SIZE(gs_tx_desc); ul_index++ )
{
uint32_t ulAddr = gs_tx_desc[ul_index].addr;
if (ulAddr) {
returnTxBuffer ((uint8_t *)ulAddr);
}
}
}
/* Set up the TX descriptors */
CIRC_CLEAR(p_dev->l_tx_head, p_dev->l_tx_tail);
for( ul_index = 0; ul_index < GMAC_TX_BUFFERS; ul_index++ )
{
#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
ul_address = (uint32_t) 0u;
}
#else
{
ul_address = (uint32_t) (&(gs_uc_tx_buffer[ul_index * GMAC_TX_UNITSIZE]));
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
gs_tx_desc[ul_index].addr = ul_address;
gs_tx_desc[ul_index].status.val = GMAC_TXD_USED;
}
/* Set the WRAP bit in the last descriptor. */
gs_tx_desc[GMAC_TX_BUFFERS - 1].status.val = GMAC_TXD_USED | GMAC_TXD_WRAP;
/* Set transmit buffer queue */
gmac_set_tx_queue(p_hw, (uint32_t) gs_tx_desc);
#if( SAME70 != 0 )
{
gmac_set_tx_priority_queue(p_hw, (uint32_t)&gs_tx_desc_null, GMAC_QUE_1);
gmac_set_tx_priority_queue(p_hw, (uint32_t)&gs_tx_desc_null, GMAC_QUE_2);
/* Note that SAME70 REV B had 6 priority queues. */
gmac_set_tx_priority_queue(p_hw, (uint32_t)&gs_tx_desc_null, GMAC_QUE_3);
gmac_set_tx_priority_queue(p_hw, (uint32_t)&gs_tx_desc_null, GMAC_QUE_4);
gmac_set_tx_priority_queue(p_hw, (uint32_t)&gs_tx_desc_null, GMAC_QUE_5);
}
#endif
}
/**
* \brief Disable receiver, reset registers and descriptor list.
*
* \param p_dev Pointer to GMAC Driver instance.
*/
static void gmac_reset_rx_mem(gmac_device_t* p_dev)
{
Gmac *p_hw = p_dev->p_hw;
uint32_t ul_index;
uint32_t ul_address;
/* Disable RX */
gmac_enable_receive(p_hw, 0);
/* Set up the RX descriptors */
p_dev->ul_rx_idx = 0;
for( ul_index = 0; ul_index < GMAC_RX_BUFFERS; ul_index++ )
{
#if( ipconfigZERO_COPY_RX_DRIVER != 0 )
{
NetworkBufferDescriptor_t *pxNextNetworkBufferDescriptor;
pxNextNetworkBufferDescriptor = pxGetNetworkBufferWithDescriptor( GMAC_RX_UNITSIZE, 0ul );
configASSERT( pxNextNetworkBufferDescriptor != NULL );
ul_address = ( uint32_t )( pxNextNetworkBufferDescriptor->pucEthernetBuffer );
}
#else
{
ul_address = ( uint32_t ) ( &( gs_uc_rx_buffer[ ul_index * GMAC_RX_UNITSIZE ] ) );
}
#endif /* ipconfigZERO_COPY_RX_DRIVER */
gs_rx_desc[ul_index].addr.val = ul_address & GMAC_RXD_ADDR_MASK;
gs_rx_desc[ul_index].status.val = 0;
}
/* Set the WRAP bit in the last descriptor. */
gs_rx_desc[GMAC_RX_BUFFERS - 1].addr.bm.b_wrap = 1;
/* Set receive buffer queue */
gmac_set_rx_queue(p_hw, (uint32_t)gs_rx_desc);
}
/**
* \brief Initialize the allocated buffer lists for GMAC driver to transfer data.
* Must be invoked after gmac_dev_init() but before RX/TX starts.
*
* \note If input address is not 8-byte aligned, the address is automatically
* adjusted and the list size is reduced by one.
*
* \param p_gmac Pointer to GMAC instance.
* \param p_gmac_dev Pointer to GMAC device instance.
* \param p_dev_mm Pointer to the GMAC memory management control block.
*
* \return GMAC_OK or GMAC_PARAM.
*/
static uint8_t gmac_init_mem(Gmac* p_gmac, gmac_device_t* p_gmac_dev)
{
/* Assign TX buffers */
#if( ipconfigZERO_COPY_TX_DRIVER == 0 )
if ((((uint32_t) gs_uc_tx_buffer) & 0x7)
|| ((uint32_t) p_dev_mm->p_tx_dscr & 0x7)) {
p_dev_mm->ul_tx_size--;
}
p_gmac_dev->p_tx_buffer =
(uint8_t *) (((uint32_t) gs_uc_tx_buffer) & 0xFFFFFFF8);
#endif
/* Reset TX & RX Memory */
gmac_reset_rx_mem(p_gmac_dev);
gmac_reset_tx_mem(p_gmac_dev);
/* Enable Rx and Tx, plus the statistics register */
gmac_enable_transmit(p_gmac, true);
gmac_enable_receive(p_gmac, true);
gmac_enable_statistics_write(p_gmac, true);
/* Set up the interrupts for transmission and errors */
gmac_enable_interrupt(p_gmac,
GMAC_IER_RLEX | /* Enable retry limit exceeded interrupt. */
GMAC_IER_RXUBR | /* Enable receive used bit read interrupt. */
GMAC_IER_ROVR | /* Enable receive overrun interrupt. */
GMAC_IER_TCOMP | /* Enable transmit complete interrupt. */
GMAC_IER_TUR | /* Enable transmit underrun interrupt. */
GMAC_IER_TFC | /* Enable transmit buffers exhausted in mid-frame interrupt. */
GMAC_IER_HRESP | /* Enable Hresp not OK interrupt. */
GMAC_IER_PFNZ | /* Enable pause frame received interrupt. */
GMAC_IER_PTZ | /* Enable pause time zero interrupt. */
GMAC_IER_RCOMP); /* Enable receive complete interrupt. */
return GMAC_OK;
}
/**
* \brief Initialize the GMAC driver.
*
* \param p_gmac Pointer to the GMAC instance.
* \param p_gmac_dev Pointer to the GMAC device instance.
* \param p_opt GMAC configure options.
*/
void gmac_dev_init(Gmac* p_gmac, gmac_device_t* p_gmac_dev,
gmac_options_t* p_opt)
{
/* Disable TX & RX and more */
gmac_network_control(p_gmac, 0);
gmac_disable_interrupt(p_gmac, ~0u);
gmac_clear_statistics(p_gmac);
/* Clear all status bits in the receive status register. */
gmac_clear_rx_status(p_gmac, GMAC_RSR_RXOVR | GMAC_RSR_REC | GMAC_RSR_BNA
| GMAC_RSR_HNO);
#ifndef GMAC_TSR_UND
/* GMAC_TSR_UND is only defined by SAM4E. */
#define GMAC_TSR_UND 0ul
#endif
/* Clear all status bits in the transmit status register */
gmac_clear_tx_status(p_gmac, GMAC_TSR_UBR | GMAC_TSR_COL | GMAC_TSR_RLE
| GMAC_TSR_TFC | GMAC_TSR_TXCOMP | GMAC_TSR_UND);
/* Clear interrupts */
gmac_get_interrupt_status(p_gmac);
#if !defined(ETHERNET_CONF_DATA_OFFSET)
/* Receive Buffer Offset
* Indicates the number of bytes by which the received data
* is offset from the start of the receive buffer
* which can be handy for alignment reasons */
/* Note: FreeRTOS+TCP wants to have this offset set to 2 bytes */
#error ETHERNET_CONF_DATA_OFFSET not defined, assuming 0
#endif
/* Enable the copy of data into the buffers
ignore broadcasts, and not copy FCS. */
gmac_set_config(p_gmac,
( gmac_get_config(p_gmac) & ~GMAC_NCFGR_RXBUFO_Msk ) |
GMAC_NCFGR_RFCS | /* Remove FCS, frame check sequence (last 4 bytes) */
GMAC_NCFGR_PEN | /* Pause Enable */
GMAC_NCFGR_RXBUFO( ETHERNET_CONF_DATA_OFFSET ) | /* Set Ethernet Offset */
GMAC_RXD_RXCOEN ); /* RXCOEN related function */
/*
* GMAC_DCFGR_TXCOEN: (GMAC_DCFGR) Transmitter Checksum Generation Offload Enable.
* Note: SAM4E/SAME70 do have RX checksum offloading
* but TX checksum offloading has NOT been implemented,
* at least on a SAM4E.
* http://community.atmel.com/forum/sam4e-gmac-transmit-checksum-offload-enablesolved
*/
{
uint32_t ulValue = gmac_get_dma(p_gmac);
/* Let the GMAC set TX checksum's. */
ulValue |= GMAC_DCFGR_TXCOEN;
#if( SAME70 != 0 )
{
/* Transmitter Packet Buffer Memory Size Select:
Use full configured addressable space (4 Kbytes). */
ulValue |= GMAC_DCFGR_TXPBMS;
}
#endif
/* Clear the DMA Receive Buffer Size (DRBS) field: */
ulValue &= ~( GMAC_DCFGR_DRBS_Msk );
/* And set it: */
ulValue |= ( GMAC_RX_UNITSIZE / 64 ) << GMAC_DCFGR_DRBS_Pos;
gmac_set_dma(p_gmac, ulValue);
}
/* Enable/Disable Copy(Receive) All Valid Frames. */
gmac_enable_copy_all(p_gmac, p_opt->uc_copy_all_frame);
/* Disable/Enable broadcast receiving */
gmac_disable_broadcast(p_gmac, p_opt->uc_no_boardcast);
/* Initialize memory */
gmac_init_mem(p_gmac, p_gmac_dev);
/* Set Mac Address */
gmac_set_address(p_gmac, 0, p_opt->uc_mac_addr);
}
/**
* \brief Frames can be read from the GMAC in multiple sections.
*
* Returns > 0 if a complete frame is available
* It also it cleans up incomplete older frames
*/
static uint32_t gmac_dev_poll(gmac_device_t* p_gmac_dev)
{
uint32_t ulReturn = 0;
int32_t ulIndex = p_gmac_dev->ul_rx_idx;
gmac_rx_descriptor_t *pxHead = &gs_rx_desc[ulIndex];
// #warning Just for debugging
// if((pxHead->addr.val & GMAC_RXD_OWNERSHIP) != 0)
// {
// NVIC_DisableIRQ( GMAC_IRQn );
// }
#if( ipconfigZERO_COPY_RX_DRIVER == 0 )
{
/* Discard any incomplete frames */
while ((pxHead->addr.val & GMAC_RXD_OWNERSHIP) &&
(pxHead->status.val & GMAC_RXD_SOF) == 0)
{
pxHead->addr.val &= ~(GMAC_RXD_OWNERSHIP);
circ_inc32 (&ulIndex, GMAC_RX_BUFFERS);
pxHead = &gs_rx_desc[ulIndex];
p_gmac_dev->ul_rx_idx = ulIndex;
#if( GMAC_STATS != 0 )
{
gmacStats.incompCount++;
}
#endif
}
}
#endif /* ipconfigZERO_COPY_RX_DRIVER == 0 */
while ((pxHead->addr.val & GMAC_RXD_OWNERSHIP) != 0)
{
#if( ipconfigZERO_COPY_RX_DRIVER == 0 )
{
if ((pxHead->status.val & GMAC_RXD_EOF) != 0) {
/* Here a complete frame has been seen with SOF and EOF */
ulReturn = pxHead->status.bm.b_len;
break;
}
circ_inc32 (&ulIndex, GMAC_RX_BUFFERS);
pxHead = &gs_rx_desc[ulIndex];
if ((pxHead->addr.val & GMAC_RXD_OWNERSHIP) == 0) {
/* CPU is not the owner (yet) */
break;
}
if ((pxHead->status.val & GMAC_RXD_SOF) != 0) {
/* Strange, we found a new Start Of Frame
* discard previous segments */
int32_t ulPrev = p_gmac_dev->ul_rx_idx;
pxHead = &gs_rx_desc[ulPrev];
do {
pxHead->addr.val &= ~(GMAC_RXD_OWNERSHIP);
circ_inc32 (&ulPrev, GMAC_RX_BUFFERS);
pxHead = &gs_rx_desc[ulPrev];
#if( GMAC_STATS != 0 )
{
gmacStats.truncCount++;
}
#endif
} while (ulPrev != ulIndex);
p_gmac_dev->ul_rx_idx = ulIndex;
}
}
#else /* ipconfigZERO_COPY_RX_DRIVER */
{
if ((pxHead->status.val & (GMAC_RXD_SOF|GMAC_RXD_EOF)) == (GMAC_RXD_SOF|GMAC_RXD_EOF)) {
/* Here a complete frame in a single segment. */
ulReturn = pxHead->status.bm.b_len;
break;
}
/* Return the buffer to DMA. */
pxHead->addr.bm.b_ownership = 0;
/* Let ulIndex/pxHead point to the next buffer. */
circ_inc32 (&ulIndex, GMAC_RX_BUFFERS);
pxHead = &gs_rx_desc[ulIndex];
/* And remember this index. */
p_gmac_dev->ul_rx_idx = ulIndex;
}
#endif /* ipconfigZERO_COPY_RX_DRIVER */
}
return ulReturn;
}
/**
* \brief Frames can be read from the GMAC in multiple sections.
* Read ul_frame_size bytes from the GMAC receive buffers to pcTo.
* p_rcv_size is the size of the entire frame. Generally gmac_read
* will be repeatedly called until the sum of all the ul_frame_size equals
* the value of p_rcv_size.
*
* \param p_gmac_dev Pointer to the GMAC device instance.
* \param p_frame Address of the frame buffer.
* \param ul_frame_size Length of the frame.
* \param p_rcv_size Received frame size.
*
* \return GMAC_OK if receiving frame successfully, otherwise failed.
*/
uint32_t gmac_dev_read(gmac_device_t* p_gmac_dev, uint8_t* p_frame,
uint32_t ul_frame_size, uint32_t* p_rcv_size,
uint8_t** pp_recv_frame)
{
int32_t nextIdx; /* A copy of the Rx-index 'ul_rx_idx' */
int32_t bytesLeft = gmac_dev_poll (p_gmac_dev);
gmac_rx_descriptor_t *pxHead;
if (bytesLeft == 0 )
{
return GMAC_RX_NO_DATA;
}
/* gmac_dev_poll has confirmed that there is a complete frame at
* the current position 'ul_rx_idx'
*/
nextIdx = p_gmac_dev->ul_rx_idx;
/* Read +2 bytes because buffers are aligned at -2 bytes */
bytesLeft = min( bytesLeft + 2, ( int32_t )ul_frame_size );
#if( __DCACHE_PRESENT != 0 ) && defined( CONF_BOARD_ENABLE_CACHE )
SCB_InvalidateDCache();
#endif
#if( ipconfigZERO_COPY_RX_DRIVER == 0 )
{
/* The frame will be copied in 1 or 2 memcpy's */
if( ( p_frame != NULL ) && ( bytesLeft != 0 ) )
{
const uint8_t *source;
int32_t left;
int32_t toCopy;
source = gs_uc_rx_buffer + nextIdx * GMAC_RX_UNITSIZE;
left = bytesLeft;
toCopy = ( GMAC_RX_BUFFERS - nextIdx ) * GMAC_RX_UNITSIZE;
if(toCopy > left )
{
toCopy = left;
}
memcpy (p_frame, source, toCopy);
left -= toCopy;
if( left != 0ul )
{
memcpy (p_frame + toCopy, (void*)gs_uc_rx_buffer, left);
}
}
}
#else /* ipconfigZERO_COPY_RX_DRIVER */
{
if( p_frame != NULL )
{
/* Return a pointer to the earlier DMA buffer. */
*( pp_recv_frame ) = ( uint8_t * )
( ( ( gs_rx_desc[nextIdx].addr.val ) & ~( 0x03ul ) ) + 2 );
/* Set the new DMA-buffer. */
gs_rx_desc[nextIdx].addr.bm.addr_dw = ( ( uint32_t )p_frame ) / 4;
}
else
{
/* The driver couldn't not allocate a buffer to receive a packet.
Leave the current DMA buffer in place. */
}
}
#endif /* ipconfigZERO_COPY_RX_DRIVER */
do
{
pxHead = &gs_rx_desc[nextIdx];
pxHead->addr.val &= ~(GMAC_RXD_OWNERSHIP);
circ_inc32 (&nextIdx, GMAC_RX_BUFFERS);
} while ((pxHead->status.val & GMAC_RXD_EOF) == 0);
p_gmac_dev->ul_rx_idx = nextIdx;
*p_rcv_size = bytesLeft;
// #warning Just for debugging
// NVIC_EnableIRQ( GMAC_IRQn );
return GMAC_OK;
}
extern void vGMACGenerateChecksum( uint8_t *apBuffer, size_t uxLength );
/**
* \brief Send ulLength bytes from pcFrom. This copies the buffer to one of the
* GMAC Tx buffers, and then indicates to the GMAC that the buffer is ready.
* If lEndOfFrame is true then the data being copied is the end of the frame
* and the frame can be transmitted.
*
* \param p_gmac_dev Pointer to the GMAC device instance.
* \param p_buffer Pointer to the data buffer.
* \param ul_size Length of the frame.
*
* \return Length sent.
*/
uint32_t gmac_dev_write(gmac_device_t* p_gmac_dev, void *p_buffer,
uint32_t ul_size)
{
volatile gmac_tx_descriptor_t *p_tx_td;
Gmac *p_hw = p_gmac_dev->p_hw;
/* Check parameter */
if (ul_size > GMAC_TX_UNITSIZE) {
return GMAC_PARAM;
}
/* Pointers to the current transmit descriptor */
p_tx_td = &gs_tx_desc[p_gmac_dev->l_tx_head];
/* If no free TxTd, buffer can't be sent, schedule the wakeup callback */
if ((p_tx_td->status.val & GMAC_TXD_USED) == 0)
{
return GMAC_TX_BUSY;
}
/* Set up/copy data to transmission buffer */
if (p_buffer && ul_size) {
/* Driver manages the ring buffer */
/* Calculating the checksum here is faster than calculating it from the GMAC buffer
* because withing p_buffer, it is well aligned */
#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
/* Zero-copy... */
p_tx_td->addr = ( uint32_t ) p_buffer;
}
#else
{
/* Or memcopy... */
memcpy((void *)p_tx_td->addr, p_buffer, ul_size);
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
vGMACGenerateChecksum( ( uint8_t * ) p_tx_td->addr, ( size_t ) ul_size );
}
//#warning Trying out
gmac_start_transmission(p_hw);
/* Update transmit descriptor status */
/* The buffer size defined is the length of ethernet frame,
so it's always the last buffer of the frame. */
if( p_gmac_dev->l_tx_head == ( int32_t )( GMAC_TX_BUFFERS - 1 ) )
{
/* No need to 'and' with GMAC_TXD_LEN_MASK because ul_size has been checked
GMAC_TXD_USED will now be cleared. */
p_tx_td->status.val =
ul_size | GMAC_TXD_LAST | GMAC_TXD_WRAP;
} else {
/* GMAC_TXD_USED will now be cleared. */
p_tx_td->status.val =
ul_size | GMAC_TXD_LAST;
}
circ_inc32( &p_gmac_dev->l_tx_head, GMAC_TX_BUFFERS );
/* Now start to transmit if it is still not done */
gmac_start_transmission(p_hw);
return GMAC_OK;
}
/**
* \brief Get current load of transmit.
*
* \param p_gmac_dev Pointer to the GMAC device instance.
*
* \return Current load of transmit.
*/
uint32_t gmac_dev_get_tx_load(gmac_device_t* p_gmac_dev)
{
uint16_t us_head = p_gmac_dev->l_tx_head;
uint16_t us_tail = p_gmac_dev->l_tx_tail;
return CIRC_CNT(us_head, us_tail, GMAC_TX_BUFFERS);
}
/**
* \brief Register/Clear TX wakeup callback.
*
* When gmac_dev_write() returns GMAC_TX_BUSY (all transmit descriptor busy), the application
* task calls gmac_dev_set_tx_wakeup_callback() to register func_wakeup() callback and
* enters suspend state. The callback is in charge to resume the task once
* several transmit descriptors have been released. The next time gmac_dev_write() will be called,
* it shall be successful.
*
* This function is usually invoked with NULL callback from the TX wakeup
* callback itself, to unregister. Once the callback has resumed the
* application task, there is no need to invoke the callback again.
*
* \param p_gmac_dev Pointer to GMAC device instance.
* \param func_wakeup Pointer to wakeup callback function.
* \param uc_threshold Number of free transmit descriptor before wakeup callback invoked.
*
* \return GMAC_OK, GMAC_PARAM on parameter error.
*/
#if( GMAC_USES_WAKEUP_CALLBACK )
uint8_t gmac_dev_set_tx_wakeup_callback(gmac_device_t* p_gmac_dev,
gmac_dev_wakeup_cb_t func_wakeup_cb, uint8_t uc_threshold)
{
if (func_wakeup_cb == NULL) {
p_gmac_dev->func_wakeup_cb = NULL;
} else {
if (uc_threshold <= GMAC_TX_BUFFERS) {
p_gmac_dev->func_wakeup_cb = func_wakeup_cb;
p_gmac_dev->ul_wakeup_threshold = ( uint32_t )uc_threshold;
} else {
return GMAC_PARAM;
}
}
return GMAC_OK;
}
#endif /* GMAC_USES_WAKEUP_CALLBACK */
/**
* \brief Reset TX & RX queue & statistics.
*
* \param p_gmac_dev Pointer to GMAC device instance.
*/
void gmac_dev_reset(gmac_device_t* p_gmac_dev)
{
Gmac *p_hw = p_gmac_dev->p_hw;
gmac_reset_rx_mem(p_gmac_dev);
gmac_reset_tx_mem(p_gmac_dev);
gmac_network_control(p_hw, GMAC_NCR_TXEN | GMAC_NCR_RXEN
| GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT);
}
void gmac_dev_halt(Gmac* p_gmac);
void gmac_dev_halt(Gmac* p_gmac)
{
gmac_network_control(p_gmac, GMAC_NCR_WESTAT | GMAC_NCR_CLRSTAT);
gmac_disable_interrupt(p_gmac, ~0u);
}
/**
* \brief GMAC Interrupt handler.
*
* \param p_gmac_dev Pointer to GMAC device instance.
*/
#if( GMAC_STATS != 0 )
extern int logPrintf( const char *pcFormat, ... );
void gmac_show_irq_counts ()
{
int index;
for (index = 0; index < ARRAY_SIZE(intPairs); index++) {
if (gmacStats.intStatus[intPairs[index].index]) {
logPrintf("%s : %6u\n", intPairs[index].name, gmacStats.intStatus[intPairs[index].index]);
}
}
}
#endif
void gmac_handler(gmac_device_t* p_gmac_dev)
{
Gmac *p_hw = p_gmac_dev->p_hw;
gmac_tx_descriptor_t *p_tx_td;
uint32_t ul_tx_status_flag;
#if( GMAC_STATS != 0 )
int index;
#endif
uint32_t ul_isr = gmac_get_interrupt_status(p_hw);
uint32_t ul_rsr = gmac_get_rx_status(p_hw);
uint32_t ul_tsr = gmac_get_tx_status(p_hw);
#if( GMAC_STATS != 0 )
{
for (index = 0; index < ARRAY_SIZE(intPairs); index++) {
if (ul_isr & intPairs[index].mask)
gmacStats.intStatus[intPairs[index].index]++;
}
}
#endif /* GMAC_STATS != 0 */
/* RX packet */
if ((ul_isr & GMAC_ISR_RCOMP) || (ul_rsr & (GMAC_RSR_REC|GMAC_RSR_RXOVR|GMAC_RSR_BNA))) {
/* Clear status */
gmac_clear_rx_status(p_hw, ul_rsr);
if (ul_isr & GMAC_ISR_RCOMP)
ul_rsr |= GMAC_RSR_REC;
/* Invoke callbacks which can be useful to wake op a task */
xRxCallback( ul_rsr );
}
/* TX packet */
if ((ul_isr & GMAC_ISR_TCOMP) || (ul_tsr & (GMAC_TSR_TXCOMP|GMAC_TSR_COL|GMAC_TSR_RLE|GMAC_TSR_UND))) {
ul_tx_status_flag = GMAC_TSR_TXCOMP;
/* A frame transmitted */
/* Check RLE */
if (ul_tsr & GMAC_TSR_RLE) {
/* Status RLE & Number of discarded buffers */
ul_tx_status_flag = GMAC_TSR_RLE | CIRC_CNT(p_gmac_dev->l_tx_head,
p_gmac_dev->l_tx_tail, GMAC_TX_BUFFERS);
gmac_reset_tx_mem(p_gmac_dev);
gmac_enable_transmit(p_hw, 1);
}
/* Clear status */
gmac_clear_tx_status(p_hw, ul_tsr);
if (!CIRC_EMPTY(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail)) {
/* Check the buffers */
do {
p_tx_td = &gs_tx_desc[p_gmac_dev->l_tx_tail];
/* Any error? Exit if buffer has not been sent yet */
if ((p_tx_td->status.val & GMAC_TXD_USED) == 0) {
break;
}
/* Notify upper layer that a packet has been sent */
xTxCallback(ul_tx_status_flag, (void*)p_tx_td->addr); // Function call prvTxCallback
#if( ipconfigZERO_COPY_TX_DRIVER != 0 )
{
p_tx_td->addr = 0ul;
}
#endif /* ipconfigZERO_COPY_TX_DRIVER */
circ_inc32(&p_gmac_dev->l_tx_tail, GMAC_TX_BUFFERS);
} while (CIRC_CNT(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
GMAC_TX_BUFFERS));
}
if (ul_tsr & GMAC_TSR_RLE) {
/* Notify upper layer RLE */
xTxCallback(ul_tx_status_flag, NULL);
}
#if( GMAC_USES_WAKEUP_CALLBACK )
/* If a wakeup has been scheduled, notify upper layer that it can
send other packets, and the sending will be successful. */
if ((CIRC_SPACE(p_gmac_dev->l_tx_head, p_gmac_dev->l_tx_tail,
GMAC_TX_BUFFERS) >= p_gmac_dev->ul_wakeup_threshold)
&& p_gmac_dev->func_wakeup_cb) {
p_gmac_dev->func_wakeup_cb();
}
#endif
}
}
//@}
/// @cond 0
/**INDENT-OFF**/
#ifdef __cplusplus
}
#endif
/**INDENT-ON**/
/// @endcond