Table of Contents

Table of Chapters

2. PORTING GUIDE

This section describes the steps needed to port NicheStack to a new environment. The discussions below generally assume that the stack is being ported to a small or embedded system with a network interface and that a minimal ANSI C library is available.

The recommended steps to getting the InterNiche stack working on your target system are summarized below:

  1. Create an InterNiche stack source code tree in your development environment.
  2. Create your version of ipport.h_h (Section 2.2) and compile portable sources.
  3. Code your glue layers and compile.
  4. Build a target system image, test and debug.

2.1 Creating an InterNiche Source Code Tree

The InterNiche sources are typically distributed as a .zip file, which should be unzipped with an appropriate utility in such a way as to preserve the underlying directory structure, described in Section 1.4.

Please refer to toolnote.doc for information about building the sources.

2.2 The IP Port File: ipport.h_h

Before you compile the portable TCP/IP stack files you should create a version of the file ipport.h_h that is appropriate for your target system. An inspection of the NicheStack source code will show that many of the C language source files include a file named ipport.h. ipport.h is intended to contain not only the IP layer's port dependent definitions, but all the architectural definitions for all the IP related code. CPU architectures (big vs. little endian), compiler idiosyncrasies and optional features (DHCP, multiple interfaces, IP routing support) are controlled in this file. ipport.h is one of the target system include files that are copied to the h directory and thus should not be edited directly. See the section titled Target System Include Directory /h for instructions on how to deal with files of this type.

This section outlines the basic contents of ipport.h. Remember, the modifications described in this section should be made to the file named ipport.h_h that exists in your target system dependent directory, not to any file named ipport.h.

Changes that you make directly to ipport.h WILL BE LOST the next time you execute make because they WILL BE OVERWRITTEN with the contents of ipport.h_h.

2.2.1 Standard Macros and Definitions

The InterNiche stack expects TRUE, FALSE, and NULL to be defined within the scope of ipport.h_h. The best way to do this is usually to include the standard C library file stdio.h inside of ipport.h_h. If stdio.h is impractical to use or not available on your development system, the examples below will work for almost every C environment:

#ifndef TRUE
#define TRUE -1
#define FALSE 0
#endif
#ifndef NULL
#define NULL (void*)0
#endif

2.2.2 CPU Architecture

Four common macros are used from Berkeley UNIX for doing byte order conversions between the representation used in a particular CPU and the CPU independent "network" order. These are htons(), htonl(), ntohs(), and ntohl(). They may be either macros or functions. They accept 16 and 32 bit quantities as shown and convert them between network order ("big-endian") and the local CPU format, often referred to as host order. Most "big-endian processors", such as Motorola 68K, Power PC and ARM can just return the variable passed, as in this example:

#define htonl(long_var)   (long_var)
#define htons(short_var)  (short_var)
#define ntohl(long_var)   (long_var)
#define ntohs(short_var)  (short_var)

The Intel 8086 and its descendants require the byte order in the word or long to be swapped ("little-endian"). The standard InterNiche stack source code distribution which works for Intel processors in 16 bit real mode implements htons() and ntohs() as macros, whereas htonl() and ntohl() are implemented as function calls to the assembly language function lswap(), the implementation of which is contained in the file cksum1.asm.

#define htonl(long_var)   lswap(long_var)
#define ntohl(long_var)   lswap(long_var)
#define htons(short_var)  (((u_short)(short_var) >> 8) | \
                              ((u_short)(short_var) << 8))
#define ntohs(short_var)  htons(short_var)

Depending on your C compiler, it may be more efficient to define inline C macros or inline assembly language implementations of these macros.

#define   LITTLE_ENDIAN   1234
#define   BIG_ENDIAN      4321
#define   BYTE_ORDER      LITTLE_ENDIAN

In addition to the byte order conversion functions described above, it is necessary to set the value of the defined constant BYTE_ORDER to either LITTLE_ENDIAN or BIG_ENDIAN in order to indicate the byte ordering of the target system processor.

#define  ALIGN_TYPE   2     /* 16 bit alignment */

Some processors will access memory more efficiently if the addresses of the addressed data are evenly divisible by 2 or 4. If the target system processor is of this variety, set the defined constant ALIGN_TYPE to either 2 or 4, respectively. ALIGN_TYPE affects the memory alignment of allocated packet buffers.

2.2.3 Mutual Exclusion

There are several data structures in the InterNiche stack for which it is necessary to ensure that access is serialized. By serialized we mean that once access to the data structure is started by one thread of execution, then that thread of execution must complete its access to the data structure before another thread of execution accesses it.

In software applications that are implemented as a single polling loop with no interrupts, there is only one thread of execution and therefore serialization of access to shared data structures is inherent to the system. However, in systems that use interrupts or multitasking operating systems, serialization may need to be performed explicitly. This explicit serialization of access to shared data structures is referred to as mutual exclusion.

The InterNiche stack makes use of one of two different methods of mutual exclusion that are referred to as the critical section method and the net resource method. Generally, the critical section method is used farther down the function call tree or at a lower level than the net resource method.

Usually the critical section method is appropriate for embedded systems that lack a multitasking operating system, and the net resource method is appropriate for systems with a multitasking OS.

Failure to provide a correct implementation of mutual exclusion can result in the most intermittent, difficult to find, types of bugs. It is well worth the porting engineer's effort to desk check his implementations of mutual exclusion carefully since improper implementations can easily result in a system that works 99.9% of the time yet still crashes on occasion.

2.2.3.1 Critical Section Method

