Table of Contents

Table of Chapters

2. BACKGROUND INFORMATION

This chapter is concerned with some of the more fundamental data structures underlying NicheLite and is provided to assist the application programmer with a more general understanding of how the the system operates. Other than the section identifying some of the Error Codes, and since this product has already been ported and tested on the MCF5223EVB, these sections can be safely be ignored by most users of NicheLite.

2.1 Error Codes

The following error codes are used throughout NicheLite. Generally, full success is communicated by a return code of zero; definite errors are negative numbers and indeterminate conditions are positive numbers. These codes are provided in ipport.h so that they can be modified to wrap around an existing system, within the guidelines mentions above. Please do NOT redefine errors have non-negative values or the stack will not work. These are usually returned by functions which return an int, but may also be left in a global t_errno. See the function specifications for per-function details.

#define SUCCESS           0  /* whatever it was, it worked. */
#define OK                0

/* programming errors */
#define ENP_PARAM       -10  /* bad parameter */
#define ENP_LOGIC       -11  /* sequence of events that shouldn't happen */

/* system errors */
#define ENP_NOMEM       -20  /* malloc or calloc failed */
#define ENP_NOBUFFER    -21  /* ran out of free packets */
#define ENP_RESOURCE    -22  /* ran out of other queue-able resource */
#define SEND_DROPPED    ENP_RESOURCE  /* full queue or similar lack of resource */

/* net errors */
#define ENP_SENDERR     -30  /* send to net failed at low layer */
#define ENP_NOARPREP    -31  /* no arp for a given host */
#define ENP_BAD_HEADER  -32  /* bad header at upper layer (for upcalls) */
#define ENP_NO_ROUTE    -33  /* can't find a reasonable next IP hop */

/* conditions that are not really fatal OR success: */
#define ENP_SEND_PENDING  1  /* packet queued pending an arp reply */
#define ENP_NOT_MINE      2  /* packet was not of interest (upcall reply only)*/

/* arp holding packet while awaiting a response from fhost */
#define ARP_WAITING       ENP_SEND_PENDING

2.2 Data Structures

This section describes various data structures the contents of which are important to understand in order to port NicheLite to another environment.

2.2.1 The netbuf Structure and the Packet Queues

The netbuf structure is used to define a packet that is to be transmitted or that has been received. PACKET is typed to be a pointer to a netbuf structure as a convenience.

struct netbuf {
   struct netbuf * next;   /* queue link */
   char *nb_buff;          /* beginning of raw buffer */
   unsigned nb_blen;       /* length of raw buffer */
   char *nb_prot;          /* beginning of protocol data */
   unsigned nb_plen;       /* length of protocol data */
   long nb_tstamp;         /* packet timestamp */
   struct net *net;        /* the interface (net) it came in on, 0-n */
   ip_addr fhost;          /* IP address associated with packet */
   unsigned short type;    /* i.e. 0800 for IP, filled in by receiver(rx) or net layer(tx)*/
};

typedef struct netbuf * PACKET; /* struct netbuf in netbuf.h */

netbuf structures are allocated and freed dynamically by the network stack via calls to the functions pk_alloc() and pk_free(). These functions maintain two queues of netbuf structures for the purpose of this dynamic allocation, bigfreeq and lilfreeq.

queue   bigfreeq;   /* big free buffers */
queue   lilfreeq;   /* small free buffers */

Each queue structure is initialized to contain a defined set of netbuf structures during system initialization. pk_alloc() removes a netbuf structure from one of the queues and returns a pointer to that structure to the network stack. pk_free() adds the netbuf structure that is passed to it to one of the queues when the stack is done using the structure. The intent of maintaining two queues is to address the reality that with most Internet applications, the packets that get transmitted tend to fall into two groups with regard to packet size. Big packets are used to transmit applications’ TCP based payload data. Little packets are used to transmit TCP acknowledgments and ICMP messages. By maintaining separate queues for big and little packets, target system memory utilization can be optimized because packet buffers of a single maximum size do not need to be allocated to transmit little packets.

The stack maintains global variables to allow the porting engineer to define the number of big and little packets that are allocated for these queues during stack initialization. Global variables are also maintained to allow the porting engineer to define the sizes of the big and little packets. These global variables are shown below.

During initialization the porting engineer can modify the values contained in these variables to suit the needs of the target system. In this way the number and sizes of the packet buffers can be tuned on a per-target-system basis and could conceivably even vary from one boot to the next if the system configuration changed. These variables should never be modified after the IP stack initialization function ip_startup() is called. These values are stored in variables rather than constants so that they can be assigned at runtime.

unsigned lilbufs = 6;      /* number of small bufs to init */
unsigned lilbufsiz = 128;  /* big enough for average packet */
unsigned bigbufs = 3;      /* number of big bufs to init */
unsigned bigbufsiz = 1536; /* big enough for max. ethernet packet */

The remainder of this section describes the various fields of the netbuf structure. Most of this will be informational to the typical porting engineer, though some of the fields are significant to those who are writing network interface code or are using the lightweight UDP interface.

