<< >>

A Sample Ethernet Application (tutorial)

This tutorial describes a demo included in the xmos ethernet package. The demo can be found in the directory app_ethernet_demo and provides a simple ethernet application that responds to ICMP ping requests. It assumes a basic knowledge of XC programming. For information on XMOS programming, you can find reference material about XC programming at the XMOS website.

To write an ethernet enabled application for an XMOS device requires several things:

  1. Write a Makefile for our application
  2. Provide an ethernet_conf.h configuration file
  3. Provide a custom filter function
  4. Write the application code that uses the component

Makefile

The Makefile is found in the top level directory of the application. It uses the general XMOS makefile in module_xmos_common which compiles all the source files in the application and the modules that the application uses. We only have to add a couple of configuration options.

Firstly, this application is for an XC-2 development board so the TARGET variable needs to be set in the Makefile.

# The TARGET variable determines what target system the application is
# compiled for. It either refers to an XN file in the source directories
# or a valid argument for the --target option when compiling.

TARGET = XC-2

Secondly, the application will use the ethernet module (and the locks module which is required by the ethernet module). So we state that the application uses these.

# The USED_MODULES variable lists other module used by the application.

USED_MODULES = module_ethernet module_locks

Given this information, the common Makefiles will build all the files in the required modules when building the application. This works from the command line (using xmake) or from Eclipse.

ethernet_conf.h

The ethernet_conf.h file is found in the src/ directory of the application. This file contains a series of #defines that configure the ethernet stack. The possible #defines that can be set are described in Configuration Defines.

Within this application we set the maximum packet size we can receive to be the maximum possible allowed in the ethernet standard and set the number of buffers to be 5 packets for incoming packets and 5 for outgoing.

The maximum number of ethernet clients (chanends we can connect to the ethernet server) is set to 4 (even though we only have one client in this example).

// Copyright (c) 2011, XMOS Ltd, All rights reserved
// This software is freely distributable under a derivative of the
// University of Illinois/NCSA Open Source License posted in
// LICENSE.txt and at <http://github.xcore.com/>


#define MAX_ETHERNET_PACKET_SIZE (1518)

#define NUM_MII_RX_BUF 5
#define NUM_MII_TX_BUF 5

#define MAX_ETHERNET_CLIENTS   (4)    

mac_custom_filter

The mac_custom_filter function allows use to decide which packets get passed through the MAC. To do this, we have to provide the mac_custom_filter.h header file and a definition of the mac_custom_filter function itself.

The header file in this example just prototypes the mac_custom_filter function itself.

// Copyright (c) 2011, XMOS Ltd, All rights reserved
// This software is freely distributable under a derivative of the
// University of Illinois/NCSA Open Source License posted in
// LICENSE.txt and at <http://github.xcore.com/>

extern int mac_custom_filter(unsigned int data[]);

The module requires the application to provide the header to cater for the case where the function is describe as an inline function for performance. In this case it is just prototyped and the definition of mac_custom_filter is in our main application code file demo.xc

int mac_custom_filter(unsigned int data[]){
	char addr[6];

	addr[0] = mac_address[0];
	addr[1] = mac_address[0] >> 8;
	addr[2] = mac_address[0] >> 16;
	addr[3] = mac_address[0] >> 24;
	addr[4] = mac_address[1];
	addr[5] = mac_address[1] >> 8;

	if (is_broadcast((data,char[])) &&
            is_ethertype((data,char[]), ethertype_arp)){
		return 1;
	}else if (is_mac_addr((data,char[]), addr) &&
                  is_ethertype((data,char[]), ethertype_ip)){          
		return 1;
	}

	return 0;
}

This function returns 0 if we do not want to handle the packet and non-zero otherwise. The non-zero value is used later to distribute to different clients. In this case we detect ARP packets and ICMP packets which match our own mac address as a destination. In this case the function returns 1. The defintions os is_broadcast, is_ethertype and is_mac_addr are in demo.xc