The stack calls two entry points, ENTER_CRIT_SECTION and EXIT_CRIT_SECTION, when using the critical section method of mutual exclusion. Basically, any code which needs to serialize access to a shared data structure calls ENTER_CRIT_SECTION() before it starts to access the data structure and EXIT_CRIT_SECTION() after it completes its access to the data structure An example is shown in the code fragment below:

thread1()
{
   ...
   ENTER_CRIT_SECTION(q);
   queue an element to the head of a shared queue structure named q;
   EXIT_CRIT_SECTION(q);
   ...
}
thread2()
{
   ...
   ENTER_CRIT_SECTION(q);
   dequeue an element from the tail of a shared queue structure named q;
   EXIT_CRIT_SECTION(q);
   ...
}

When a given thread of execution returns from a call to ENTER_CRIT_SECTION(), other threads of execution are prevented from accessing the shared data structure until the first thread calls EXIT_CRIT_SECTION() to release its exclusive access to the data structure. In the example shown above, if thread1() were to call ENTER_CRIT_SECTION() first then thread2() would be prevented from accessing the queue named q until thread1() called EXIT_CRIT_SECTION().

It is the responsibility of the porting engineer to provide implementations of ENTER_CRIT_SECTION() and EXIT_CRIT_SECTION() that are appropriate for his target system and application. The porting engineer should consider the following issues before deciding how to implement these entry points:

On systems without a multitasking operating system and in which interrupt service routines (ISRs) never access shared data structures, explicit mutual exclusion is not needed. In these cases ENTER_CRIT_SECTION() and EXIT_CRIT_SECTION() can be no-ops.

On systems in which both ISRs and non-ISR code access shared data structures, ENTER_CRIT_SECTION() should be implemented as inline assembly code that pushes the current interrupt state onto the processor stack and disables interrupts. EXIT_CRIT_SECTION() should be implemented as inline assembly code that pops the processor stack to restore the interrupt state to the state that existed before the matching call to ENTER_CRIT_SECTION(). Note that it is not sufficient to simply disable interrupts in ENTER_CRIT_SECTION() and enable them in EXIT_CRIT_SECTION() because calls to ENTER_CRIT_SECTION() can be nested. This interrupt based implementation is the simplest for most target systems.

For systems that have hard real time requirements, disabling interrupts to implement these macros could present a problem. In cases like these, the porting engineer could implement his system such that the InterNiche stack shared data structures are never accessed by ISRs and in which ENTER_CRIT_SECTION() and EXIT_CRIT_SECTION() are implemented as functions which acquire and release a single operating system semaphore or mutex, respectively, keeping in mind the fact that the calls can be nested in the same thread of execution. Using this method, the OS tasks that access the InterNiche shared data structures can be executed at low priority and can be pre-empted by higher priority tasks and ISRs that support the system's hard real time requirements.

The example code shown above and the InterNiche stack code pass a parameter to ENTER_CRIT_SECTION() and EXIT_CRIT_SECTION(). In the current implementation of the stack, these parameters are not significant and should be ignored in the implementation of these macros.

2.2.3.2 Net Resource Method

The net resource method is usually a concern for target systems that have pre-emptive multitasking operating systems. For systems without multitasking, LOCK_NET_RESOURCE() and UNLOCK_NET_RESOURCE() can in most instances be implemented as no-ops.

For engineers who are porting the stack to a pre-emptive multitasking system, one way to think about the design of these functions is that they are intended to simulate the non-preemptive kernel context present in many implementations of the UNIX operating system. These NicheStack functions are based on the Berkeley implementation where they ran as device drivers in the BSD UNIX kernel context.

As such, the BSD Sockets and TCP code would never be pre-empted by a context switch because in Berkeley UNIX pre-emptive context switching is prohibited in kernel context. A similar requirement applies to InterNiche Sockets and TCP/IP stack implementations. Once a task has made a call into the stack, it must not be pre-empted by another task making a call into the stack until the first task exits from the stack, either by returning from its call or blocking in the tcp_sleep() function (described later). To facilitate this requirement all API calls where an application may call into the stack begin with a call to LOCK_NET_RESOURCE(NET_RESID) and end with a call to UNLOCK_NET_RESOURCE(NET_RESID). Also, a proper implementation of tcp_sleep() on a multitasking system will call UNLOCK_NET_RESOURCE(NET_RESID) before blocking the caller and will call LOCK_NET_RESOURCE(NET_RESID) upon returning from the block. Tasks making calls to the stack may be pre-empted by other higher priority tasks so long as the higher priority tasks do not make calls into Sockets, thus it is not necessary nor desirable to implement LOCK_NET_RESOURCE() by disabling context switching. The most natural implementation of LOCK_NET_RESOURCE() on a multitasking system is to have LOCK_NET_RESOURCE() pend on a properly initialized semaphore or mutex and have UNLOCK_NET_RESOURCE() post to it.

One of three resource identifiers are passed to these functions:

#define NET_RESID    0
#define RXQ_RESID    1
#define FREEQ_RESID   2

NET_RESID is passed to serialize access to the Sockets, TCP, UDP, and IP layers of the stack. RXQ_RESID is passed to serialize access to the received-packet queue structure, rcvdq. For example, the portable function pktdemux() locks RXQ_RESID when it dequeues packets from rcvdq. FREEQ_RESID is passed to serialize access to the free packet buffer queue structures via the portable pk_alloc() and pk_free() functions.

The porting engineer should consider the following issues before deciding how to implement these functions:

2.2.3.3 Net Resource Method, Walkthrough


The diagrams below illustrate how InterNiche's TCP/IP stack can serialize the access of many processes to the stack with a single Mutex or Semaphore.