struct netbuf * next;   /* queue link */

The next field is used to create a linked list of netbuf structures. This linked list is used to implement the bigfreeq and lilfreeq free queues. The next field is not significant between the time a netbuf structure is allocated with pk_alloc() and freed with pk_free().

char *nb_buff;          /* beginning of raw buffer */
unsigned nb_blen;       /* length of raw buffer */

nb_buff contains the address of the beginning of a data buffer that is used to store data that is to be transmitted or that has been received. nb_blen contains the length of this buffer in bytes. Their values should not be modified by any function other than the pk_init() function that creates the free queues.

char *nb_pro;           /* beginning of protocol data */
unsigned nb_plen;       /* length of protocol data */

nb_prot and nb_plen are used by the stack to support encapsulation on packet transmission and de-multiplexing on packet reception. For example, when a UDP packet is to be sent and a netbuf structure is allocated to contain it, the nb_prot field is initially set to point to an offset into the data buffer at which the application constructs the UDP data. The offset chosen is large enough that lower layers in the stack can prefix the data with the UDP, IP and link layer headers. The nb_plen field is initially set to the length of the UDP data. As the packet is processed by succeeding lower layers of the stack, nb_prot is decreased to point to the beginning of the UDP header, the IP header and eventually the link layer header. Likewise, nb_plen is increased to include the sizes of these headers. On packet reception a similar process occurs in reverse. nb_prot is initially set to the beginning of the received link layer frame and nb_plen is set to the length of the entire received frame. As the packet is processed by succeeding upper layers of the stack, nb_prot is increased and nb_plen is decreased.

The porting engineer needs to concern himself with these fields in the following circumstances:

long nb_tstamp;         /* packet timestamp */

When received packets are placed into a netbuf structure, the current value of cticks is stored in nb_tstamp. The field is not otherwise used, but can be useful during debugging.

struct net *net         /* the interface (net) it came in on, 0-n */

net contains a pointer to the net structure that is associated with the network interface on which a packet is to be transmitted or on which a packet has been received. Applications above the Sockets layer would normally not set this field as this is performed by the stack’s IP routing function. Network interface implementations need to set this field to point to the net structure that is associated with the network interface when packets are received.

ip_addr fhost;          /* IP address associated with packet */

The stack uses the fhost field to store the IP address of a packet’s "foreign host" where the foreign host is the destination address on transmitted packets and the source address on received packets. Porting engineers would normally not modify a netbuf structure’s fhost field. An exception to this occurs when using the lightweight UDP API in which case the fhost field should be set to the destination IP address before the call to udp_send().

unsigned short type; /*i.e. 0800 for IP, filled in by receiver(rx) or net layer.(tx)*/

Network interface implementations need to set this field to indicate the link layer protocol type of a received packet. One of two values should be assigned; ARPTP for received ARP packets and IPTP for received IP packets.

2.2.2 The net Structure, the nets[] Array, and the netlist

The net structure is used to define attributes of a network interface, such as an Ethernet, to the IP layer.

struct net {
   struct net * n_next;    /* pointer next net */
   char name[IF_NAMELEN];  /* device ID name */
   int (*n_init)(int);     /* net initialization routine */
   int (*raw_send)(struct net *, char*, unsigned);   /* put raw data on media */
   int (*pkt_send)(struct netbuf *);                 /* send packet on media */
   int (*n_close)(int);    /* net close routine */
   int (*n_reg_type)(unshort, struct net*);
                           /* register a MAC type, ie 0x0800 for IP */
   void  (*n_stats)(void * pio,int iface);
                           /* per device-type (ODI, pktdrv) statistics dump */
   int n_lnh;              /* net's local net header  size */
   int n_mtu;              /* net's largest legal buffer size */
   ip_addr n_ipaddr;       /* interface's internet address */
   int n_snbits ;          /* number of subnet bits */
   ip_addr snmask;         /* interface's subnet mask */
   ip_addr n_defgw;        /* the default gateway for this net */
   ip_addr n_netbr;        /* our network broadcast address */
   ip_addr n_netbr42;      /* our (4.2BSD) network broadcast  */
   ip_addr n_subnetbr;     /* our subnetwork broadcast address */
   unsigned n_hal;         /* Hardware address length */
   char *n_haddr;          /* Pointer to hardware address, size = n_hal */
   IFMIB n_mib;            /* pointer to interface(if) mib structure */
   void *n_local;          /* pointer to custom info, null if unused */
   int n_flags;            /* mask of the NF_ bits below */
};

2.3 Initialization

This section discusses various issues that have to do with runtime stack initialization. The steps required to initialize the stack are summarized in the list below:

Following this initialization the stack imposes requirements on the target system for various services to be performed, the nature of which will depend on the software architecture of the rest of the target system.

2.3.1 Initialization of net Structure IP Addressing Fields

