emNet PHY Loopback (Sample)
| 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 ****************************/