The first diagram illustrates the portions of the Interniche code which are protected by the "NET_RESID" Object (the Mutex or Semaphore). Any thread which enters this code (for example, by making a sockets call) must acquire the NET_RESID mutex before entering. If the NET_RESID mutex cannot be acquired, the thread blocks; as per the definition of LOCK_NET_RESOURCE().

The second picture illustrates the FTP application making a sockets call. The process had to acquire the NET_RESID mutex early in the socket call to proceed into the protected code. The code inside the "Protected by" oval will never block or busy-wait.

The third picture illustrates what happens if the socket is not ready for return to the application, for example, the application called recv(), on a blocking socket, but no received data is ready. The TCP code calls the OS dependent call tcp_sleep(), which releases the NET_RESID mutex before suspending the thread.

While the FTP thread is suspended, the telnet thread initiates a socket call. This Thread may enter the stack (acquire NET_RESID) since the FTP thread released NET_RESID prior to suspending.

While the telnet thread is in the stack, the scenario we are protecting against occurs: A datagram arrives for the suspended FTP thread. The OS suspends the telnet thread and wakes the FTP thread. The FTP thread, however cannot acquire the NET_RESID mutex, since it's still owned by the telnet thread. It blocks inside the call to LOCK_NET_RESOURCE(NET_RESID) and is unable to return from tcp_sleep().

The OS resumes the telnet thread. In this example, it blocks (perhaps also waiting for received data) by calling tcp_sleep() - which frees the NET_RESID mutex. The FTP thread now acquires the NET_RESID mutex and returns from tcp_sleep().

The FTP thread is able to copy the received TCP data and return to the FTP application. From the application's perspective the thread was blocked "inside" the stack the whole time.

Similar mechanisms, each with its own mutex, are used to protect the queue of free packet buffers and the queue of received packet buffers. When the code inside the TCP stack uses buffers it will often hold two mutexes (NET_RESID and one of the buffer mutexes) at the same time, however the buffer queue mutex is only held for a few cycles. This allows the buffer queue mutexes to synchronize with ISR code by disabling hardware interrupts around accesses to the buffer queues.


2.2.4 Debugging Aids

dtrap() is a macro called by the stack code whenever it detects a situation which should not be occurring. The intention is for the dtrap() routine or macro to try to trap to whatever debugger may be in use by the porting engineer. Think of it as an embedded break point. For most Intel x86 processor debuggers, this can be done with an int 3 opcode. The macro below is effective if your Intel C compiler accepts inline assembly:

   #define dtrap();   _asm{ int 3 }

The stack code will generally continue executing after a dtrap(), but the dtrap() usually indicates that something is wrong with the port. NO PRODUCT BASED ON THIS CODE SHOULD BE SHIPPED UNTIL THE CAUSES OF ALL CALLS TO dtrap() HAVE BEEN ELIMINATED! When it comes time to ship code, the dtrap()s can be redefined to a null function to slightly reduce code size.

The next two primitives have the same function and syntax as printf(). They have separate names so that they can have their output redirected or be completely disabled. The first, initmsg(), is called by various stack routines to print out routine status messages during initialization. These messages are informational and not considered warnings. dprintf() is used throughout the stack code to print warning messages when something seems to be wrong.

In most ports, these can be mapped to printf() as shown while the product is under development. Note: This example works on Microsoft C, but some compilers will complain about this syntax since it ignores the fact that these names have parameters. You may have to experiment.

#define initmsg   printf    /* same parms as printf, called at boot time */
#define dprintf   printf   /* same parms as printf, called during run time */

For some products, it may make sense to define these away before FCS.

#define initmsg(...)   /* define to nothing */
#define dprintf(...)      /* define to nothing */

The last debugging tool in ipport.h_h is the #define NPDEBUG. Defining this will cause the debug code to be compiled into the build. This code does things like check for valid parameters and sensible configurations during runtime. It frequently invokes dtrap() or dprintf() to inform the programmer of detected problems. The porting engineer will want make sure this flag is defined during development. Unless PROM space is tight, it is OK to leave it defined for FCS - there will be no noticeable performance degradation from this code.

#define NPDEBUG   1   /* enable debug checks */

2.2.5 Timers and Multitasking

IP stacks require a clock tick for such things as TCP retry, ARP cache time-outs, etc. The InterNiche stack depends on an unsigned long variable named cticks. cticks should be regularly incremented by the port code between 5-100 times per second, wrapping back to 0 after reaching 0xFFFFFFFF. The stack code adjusts cticks for actual time by using the macro TPS (ticks per second). This needs to be defined in ipport.h_h. The DOS example is reproduced here:

extern unsigned long cticks;   /* clock tick counter */
#define TPS   18      /* cticks per second (DOS example) */

2.2.6 Stack Features and Options

The stack assumes you have at least one device for sending and receiving network packets. These are usually hardware devices such as Ethernet or serial ports, but they also may be logical devices such as loopback drivers or inter-process communication software. Many IP stacks on embedded systems support only one device (most commonly Ethernet), however devices like routers need two or more. The InterNiche stack supports multiple logical devices and has been used with up to three. The structures to manage these devices are statically allocated at compile time, so the maximum number of devices the system will use at runtime must be set in ipport.h_h via the constant MAXNETS.

/* define the maximum number of hardware interfaces */
#define MAXNETS   2   /* maximum entries of nets[ ] array */

The stack supports a fixed size IP routing table. The size of the routing table can be defined via the constant RT_TABS.

#define RT_TABS   16   /* number of entries in IP routing table */