The net structures and the fields they contain are described in the section titled "The net Structure, the nets[] Array". This implementation includes interface preparation functions that when called, will initialize most of the fields of the interface’s net structure. The provided preparation functions do not however do the whole job of net structure initialization because some of these fields define target system IP addressing information that must be unique for each instance of the target system.

The initialization of the following IP addressing fields of a target’s net structures is, by convention, performed before the interface preparation functions are called, usually in the main() function of the target system before the call to ip_startup() (we want to emphasize that this is just a convention, not a requirement).

n_ipaddrInterface’s IP address.
snmaskInterface’s subnet mask.
n_defgwTarget system’s default route.

How the values for these fields are to be determined will depend on the target system. Some target systems will include some sort of non-volatile storage (EPROM, FLASH, disk, etc.) into which these values can be stored and retrieved during system initialization. We refer to this as "static" IP address resolution. Other target systems will use a protocol such as BOOTP or DHCP to allow the network to configure these addresses. We refer to this as "dynamic" IP address resolution.

If a target system is to use static IP address resolution, then these fields should be assigned for the target system’s interface. Note that this information should be stored in these fields in network order. If a target system is to use dynamic IP address resolution, then these fields should be set to 0. Dynamic IP address resolution is described in the section titled "Dynamic Internet Configuration with DHCP".

2.3.2 Initialization of the Packet Buffer Queue Sizes

The packet buffer queues are described in the section titled "The netbuf Structure and the Packet Queues". The global variables that define the sizes of these queues and the sizes of the big and little packets need to be assigned. There are no hard and fast rules to determine what queue sizes should be used on a given target system. The application engineer will likely find that the best way to determine these queue sizes is to do a little experimentation with the target system under whatever is considered to be heavy network load for the application.

2.3.3 Initialization of the IP and TCP Layers: ip_startup()

The next step is to call the function ip_startup(). The function prototype for ip_startup() is shown below:

char *ip_startup(void);

ip_startup() returns NULL if successful, otherwise it returns an ASCII string that is descriptive of the error that it encountered that caused it to be unsuccessful. In a properly ported system, ip_startup() should always succeed.

The following list summarizes what ip_startup() does:

2.3.4 Dynamic Internet Configuration with DHCP

If a target system is to determine its Internet configuration dynamically, it should be done after a call to ip_startup(). The InterNiche stack supports the usage of the DHCP protocol to allow the target to determine its IP address and other configuration information from the network. DHCP is a superset of BOOTP, so the target’s configuration information can be retrieved from both BOOTP and DHCP servers on a connected network.

In order to use DHCP to determine a target system’s IP addresses, the following steps should be taken:

Where iface is an index into the nets[ ] array that is associated with the interface and call_back is a function that will be called when the DHCP state machine determines that the interface has been configured with an IP address via DHCP.

If and when the DHCP state machine determines that the interface has been configured with valid IP addresses, it will call the call_back function provided in the call to dhc_set_callback(). The call_back function provided should signal or otherwise notify the initialization sequence that the DHCP configuration process is done so that initialization can continue. How this is done will depend on the nature of the target system.

It is possible however that the call_back function will never be called. This can happen for example, if there is no DHCP server on the connected network. For this reason the servicing of the stack should be timed so that after some reasonable time out, the exception condition is noted. What to do with regard to resolution of the target system’s IP addresses in this case is up to the porting engineer to determine. There is usually not much that can be done except display an error message and re-boot to try again.

The main() functions contained in the various target system dependent directories contain examples of how the DHCP state machine can be driven on various target systems.

DHCP can also be configured at build time to retrieve name server IP addresses. This requires that DHCP be built with the DHC_MAXDNSRVS macro set to the maximum number of name server IP addresses that DHCP may accept for each interface on which it is run. DHCP will place accepted name server IP addresses in dhc_states[iface].dnsrv before calling the call_back function.

2.3.5 Initialization of Application Servers

The final step of initialization on most targets will be to call functions which initialize various application level services. Most of these application level services are provided as separate InterNiche products, the initialization of which is described in the documentation that accompanies those products. If you are using these other InterNiche products with the NicheLite stack, this is the point at which to call their initialization functions.

There are however a few application level services that are part of the NicheLite stack. Their initialization functions are described below. These functions all accept no parameters and return 0 when successful, unless specified otherwise:

ping_init()Initializes the user interface ping application so that it is possible to initiate pings (ICMP echo requests) from the target system. The call is necessary if it is desired to send ping requests. The stack will respond to ping requests from other hosts with or without the ping application being initialized.
udp_echo_init()Initializes a UDP echo server in the target that will listen on the standard UDP echo port and respond to UDP datagrams sent to it from other hosts.
tcp_echo_init()Initializes a TCP echo server in the target that will listen on the standard TCP echo port and respond to connections made to it from other hosts.

It is recommended that the above application services be enabled and initialized on the target system, if only for the duration of target system debugging and testing. They are invaluable tools for debugging and validating a target system and port. The TCP echo server in particular has proven to be good at turning up problems related to heavy network load and throughput that do not surface otherwise.