emNet PHY Loopback (Sample)

From SEGGER Knowledge Base
Jump to navigation Jump to search
IP_PhyLoopbackTest_Sample.c
Requires modifications Yes
Download IP_PhyLoopbackTest_Sample.c

This Sample runs a physical loopback test when loopback is enabled in the PHY. It periodically sends a broadcast Ethernet frame to the network and expects it to get mirrored by the loopback enabled in the PHY.

NOTE: The Sample is designed for a far-end loopback test. Using it as a near-end loopback test requires modifications in the PHY driver and the NI driver. Far-end loopback tests the entire transmission path (loops at the remote transceiver), near-end loops at the source/local transceiver.

For a far-end loopback test the following needs to be done:

The loopback behavior in the PHY needs to be enabled. This can be done by adding an OnReset callback via IP_PHY_AddResetHook() in IP_X_Config() and then read-modify-write the necessary PHY registers to enable the PHY loopback. Please refer to the manual of your specific PHY for instructions on how to enable the far end loopback.

Code

/*********************************************************************
*                   (c) SEGGER Microcontroller GmbH                  *
*                        The Embedded Experts                        *
*                           www.segger.com                           *
**********************************************************************

-------------------------- END-OF-HEADER -----------------------------

Purpose : Runs a physical loopback test when loopback is enabled in the PHY.

Notes:
  (1) For compatibility with interfaces that need to connect in
      any way this sample calls connect and disconnect routines
      that might not be needed in all cases.
      This sample can be used for Ethernet interfaces
      and is configured to use the last registered interface as
      its main interface.

Additional information:
  This sample periodically sends a broadcast Ethernet frame with a
  bogus Ethernet type to the network and expects it to get mirrored
  by a loopback enabled in the PHY.

  Preparations:
    This sample application is designed for a far end loopback test
    but can also be used with a near end loopback test but in this
    case requires code modification in the PHY driver and NI driver
    for a fixed Ethernet speed without auto-negotiation.

    For a far end loopback test the following needs to be done:
    The loopback behavior in the PHY needs to be enabled. This can be
    done by adding an OnReset callback during IP_X_Config() by calling
    IP_PHY_AddResetHook() and read-modify-write the necessary PHY
    registers to enable the PHY loopback.
    Please note that modifying register 0 (BMCR) for local loopback
    is not possible right now without modifying code of the generic PHY
    driver itself. For instructions on how to enable the far end loopback
    test mode for your PHY, please refer to the user manual for your
    specific PHY used with your hardware.

  Expected behavior:
    This sample sends a broadcast Ethernet frame each second and expects
    to receive it back according to the PHY loopback enabled. The sample
    compares the frame received to the last frame sent and outputs the
    current statistics for the test.
    The bogus Ethernet type used will make the packet get discarded by
    the stack in any case as no handler for this type exists. This avoids
    any potential protocol confusion in the stack.

  Sample output:
    ...
    0:019 MainTask - INFO: Giving the PHY a startup time of 3000ms
    3:019 MainTask - INFO: Starting loopback test
    4:019 MainTask - INFO: Sending test frame #1
    4:019 IP_Task - OK  : Looped back frame received.
    5:000 IP_Task - DHCPc: Sending discover!
    5:019 MainTask - INFO: Sending test frame #2
    5:019 IP_Task - OK  : Looped back frame received.
    6:019 MainTask - INFO: Sending test frame #3
    6:019 IP_Task - OK  : Looped back frame received.
    7:019 MainTask - INFO: Sending test frame #4
    7:019 IP_Task - OK  : Looped back frame received.
    8:019 MainTask - INFO: Sending test frame #5
    8:019 IP_Task - OK  : Looped back frame received.
    9:019 MainTask - STAT: Sent: 5; RecvOK: 5; RecvFail: 0; Missing: 0
    12:019 MainTask - INFO: Sending test frame #6
    ...
*/

#include "RTOS.h"
#include "BSP.h"
#include "IP.h"

/*********************************************************************
*
*       Defines, configurable
*
**********************************************************************
*/

#define USE_RX_TASK       (0u)     // 0: Packets are read in ISR, 1: Packets are read in a task of its own.
#define PHY_STARTUP_TIME  (3000u)  // Grant the PHY some startup time before trying to send packets.
#define PRINT_STAT_AFTER  (5u)     // Number of test frames to send each before printing the current statistics.