The stack maintains two packet buffer pools, one for big packets and one for little packets. The constants below define the maximum number of big and little packet buffers that can be allocated by the stack code.

#define MAXBIGPKTS   50
#define MAXLILPKTS   50
#define MAXPACKETS   (MAXLILPKTS+MAXBIGPKTS)

2.2.7 Package Options

Near the beginning of ipport.h_h are a number of defined constants that are bounded by comments as shown below.

/* options to trade off features for size. Do not enable options
 * for modules you don't have or your link will get unresolved
 * externals.
 */

#define INCLUDE_ARP  1   /* use ethernet ARP */
...
/* end of option list */

These defined constants specify which features and applications are to be enabled in the stack. For example, if the constant INCLUDE_ARP is defined then the stack will use the ARP protocol for physical layer address resolution on interfaces that support the use of ARP. Each InterNiche customer receives a different set of enabled options in the version of ipport.h_h that is shipped in the target system dependent directory that the customer has selected that is dependent on which features and products he has ordered. The porting engineer can disable an option that is not needed by his application by moving the defined macro to the #define NOT_USED section. Note: The setting the value of the macro to 0 does NOT disable the feature, as the Interniche code does NOT test the value of a macro, instead it checks if the macro has been defined or not. Disabling options can result in reduced target system memory requirements. The available options are described below:

INCLUDE_ARPIf defined, the stack will use the ARP protocol to perform physical address resolution on those interfaces that support ARP. If not defined, the ARP protocol will not be performed.
FULL_ICMPIf defined, the stack will implement the entire ICMP protocol. If not defined, the stack will implement only the ping protocol.
INCLUDE_TCPIf defined, the stack will include support for the TCP protocol.
TCP_ZEROCOPYIf defined, the stack will include support for the TCP Zero-Copy API extensions.
NPDEBUGDefining this constant causes debug messages to be displayed on the system console.
IP_FRAGMENTSIf defined, the stack will attempt to reassemble IP packet fragments that are destined for the target system. If not defined, the stack will silently discard fragmented IP packets that are destined for the target system. This options also controls whether the stack will generate fragmented IP packets.
NB_CONNECTIf defined, the stack will include code to support non-blocking connection attempts.
MUTE_WARNSIf defined, the stack will generate extra code if that code is necessary to suppress compiler warnings.
IP_MULTICASTSupports end node IP Multicasting.
IP_ROUTINGControls whether IP layer packet routing will be performed on multihomed hosts.
MULTI_HOMEDThis constant should be defined if the target system can have more than one physical interface, for example, an Ethernet and a serial link. This constant should be undefined on targets with a single interface to minimize target memory usage.
DYNAMIC_IFACESIf defined, the stack will include support for creation and deletion of network interfaces at run-time, after the stack has been initialized and is up and running. If not defined, the stack will support only network interfaces created during its startup (by the prep_ifaces() function).
DHCP_CLIENTIf defined, the stack will implement the client side of the DHCP protocol during system startup.
PING_APPThis constant should be defined if the target system generates ICMP ping/echo requests. It is not necessary for this constant to be defined in order for the client to respond to echo requests from other hosts.
NET_STATSIf defined, the CUI will include to ability to display statistics.
DNS_CLIENTIf defined, the stack will include code to perform DNS lookups of host names.
IN_MENUSIf defined, the stack will include the CUI.
UDPSTESTIf defined, the Sockets based UDP echo client and server will be included in the target.
FTP_SERVERIf defined, the stack will include an FTP server.
FTP_CLIENTIf defined, the stack will include an API that allows the calling application to generate the client side of the FTP protocol.
WEBPORTIf defined, the stack will include the InterNiche HTTP server. This option is functional if you have purchased the InterNiche WebPort Server software.
INCLUDE_SNMPIf defined, the stack will include hooks for an SNMP agent. Note that the SNMP agent code is a separate InterNiche product.
SNMP_SOCKETIf defined, the stack will implement the SNMP agent using the standard Sockets API. This is useful for customers who wish to use the InterNiche SNMP product without the InterNiche TCP/IP product.
USE_PPPIf defined, the stack will implement the PPP protocol. This option is functional if you have purchased the Interniche PPP software.
USE_MODEMIf defined, the stack will support call setup and hangup using a Hayes compatible modem. This option is functional if you have purchased the Interniche PPP software.
NATRTIf defined, the stack will include hooks for the InterNiche NAT router. This option is functional if you have purchased the Interniche NAT Router software.
SMTP_ALERTSIf defined, the stack will generate email alerts to a configured user upon the detection of various runtime exception and error conditions. This optional is function if you have purchased the Interniche SMTP Client software.
IP_LOOPBACKIf defined, the stack will loopback packets destined for the IP loopback address at the bottom of the IP layer without being queued to a network interface.
MAC_LOOPBACKIf defined, the stack will loopback packets destined for the IP loopback address via a separate loopback network interface.
DHCP_SERVERIf defined, the stack will include a DHCP server. For this option to be functional, the InterNiche DHCP Server software will have to have been purchased.
TCP_ECHOTESTIf defined, the TCP echo client and server will be included in the target.
BOOTPTABDHCP supports a UNIX-ish bootptab file.
USE_SLIPIf defined, the stack will support the SLIP protocol.
NO_UDP_CKSUMIf defined, UDP checksums will not be generated for transmitted UDP packets and will not be verified on received UDP packets.
TELNET_SVRIf defined, the stack will include hooks for a Telnet protocol server.
NATIVE_PRINTFThe misclib directory contains an implementation of formatted output functions that perform a function similar to printf(). If you intend to use these functions instead of the formatted output function provided by your C compiler, then this constant should be undefined. If you intend to use your C compiler's printf() for formatted output, then this constant should be defined.
RIP_SUPPORTIf defined, the stack will include a RIP server.
MEM_BLOCKSThe stack includes a rudimentary memory management system for use in those targets whose C library packages lack one. This constant should be defined for those targets that intend to use this memory management system.
SEG16_16This constant should be defined on Intel x86 based targets that use the real mode large memory model.
INICHE_MEMThe stack includes implementations of several C library functions like memcpy(), memset(), and memcmp(), for targets whose C libraries do not include these functions. Define this constant if your C library does not include implementations of these functions.
INICHE_LIBSThe stack includes implementations of several C library functions that operate on strings (strlen(), strcpy(), etc.) for targets whose C libraries do not include these functions. Define this constant if your C library does not include implementations of these functions.