Top level program structure

Now that we have the basic ethernet building blocks, we can build our application. This application is contained in demo.xc. Within this file is the main() function which declares some variables (primarily XC channels). It also contains a top level par construct which sets the various functional units running that make up the program.

On core 2 we run the ethernet server. First, the function ethernet_getmac_otp() reads the device mac address from ROM. The function phy_init() initializes the phy and them the main function ethernet_server() runs the ethernet component. The server communicates with other threads via the rx and tx channel arrays.

      on stdcore[2]:
      {
		ethernet_getmac_otp(otp_data, otp_addr, otp_ctrl,
                                    (mac_address, char[]));
		phy_init(clk_smi,
#ifdef PORT_ETH_RST_N
               p_mii_resetn,
#else
               null,
#endif
                 smi,
                 mii);
        ethernet_server(mii, mac_address,
                        rx, 1,
                        tx, 1,
                        null,
                        null);
      }

On core 0 we run the demo() function which takes ethernet packets and responds to ICMP ping requests. This function is described in the next section.

      on stdcore[0] : demo(tx[0], rx[0]);

Ethernet packet processing

The demo() function does the actual ethernet packet processing. First the application gets the device mac address from the ethernet server on core 2.

  mac_get_macaddr(tx, own_mac_addr);

Then the packet filter is set up. The mask value passed to mac_set_custom_filter() is used within the mac. After the custom_mac_filter function is run, if the result is non-zero then the result is and-ed against the mask. If this is non-zero then the packet is forwarded to the client.

So in this case, the mask is 1 so all packets that get a 1 result from custom_mac_filter function will get passed to this client.

  mac_set_custom_filter(rx, 0x1);

After we are set up to receive the correct packets we can go into the main loop that responds to ARP and ICMP packets.

The first task in the loop is to receive a packet into the rxbuf buffer using the mac_rx() function.

  while (1)
  {
    unsigned int src_port;
    unsigned int nbytes;
    mac_rx(rx, rxbuf, nbytes, src_port);
    printstr("packet\n");

When the packet is received it may be an ARP or IP packet since both get past our filter. First we check if it is an ARP packet, if so then we build the response (in the txbuf array) and send it out over ethernet using the mac_tx() function. The functions is_valid_arp_packet and build_arp_response are defined demo.xc.

    if (is_valid_arp_packet(rxbuf, nbytes))
      {
        build_arp_response(rxbuf, txbuf, own_mac_addr);
        mac_tx(tx, txbuf, nbytes, ETH_BROADCAST);
        printstr("ARP response sent\n");
      }

If the packet is not an ARP packet we check if it is an ICMP packet and in the same way build a response and send it out.

    else if (is_valid_icmp_packet(rxbuf, nbytes))
      {
        build_icmp_response(rxbuf, (txbuf, unsigned char[]), own_mac_addr);
        mac_tx(tx, txbuf, nbytes, ETH_BROADCAST);
        printstr("ICMP response sent\n");
      }

Running the application

To test the application the following define in demo.xc needs to be set to an IP address that is routable in the network that the application is to be tested on.

// NOTE: YOU MAY NEED TO REDEFINE THIS TO AN IP ADDRESS THAT WORKS
// FOR YOUR NETWORK
#define OWN_IP_ADDRESS {169, 254, 5, 27}

Once this is done, the demo can be compiled and the XC-2 connected to a PC. Pinging the IP address defined should now get a response e.g.:

PING 192.168.0.3 (192.168.0.3) 56(84) bytes of data.
64 bytes from 192.168.0.3: icmp_seq=1 ttl=64 time=2.97 ms
64 bytes from 192.168.0.3: icmp_seq=2 ttl=64 time=2.93 ms
64 bytes from 192.168.0.3: icmp_seq=3 ttl=64 time=2.91 ms
64 bytes from 192.168.0.3: icmp_seq=4 ttl=64 time=2.96 ms
...