//
// Task priorities.
//
enum {
   TASK_PRIO_IP_TASK = (150)  // Priority should be higher than all IP application tasks.
#if USE_RX_TASK
  ,TASK_PRIO_IP_RX_TASK       // Must be the highest priority of all IP related tasks.
#endif
};

/*********************************************************************
*
*       Data types
*
**********************************************************************
*/

typedef struct {
  U16 Dummy;  // Ethernet in the stack uses 2 bytes padding. Skip when sending.
  U8  aMACDest[6];
  U8  aMACSrc [6];
  U16 Type;
  U32 PacketId;
} TEST_FRAME;

/*********************************************************************
*
*       Static data
*
**********************************************************************
*/

static IP_HOOK_ON_STATE_CHANGE _StateChangeHook;
static int                     _IFaceId;

//
// Task stacks and Task-Control-Blocks.
//
static OS_STACKPTR int _IPStack[TASK_STACK_SIZE_IP_TASK/sizeof(int)];       // Stack of the IP_Task.
static OS_TASK         _IPTCB;                                              // Task-Control-Block of the IP_Task.

#if USE_RX_TASK
static OS_STACKPTR int _IPRxStack[TASK_STACK_SIZE_IP_RX_TASK/sizeof(int)];  // Stack of the IP_RxTask.
static OS_TASK         _IPRxTCB;                                            // Task-Control-Block of the IP_RxTask.
#endif

//
// Sample specific data.
//
static TEST_FRAME _TestFrame;
static U32        _LastRecvPacketId;
static U32        _LastSendPacketId;
static U32        _RecvOkCnt;
static U32        _RecvFailCnt;
static U32        _RecvDropCnt;
static unsigned   _StatCnt;

/*********************************************************************
*
*       Prototypes
*
**********************************************************************
*/
#ifdef __cplusplus
extern "C" {     /* Make sure we have C-declarations in C++ programs */
#endif
void MainTask(void);
#ifdef __cplusplus
}
#endif

/*********************************************************************
*
*       Local functions
*
**********************************************************************
*/

/*********************************************************************
*
*       _OnStateChange()
*
* Function description
*   Callback that will be notified once the state of an interface
*   changes.
*
* Parameters
*   IFaceId   : Zero-based interface index.
*   AdminState: Is this interface enabled ?
*   HWState   : Is this interface physically ready ?
*/
static void _OnStateChange(unsigned IFaceId, U8 AdminState, U8 HWState) {
  //
  // Check if this is a disconnect from the peer or a link down.
  // In this case call IP_Disconnect() to get into a known state.
  //
  if (((AdminState == IP_ADMIN_STATE_DOWN) && (HWState == 1)) ||  // Typical for dial-up connection e.g. PPP when closed from peer. Link up but app. closed.
      ((AdminState == IP_ADMIN_STATE_UP)   && (HWState == 0))) {  // Typical for any Ethernet connection e.g. PPPoE. App. opened but link down.
    IP_Disconnect(IFaceId);                                       // Disconnect the interface to a clean state.
  }
}

/*********************************************************************
*
*       _OnRxTest()
*
*  Function description
*    This function receives a packet looped back via a PHY loopback
*    that has previously been enabled and sent from the MainTask
*    periodically.
*
*  Parameters
*    pPacket: Pointer to packet received. pData points to the IP header.
*
*  Return value
*    0    : Packet will be processed by the stack.
*    Other: Packet will be freed     by the stack.
*/
static int _OnRxTest(IP_PACKET* pPacket) {
  const TEST_FRAME* pFrame;
        int         r;

  r = 0;   // Let the stack process the packet.

  pFrame = (TEST_FRAME*)pPacket->pBuffer;                                                      // Ethernet has an internal padding of 2 bytes at the start of pBuffer, this is known by the TEST_FRAME structure.
  if(IP_MEMCMP(&pFrame->Type, &_TestFrame.Type, sizeof(_TestFrame.Type)) == 0) {                  // Is this our bogus Ethernet type ?
    if (IP_MEMCMP(&pFrame->aMACDest[0], &_TestFrame.aMACDest[0], sizeof(_TestFrame) - 2) == 0) {  // ==> Is the frame identical to what we sent last ?
      IP_Logf_Application("OK  : Looped back frame received.");                                // ====> Frame is     the same.
      _RecvOkCnt++;
      _LastRecvPacketId++;                    // We received our loopback frame as expected.
    } else {
      IP_Logf_Application("ERR : Looped back frame not the same.");                            // !===> Frame is NOT the same.
      _RecvFailCnt++;
      _LastRecvPacketId = _LastSendPacketId;  // We have received something but it was an error.
    }
    r = -1;  // Let the stack discard the packet.
  }
  return r;
}