There are also several package options that have meaning only on DOS target systems. They are:

USE_ODIIf defined, the stack will interface to a Novell Netware style ODI driver for Ethernet access.
DOS_TSRThis constant should be defined if the TCP/IP stack is to be loaded as a DOS TSR. If not defined, the stack software will be linked directly to any application code above the Sockets API.
MS_DOSThis should be defined for TSR implementations on Microsoft DOS systems.
GENSOFTThis should be defined for TSR implementations on General Software embedded DOS.
AMDISA_LANCEIf defined, the stack will interface to an AMD Lance ISA Ethernet card for Ethernet access.
AMDISA_MACEIf defined, the stack will interface to an AMD MACE ISA Ethernet card for Ethernet access.
USE_PKTDRVIf defined, the stack will interface to an FTP software style Packet Driver for Ethernet access.

2.2.8 Error Codes

The following error codes are used throughout the InterNiche transport stacks, including the UDP/IP stack provided with the basic portable agent kit. Generally, full success is 0; definite errors are negative numbers and indeterminate conditions are positive numbers. These codes are provided in ipport.h_h so that they can be modified to wrap around an existing system, within the guidelines mentions above. Please do NOT make 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.3 The "glue" Layer

Once the ipport.h_h file has been implemented as described in the previous section, the next step is to code the glue layers. These are the routines which map the generic service requests that the target system independent code makes to specific services your target system provides. Many may have already been handled through #define mapping in ipport.h_h. Ideally many more will have been implemented in the code contained in the target system dependent directory that best fits the description of your target system.

Usually the most complex part of the glue layers is the network hardware interface, described in the Network Interfaces, Section 3.2. ipport.c should use a routine named prep_ifaces() which initializes the pre-allocated network structure(s) to point to the interface routines, fills in hardware specific parameters, and sets up MIB-II structures. This routine calls the function pointed to by port_prep for initializing the interface specific to the target.

Most of the glue layer is described in the Chapter 3, "Porting Engineer Provided Functions". Every function in that section should be either coded or #defined to a system function in ipport.h_h.

2.4 Task Control

2.4.1 Multitasking vs. Superloop

NicheStack needs to obtain CPU cycles to process received packets and handle time-outs on a timely basis. The stack (and all Internet applications) support two methods of doing this; the "superloop" method (used in the DOS demo), which involves regularly polling a central routine, and the sleep()/wake() method (preferred by multitasking systems) where a network task is blocked (put to sleep) until its services are required (usually because packets are received). The porting engineer will need to choose the method which best fits with the target system and implement the appropriate logic, generally just a few lines of code in main.c (or equivalent) file and possibly in tcpport.c.

2.4.1.1 The Superloop Method

Our DOS demo, like some simpler embedded systems, has no real multitasking system available to it. DOS Programs of any sophistication obtain control from DOS after they are loaded and run until the user tells them to stop. Internally they are in an infinite loop waiting for new input to act on. We refer to this internal loop as the "superloop".

Our DOS demo works exactly this way. At the end of the main() function in dosmain/main.c , after calling all the initialization routines, it falls into an infinite loop calling the routine tk_yield(). This routine polls all the linked modules (the IP stack, servers, modem drivers, etc.) and returns. Each of these modules is called via a portable routine which checks for work to be done by that module, processes any work and returns. In the case of the IP stack, tk_yield() calls the non-portable packet_check() (to guard against re-entry), which in turn calls pktdemux() - the portable routine which dequeues received packets.

By rapidly looping forever in this manner, the superloop yields excellent performance on task-less systems. The drawback is that it wastes CPU cycles polling routines when they have no work to do.

2.4.1.2 Multitasking

If the target system has a multitasking operating system (OS), the preferred method of processing received packets and timer events is to create a "network" task at boot time whose job will be to process received packets and network timer ticks. This task's code will look similar to the main() routine in the DOS demo, however the loop which calls tk_yield() will be replaced with a call to an operating system function that blocks on some sort of event, followed by a call to pktdemux(). The code which implements network device drivers should then post the event on which the network task has blocked whenever it has enqueued a received packet into the queue rcvdq. For an example of how to enqueue the packet and post the event, see the use of the SignalPktDemux() macro in net/macloop.c. Timer events may also unblock the task so it can make the required calls to the various timer routines.

InterNiche has sample "main task" software for several popular commercial embedded RTOS systems. Call us for example source code. If we don't have a port for your exact OS, we probably have something quite like it.

2.4.2 TCP

Once the ip and net directory sources have compiled successfully and the glue routines coded, there is usually one more pair of related functions to write. The TCP portion of the stack needs a mechanism to wait briefly if resources (usually free buffers) run short or the other host on a connection runs too slowly. The mechanism for this are the functions tcp_sleep() and tcp_wakeup(). If TCP is not being used in the target system, the porting engineer doesn't need to implement these. Otherwise he will need to map these into whatever temporary block or sleep function the target system OS provides.

NOTE: Prior to 1997 versions of the code, these were names sleep_chan() and wakeup_chan(). The names were changed to lessen the chances of conflict with other system symbols.

Programmers familiar with the UNIX kernel sleep() and wakeup() will notice tcp_sleep() and tcp_wakeup() can be mapped directly to the UNIX sleep() and wakeup(), respectively.

tcp_sleep() and tcp_wakeup() both take as a parameter a pointer to some location in memory (the parameter is typed as a void *, a pointer to unstructured data). The contents of the memory addressed by the pointer are not significant to the functions and should not be modified by them. What is significant is the address itself. The semantics of these functions in a multitasking environment are such that:

The period of time between making a call to tcp_sleep() and that call's return can be up to, or slightly after a tcp_wakeup() call is made to the same address.

It is useful to elaborate on some of the fine points of the above semantic definition. The InterNiche stack code interprets a return from tcp_sleep() to mean that some significant event (e.g. a packet has been received or a timer has expired) MIGHT have occurred to cause the calling task to continue execution. The calling task always checks other internal variables to determine which, if any, events actually occurred and, if it determines that no significant event occurred, the call to tcp_sleep() is repeated. This means it is perfectly acceptable for tcp_sleep() to return before a corresponding call to tcp_wakeup() is executed. Thus, a simple implementation of tcp_sleep() could be to simply delay the calling task for an OS system clock tick. tcp_wakeup() in this case would be a no-op. This simple implementation would result in some wasted CPU cycles, but if the network task is executed at low priority, this would not significantly affect overall system performance.

If minimizing CPU cycles in the target system is a requirement, a more sophisticated implementation of tcp_sleep() and tcp_wakeup() would include an algorithm that mapped the addresses passed as parameters to these functions to OS events or other IPC objects. A call to tcp_sleep() would result in a block on an OS event and a call to tcp_wakeup() with the same parameter would result in the posting or generation of the same OS event.

On the DOS demo port, tcp_sleep() calls our basic "superloop" function, tk_yield(), and tcp_wakeup() is a no-op. This is the simplest possible of round robin process, yet it gives excellent TCP performance. See the source code for details. On windows, tcp_sleep() can be a simple windows message loop, as long as packet_demux() has a chance to receive incoming packets.

2.5 Data Structures

This section describes various data structures the contents of which are important to understand in order to do a port.

2.5.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 = 8;      /* number of small bufs to init */
unsigned lilbufsiz = 200;      /* big enough for average packet */
unsigned bigbufs = 8;      /* 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_prot;      /* 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.5.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 */
#ifdef DYNAMIC_IFACES
   int (*n_setstate)(struct net *, int);
                           /* set link state up/down */
#endif   /* DYNAMIC_IFACES */
#ifdef IP_MULTICAST
   /* register a multicast list */
   int (*n_mcastlist)(struct in_multi *);
#endif   /* IP_MULTICAST */
};

The stack maintains a queue of active net structures, netlist. For compatibility with previous releases and stack-related code that uses interface indexes to access information about network interfaces, it also maintains an array of pointers to these structures, nets[ ], which is sized by the MAXNETS constant that is defined in ipport.h_h. MAXNETS therefore sets an upper limit on the number of network interfaces that the stack can support.

The stack also declares storage for the net structures that are to be used to represent static network interfaces. Static network interfaces are defined at stack startup time, initialized from the prep_ifaces() function, and will persist for the lifetime of the stack. This array, netstatic[ ], can be loaded with default IP address configuration before ip_startup() is called. This array is sized by the STATIC_NETS constant that can be defined in ipport.h_h. STATIC_NETS therefore sets an upper limit on the number of static network interfaces. Note that if STATIC_NETS is not defined, it will default to MAXNETS for compatibility with previous releases of the stack.

These arrays are shown below:

 struct net netstatic[STATIC_NETS];
 struct net *nets[MAXNETS];
 queue netlist;

Some of the fields in the net structure are initialized and used by the IP layer of the stack. The porting engineer does not need to deal with these fields. The stack expects the rest of these fields to be initialized by the code that implements the network layers. The porting engineer should make sure that these fields are properly initialized with values that are appropriate for the target system. The function prep_ifaces() is called early on during stack initialization. It contains calls to functions that initialize various standard network devices that are supplied with the InterNiche stack. It also calls port_prep (if port_prep is initialized). It is expected that the porting engineer will either use the standard devices on his target system or initialize port_prep, so that a similar interface preparation function gets called, the purpose of which is to initialize the necessary fields of the net structure that is assigned to the interface.

The fields of a net structure and their required initialization are described below:

struct net * n_next;      /* pointer next net */

n_next is used by the stack to maintain the netlist queue, and should not be modified by the application.

char name[IF_NAMELEN];   /* device ID name */

name should contain a short printable null-terminated character string that identifies the network interface. The stack and network interface drivers (e.g. the n_stats function pointer, described below) can use this for status displays and locating configuration information for the interface.

int (*n_init)(int);         /* net initialization routine */

n_init should point to a network interface initialization function that the stack will call once after all the net structures have been prepared. The purpose of this function is described in the section entitled "Network Interfaces".

int (*raw_send)(struct net *, char*, unsigned);   /* put raw data on media */
int (*pkt_send)(struct netbuf *);         /* send packet on media */