/*********************************************************************
*
*       Global functions
*
**********************************************************************
*/

/*********************************************************************
*
*       MainTask()
*
* Function description
*   Main task executed by the RTOS to create further resources and
*   running the main application.
*/
void MainTask(void) {
  int r;
  U16 Type;

  IP_Init();
  IP_AddLogFilter(IP_MTYPE_APPLICATION);
  _IFaceId = IP_INFO_GetNumInterfaces() - 1;                                           // Get the last registered interface ID as this is most likely the interface we want to use in this sample.
  OS_SetPriority(OS_GetTaskID(), TASK_PRIO_IP_TASK);                                   // For now, this task has highest prio except IP management tasks.
  OS_CREATETASK(&_IPTCB  , "IP_Task"  , IP_Task  , TASK_PRIO_IP_TASK   , _IPStack);    // Start the IP_Task.
#if USE_RX_TASK
  OS_CREATETASK(&_IPRxTCB, "IP_RxTask", IP_RxTask, TASK_PRIO_IP_RX_TASK, _IPRxStack);  // Start the IP_RxTask, optional.
#endif
  IP_SetRxHook(_OnRxTest);                                                             // Register hook to be notified of incoming packets of all kind.
  IP_AddStateChangeHook(&_StateChangeHook, _OnStateChange);                            // Register hook to be notified on disconnects.
  IP_Connect(_IFaceId);                                                                // Connect the interface if necessary.
  IP_Logf_Application("INFO: Giving the PHY a startup time of %dms", PHY_STARTUP_TIME);
  OS_Delay(PHY_STARTUP_TIME);
  IP_Logf_Application("INFO: Starting loopback test");
  //
  // Prepare the test frame with static data that will not change between sends.
  //
  IP_MEMSET(&_TestFrame, 0, sizeof(TEST_FRAME));
  IP_MEMSET(&_TestFrame.aMACDest[0], 0xFF, 6);            // Send to broadcast.
  IP_GetHWAddr(_IFaceId, &_TestFrame.aMACSrc[0], 6u);  // Use our real MAC address as source.
  Type = ntohs(0x1234u);                               // Use the bogus Ethernet type 0x1234 for our test frame.
  IP_MEMCPY(&_TestFrame.Type, &Type, sizeof(Type));       // Load via memcpy to avoid unaligned access error on some processors.
  //
  // Periodically send a test frame.
  //
  while (1) {
    BSP_ToggleLED(1);
    OS_Delay(1000);
    //
    // Check if we have received the last frame or if it got lost.
    //
    if (_LastRecvPacketId != _LastSendPacketId) {
      IP_Logf_Application("ERR : Packet sent with Id #%lu not received.", _LastSendPacketId);
      _RecvDropCnt++;
      _LastRecvPacketId = _LastSendPacketId;
    }
    //
    // Output statistics every x loops.
    //
    if (_StatCnt == PRINT_STAT_AFTER) {
      IP_Logf_Application("STAT: Sent: %lu; RecvOK: %lu; RecvFail: %lu; Missing: %lu", _LastSendPacketId, _RecvOkCnt, _RecvFailCnt, _RecvDropCnt);
      OS_Delay(3000);
      _StatCnt = 0u;
    }
    _StatCnt++;
    //
    // Send the next test frame.
    //
    _LastSendPacketId++;
    _TestFrame.PacketId = _LastSendPacketId;
    IP_Logf_Application("INFO: Sending test frame #%lu", _LastSendPacketId);
    do {
      r = IP_SendPacket(_IFaceId, &_TestFrame.aMACDest[0], sizeof(_TestFrame) - 2);  // Skip the two internal padding bytes.
      if (r != 0) {
        IP_Logf_Application("ERR : Send error %d, retrying after a short delay.", r);
        OS_Delay(100);
      }
    } while (r != 0);
  }
}

/*************************** End of file ****************************/