raw_send or pkt_send should be set to point to a function which transmits packets over the network interface. One or the other should be chosen with the other set equal to NULL. When the stack needs to transmit a packet on the network interface it will call whichever function is not NULL to do so. The decision of which field to use for a particular port depends on the nature of the network interface hardware. When the stack calls the pkt_send() function, the stack leaves it up to the function to free the PACKET structure when it is done with it (PACKET structures, which describe a packet to be transmitted, are described later). When the stack calls the raw_send() function, the stack frees the PACKET structure once the call returns. Implementations of new interface drivers on modern hardware should implement a pkt_send() function. The raw_send() function is a legacy interface that was used with older network interfaces. The functional requirements of these functions are described in the section entitled "Network Interfaces".

int   (*n_close)(int)      /* net close routine */

n_close should point to network interface shutdown function that the stack will call once during target system software termination. On embedded targets in which the software never terminates, there is no need for the function addressed by n_close to do anything.

int   (*n_reg_type)(unshort, struct net*);

With some types of standard network interface device drivers (Ethernet device drivers in particular) it is necessary to register the link layer protocol types that will be used with the device. The InterNiche stack currently registers two types: IP_TYPE (0x800) and ET_ARP (0x806). If your target system will be using a standard network interface device driver in which this registration is required, then the n_reg_type field should point to a function that does the necessary registration of the type with the driver.

void (*n_stats)(void * pio,int iface);

The user interface provided with the stack allows the user to display various statistics that are useful during debugging and testing. n_stats should point to a function that displays whatever statistics are deemed pertinent for this purpose.

int n_lnh;      /* net's local net header size */

n_lnh should contain the length in bytes of the network interface's link layer header. For example, with standard (non-IEEE 802.2/802.3) Ethernet devices n_lnh is set to ETHHDR_SIZE. Ethernet devices use the macro ETHHDR_SIZE, which is usually defined to 14 in the ipport.h_h file. On systems that require alignment, one mechanism of handling alignment is to make the ETHHDR_SIZE be 16 bytes, which includes an ETHHDR_BIAS of 2 bytes, and on received packets locate data at this ETHHDR_BIAS so that the IP header starts at a 4 byte boundary.

int n_mtu;      /* net's largest legal buffer size */

n_mtu should contain the length in bytes of the network interface's Maximum Transmission Unit or MTU. The MTU defines the maximum size of link layer frame that can be transmitted on the network interface. The size specified n_mtu should include the length of any link or MAC layer header that is used to encapsulate the IP packets that are transmitted.

ip_addr  n_ipaddr;      /* interface's internet address */

If a network interface's IP address is to be determined statically (that is no IP address resolution protocol such as DHCP is to be used) then n_ipaddr should be set to the interface's IP address expressed in network order (for a description of network order, see the section titled CPU Requirements). This should be performed before the call to ip_startup() to initialize the IP stack. If DHCP is to be used to dynamically determine the interface's IP address, n_ipaddr should be initialized to 0. See the section titled "Dynamic Internet Configuration with DHCP" for a description of how to use DHCP to determine an interface's IP address.

int n_snbits ;      /* number of subnet bits */

n_snbits is currently unused.

ip_addr  snmask;      /* interface's subnet mask */
ip_addr  n_defgw;      /* the default gateway for this net */

If a network interface's IP address is to be determined statically then snmask and n_defgw should be set to the interface's subnet mask and default gateway expressed in network order. This should be performed before the call to ip_startup() to initialize the IP stack. If DHCP is to be used to dynamically determine the interface's IP address, then these fields can be left unassigned because they will be set by the DHCP protocol.

Each target system should have at most a single default route (a.k.a. default gateway) specified. If the target has a default route then that route should be defined in the n_defgw field of the net structure associated with the network interface that is connected to the host that serves as the default gateway and the n_defgw fields of all other net structures should be set to 0. If the target has no default route then all n_defgw fields should be set to 0.

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 */

n_netbr, n_netbr42 and n_subnetbr contain various representations of network broadcast addresses. They are initialized and used internally by the IP stack and under normal conditions do not need to be modified by the porting engineer.

unsigned n_hal;      /* Hardware address length */

n_hal should be set to the length in bytes of the network interface's MAC address. For example, with Ethernet n_hal is set equal to 6.

IFMIB n_mib;      /* pointer to interface(if) mib structure */

IFMIB is typed to point to an IfMib structure. The stack's initialization code sets n_mib to point to an IfMib structure that is associated with the interface. The IfMib structure that is used to store SNMP MIB information that is associated with the interface is discussed in the section titled "The IfMib Structure".

void *n_local;      /* pointer to custom info, null if unused */

n_local is available for use by porting engineers who need to implement network interfaces. The stack code does not use it.

void *n_flags;      /* mask of the NF_ bits below */

n_flags is a mask of bit flags used by the stack and by network interface drivers to indicate various per-interface attributes. NF_DYNIF is set by the stack, and indicates that the network interface is a dynamic network interface created by the ni_create() API function. The NF_NBPROT flag is set by the network interface driver. NF_BCAST and NF_MCAST should be set by network interface drivers if the device can support broadcast and multicast operations, respectively.

int (*n_setstate)(struct net *, int);      /* set link state up/down */

n_setstate is available if the stack has been built with the DYNAMIC_IFACES option defined, and may be set by the network interface driver to point to a driver-provided function that sets the link state for the interface.

int (*n_mcastlist)(struct in_multi *);      /* register a multicast list */

n_mcastlist is available if the stack is built with the IP_MULTICAST option defined, and may be set by the network interface driver to point to a driver-provided function that accepts a list of multicast addresses for the interface.

2.5.3 The IfMib Structure

The IfMib structure is used to maintain the "Interface" MIB as defined in RFC1156. The structure contains a set of fields whose names and purposes match the Interface MIB definition that is contained in the RFC. The stack allocates an IfMib structure for each network interface that is accessible via the n_mib field of the interface's net structure. Since the RFC is readily available to define the contents of the MIB, the contents of the IfMib structure are not further defined in this document. It is the responsibility of a network interface implementation to maintain the various fields that are defined in the MIB, particularly if the target system is to support SNMP agent functionality.

2.6 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.6.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". The InterNiche stack provides implementations of several network interfaces that can be useful on some embedded systems. These implementations include interface preparation functions that when called will initialize most of the fields of the interface's net structure. These 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 each of the target system's interfaces. 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.6.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 porting 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.6.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. An unsuccessful return indicates a bug someplace.

The following list summarizes what ip_startup() does:

2.6.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:

It is then necessary to service the stack so that the DHCP state machine can transmit and receive the DHCP packets that will configure the interface. How this servicing of the stack is performed is dependent on the nature of the target system. This process is discussed in the section titled "Servicing the Stack".

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.6.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 NicheStack, this is the point at which to call their initialization functions.

There are however a few application level services that are part of NicheStack. 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.

2.6.6 Dynamically-Created Network Interfaces

Some target systems do not have a fixed network hardware configuration: the network interface hardware configuration may be changeable, or network interface hardware may need to be reassignable to other purposes. The stack can be built with the DYNAMIC_IFACES option defined to add support for this. Building with this option defined will include several additional API functions in the stack to support the creation, configuration, control, and deletion of new network interfaces.

ni_create()Creates a new network interface.
ni_set_config()Sets configuration information for a network interface.
ni_get_config()Gets configuration information for a network interface.
ni_set_state()Sets the state of a created network interface
ni_delete()Deletes a previously-created network interface.

These functions are described in section 4.6, Dynamic Network Interfaces.

2.7 Servicing the Stack

After initialization is complete, the stack needs to be serviced in order to run properly. The section titled "Task Control" provides some conceptual background for this topic. Whichever tasking method is chosen for a given target, superloop or multitasking, the following functions need to be called on a regular basis in order for the stack and various application services it provides to function properly. It can be useful to inspect the implementations of the tk_yield() function in the various target system dependent directories in order to better understand these stack servicing functions. The functions listed below all take no parameters and return no useful value.

kbdio()Periodic calls to this function drive the character orient user interface that comes with the stack. The function polls the system console, whatever device that has been configured to be, for user input and when an entire command has been entered, executes the command.
pktdemux()When this function is called the packet receive queue, rcvdq, is read and the packets contained in it are demultiplexed and passed up to higher layers of the stack. On superloop based systems, this function is usually called in every iteration of the superloop so that received packets are processed in a timely manner. On multitasking systems, this function often gets called from its own task in response to a call to SignalPktDemux().
ping_check()Calls to this function drive a state machine that handles responses to ICMP echo requests (pings) that originate on the target system. If it is desirable to be able to ping other hosts from the target system using the user interface, ping_check() should be called on a regular basis to handle the responses to these pings.
tcp_tick()The TCP protocol relies on timers. A call to tcp_tick() should be made at least once every system tick in order to service the TCP timers.
udp_echo_poll()If the UDP echo server is configured to run, udp_echo_poll() should be called on a regular basis to give this application service some CPU cycles in which to run.
tcp_echo_poll()If the TCP echo server is configured to run, tcp_echo_poll() should be called on a regular basis to give this application service some CPU cycles in which to run.
ip_frag_check()Periodic calls to this function allow the IP stack to discard partially reassembled IP packets in which some but not all IP fragments have been received within a time out period.
dhc_second()Periodic calls to this function drive the DHCP state machine described in the section "Dynamic Internet Configuration with DHCP". It does not need to be called more often than about once per second.
dns_check()Periodic calls to this function are necessary for the DNS lookup state machine to function properly. It does not need to be called more often than about once per second.

2.8 Applications and Testing

Once your ipport.h file is set up and your glue layers are coded, you are ready to test your stack. The traditional first test of most IP stacks is ping, the popular term for ICMP echo packets. All client stacks support this; just go to one of your workstations and try it. At a DOS or UNIX shell prompt, the usual command is "ping 207.45.67.9" (the number is an example IP address, use the one you assigned to your stack's network interface). The ping program will send a network packet to the InterNiche stack, which will echo it back. This indicates that the packet got from the net media to your interface, that your interface's IP information is properly configured (at least to some extent), that your IP layer is receiving from the interface and that the ICMP layer is attached to IP. It also indicates the reverse of all this works: ICMP can send to IP, which can send to the interface, which can send on the physical net. If you are using Ethernet, ARP has probably worked too. If ping works, then most of your port is done!

What other tests you can run depends on your product. InterNiche sells FTP servers, SNMP agents, and Web Servers for embedded systems. If you have these you can refer to the manuals that came with them for implementation and testing. If not, then the TCP and UDP echo servers can be used to test the TCP and UDP layers of the stack and to test the target's handling of volume network traffic. To use these servers, you will likely need to write some client test applications on a PC or workstation that can be used to connect to and interact with the servers. The nature of the echo protocol can make the implementation of these clients very simple.

In any case you should now have a working TCP/IP stack. Hopefully your porting was fast, easy, and fun. If you have any suggestions as to how this manual or NicheStack could be easier to understand or port, please contact us with them. Contact information is on the front cover of this document.