2 Network Application Development¶
This chapter describes how to begin developing network applications. It discusses the issues and guidelines involved in the development of network applications using the NDK libraries.
2.1 Configuring the NDK with C Code¶
To configure your application’s use of the NDK and its components, you can use either C code or the XGCONF configuration tool within CCStudio. Choose one method or the other to configure your application.
C Code. Configure the application by writing C code that calls
CfgNew()to create a configuration database and otherCfg*()functions to add various settings to that configuration database. Some additional configuration is done in the linker command file. This configuration method is recommended because it can be used with any RTOS supported by the SDK. If you are using this configuration method, see the subsections that follow.XGCONF. Use graphical displays within CCStudio to enable and set properties. XGCONF can also be used to configure objects used by the TI-RTOS Kernel. This configuration method can be used only if your RTOS is the TI-RTOS Kernel and you are using CCStudio to develop your application. If you are using XGCONF for configuration, see Configuring the NDK with XGCONF.
NOTE: Do not mix configuration methods. If a project uses both methods, there will be conflicts between the two configurations.
2.1.1 Required RTOS Objects¶
The NDK uses the OS adaptation layer to access the RTOS (TI-RTOS Kernel or FreeRTOS) and the HAL layer to access the hardware. These layers require the following RTOS object to be created in order for the NDK to work properly. This requirement can only be altered by modifying the code for the OS and HAL layers.
- Timer object. The timer driver in the HAL requires that an RTOS Timer object be created to drive its main timer. The Timer must be configured to fire every 100mS, and call the timer driver function
llTimerTick(). See “Constructing a Configuration for a Static IP and Gateway” for an example fromndk.cthat creates and starts a timer object.
(If you use XGCONF to configure the NDK, this object is created automatically.)
2.1.2 Include Files¶
If you are using the Cfg*() functions to add settings to the configuration database, you must add the appropriate directory to your include path. See the NDK Include File Directory section for more details.
(If you use XGCONF to configure the NDK, the correct include file directory is automatically referenced by your CCStudio project.)
2.1.3 Library Files¶
If you are using the Cfg*() functions for configuration, you are responsible for linking the correct libraries into your project. If you are using CCStudio to manage your application, you can add the desired library files directly to the CCStudio project. This way, the linker will know where to find them.
(If you use XGCONF to configure the NDK, the correct libraries are linked with the application automatically.)
2.1.4 System Configuration¶
If you are using the Cfg*() functions for configuration, you must create a system configuration in order to be able to use the NETCTRL API. The configuration is a handle-based object that holds a multitude of system parameters. These parameters control the operation of the stack. Typical configuration parameters include:
- Network Hostname
- IP Address and Subnet Mask
- IP Address of Default Routes
- Services to be Executed (DHCP, DNS, HTTP, etc.)
- IP Address of name servers
- Stack Properties (IP routing, socket buffer size, ARP timeouts, etc.)
The process of creating a configuration always begins with a call to CfgNew() to create a configuration handle. Once the configuration handle is created, configuration information can be loaded into the handle in bulk or constructed one entry at a time.
Loading a configuration in bulk requires that a previously constructed configuration has been saved to non-volatile storage. Once the configuration is in memory, the information can be loaded into the configuration handle by calling CfgLoad(). Another option is to manually add individual items to the configuration for the various desired properties. This is done by calling CfgAddEntry() for each individual entry to add.
The exact specification of the stack’s configuration API appears in the Initialization and Configuration section of the NDK API Reference Guide. Some additional examples are provided in the Configuration Examples section of this document and in the NDK example programs.
2.1.4.1 Configuration Examples¶
This section contains some sample code for constructing configurations using the Cfg*() functions.
2.1.4.1.1 Constructing a Configuration for a Static IP and Gateway¶
The ndkStackThread() function in this example consists of the main initialization thread for the stack. It creates a new configuration, configures IP, TCP, and UDP, and then boots up the stack.
It performs the following actions:
- Create and start a timer object that will be used by the NDK by calling the POSIX
timer_create()andtimer_settime()functions. Internally, these POSIX functions may use any supported RTOS, such as TI-RTOS Kernel or FreeRTOS. - Initiate a system session by calling
NC_SystemOpen(). - Create a new configuration by calling
CfgNew(). - Configure the stack’s settings for IP, TCP, and UDP.
- Configure the stack sizes used for low, normal, and high priority tasks by calling
CfgAddEntry(). - Boot the system using this configuration by calling
NC_NetStart(). - Free the configuration on system shutdown (when
NC_NetStart()returns) and callCfgFree()andNC_SystemClose(). - Call
timer_delete()to delete the timer object.
static void *ndkStackThread(void *threadArgs)
{
void *hCfg;
int rc;
timer_t ndkHeartBeat;
struct sigevent sev;
struct itimerspec its;
struct itimerspec oldIts;
int ndkHeartBeatCount = 0;
/* create the NDK timer tick */
sev.sigev_notify = SIGEV_SIGNAL;
sev.sigev_value.sival_ptr = &ndkHeartBeatCount;
sev.sigev_notify_attributes = NULL;
sev.sigev_notify_function = &llTimerTick;
rc = timer_create(CLOCK_MONOTONIC, &sev, &ndkHeartBeat);
if (rc != 0) {
DbgPrintf(DBG_INFO, "ndkStackThread: failed to create timer (%d)\n");
}
/* start the NDK 100ms timer */
its.it_interval.tv_sec = 0;
its.it_interval.tv_nsec = 100000000;
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 100000000;
rc = timer_settime(ndkHeartBeat, 0, &its, NULL);
if (rc != 0) {
DbgPrintf(DBG_INFO, "ndkStackThread: failed to set time (%d)\n");
}
rc = NC_SystemOpen(NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT);
if (rc) {
DbgPrintf(DBG_ERROR, "NC_SystemOpen Failed (%d)\n");
}
/* create and build the system configuration from scratch. */
hCfg = CfgNew();
if (!hCfg) {
DbgPrintf(DBG_INFO, "Unable to create configuration\n");
goto main_exit;
}
/* IP, TCP, and UDP config */
initIp(hCfg);
initTcp(hCfg);
initUdp(hCfg);
/* config low priority tasks stack size */
rc = 2048;
CfgAddEntry(hCfg, CFGTAG_OS, CFGITEM_OS_TASKSTKLOW, CFG_ADDMODE_UNIQUE,
sizeof(uint32_t), (unsigned char *)&rc, NULL);
/* config norm priority tasks stack size */
rc = 2048;
CfgAddEntry(hCfg, CFGTAG_OS, CFGITEM_OS_TASKSTKNORM, CFG_ADDMODE_UNIQUE,
sizeof(uint32_t), (unsigned char *)&rc, NULL);
/* config high priority tasks stack size */
rc = 2048;
CfgAddEntry(hCfg, CFGTAG_OS, CFGITEM_OS_TASKSTKHIGH, CFG_ADDMODE_UNIQUE,
sizeof(uint32_t), (unsigned char *)&rc, NULL);
do {
rc = NC_NetStart(hCfg, networkOpen, networkClose, networkIPAddr);
} while(rc > 0);
/* Shut down the stack */
CfgFree(hCfg);
main_exit:
NC_SystemClose();
/* stop and delete the NDK heartbeat */
its.it_value.tv_sec = 0;
its.it_value.tv_nsec = 0;
rc = timer_settime(ndkHeartBeat, 0, &its, &oldIts);
rc = timer_delete(ndkHeartBeat);
DbgPrintf(DBG_INFO, "ndkStackThread: exiting ...\n");
return (NULL);
}
2.1.4.1.2 Constructing a Configuration using the DHCP Client Service¶
This section examines the initIp() function from ndk.c that was called in the previous section. The function tells the stack to use the Dynamic Host Configuration Protocol (DHCP) client service to perform its IP address configuration.
Since DHCP provides the IP address, route, domain, and domain name servers, you only need to provide the hostname. See the NDK API Reference Guide for more details on using DHCP.
The code below performs the following operations:
- Add a configuration entry for the local hostname using the hostName, which is declared as
static char *hostName = "tisoc"; - Set the elements of dhcpc, which has a structure of type
CI_SERVICE_DHCPC. This structure is described in the NDK API Reference Guide. - Add a configuration entry specifying the DHCP client service to be used.
static void initIp(void *hCfg)
{
CI_SERVICE_DHCPC dhcpc;
unsigned char DHCP_OPTIONS[] = { DHCPOPT_SUBNET_MASK };
/* Add global hostname to hCfg (to be claimed in all connected domains) */
CfgAddEntry(hCfg, CFGTAG_SYSINFO, CFGITEM_DHCP_HOSTNAME, 0,
strlen(hostName), (unsigned char *)hostName, NULL);
/* Use DHCP to obtain IP address on interface 1 */
/* Specify DHCP Service on IF specified by "IfIdx" */
memset(&dhcpc, 0, sizeof(dhcpc));
dhcpc.cisargs.Mode = CIS_FLG_IFIDXVALID;
dhcpc.cisargs.IfIdx = 1;
dhcpc.cisargs.pCbSrv = &serviceReport;
dhcpc.param.pOptions = DHCP_OPTIONS;
dhcpc.param.len = 1;
CfgAddEntry(hCfg, CFGTAG_SERVICE, CFGITEM_SERVICE_DHCPCLIENT, 0,
sizeof(dhcpc), (unsigned char *)&dhcpc, NULL);
}
2.1.4.1.3 Using a Statically Defined DNS Server¶
The area of the configuration system that is used by the DHCP client can be difficult. When the DHCP client is in use, it has full control over the first 256 entries in the system information portion of the configuration system. In some rare instances, it may be useful to share this space with DHCP.
For example, assume a network application needs to manually add the IP address of a Domain Name System (DNS) server to the system configuration. When DHCP is not being used, this code is simple. To add a DNS server of 128.114.12.2, the following code would be added to the configuration build process (before calling NC_NetStart()).
uint32_t IPTmp;
// Manually add the DNS server "128.114.12.2"
IPTmp = inet_addr("128.114.12.2");
CfgAddEntry( hCfg, CFGTAG_SYSINFO, CFGITEM_DHCP_DOMAINNAMESERVER,
0, sizeof(IPTmp), (unsigned char *)&IPTmp, 0 );
Now, when a DHCP client is used, it clears and resets the contents of the part of the configuration it controls. This includes the DNS server addresses. Therefore, if the above code was added to an application that used DHCP, the entry would be cleared whenever DHCP executed a status update.
To share this configuration space with DHCP (or to read the results of a DHCP configuration), the DHCP status callback report codes must be used. The status callback function was introduced in Adding Status Report Services. When DHCP reports a status change, the application knows that the DHCP portion of the system configuration has been reset.
The following code manually adds a DNS server address when the DHCP client is in use.
//
// Service Status Reports
//
static char *TaskName[] = { "Telnet","HTTP","NAT","DHCPS","DHCPC","DNS" };
static char *ReportStr[] = { "","Running","Updated","Complete","Fault" };
static char *StatusStr[] = { "Disabled","Waiting","IPTerm", "Failed","Enabled" };
static void ServiceReport( uint32_t Item, uint32_t Status, uint32_t Report, void *h )
{
printf( "Service Status: %-9s: %-9s: %-9s: %03d\n",
TaskName[Item-1], StatusStr[Status], ReportStr[Report/256], Report&0xFF );
// Example of adding to the DHCP configuration space
//
// When using the DHCP client, the client has full control over access
// to the first 256 entries in the CFGTAG_SYSINFO space. Here, we want
// to manually add a DNS server to the configuration, but we can only
// do it once DHCP has finished its programming.
//
if( Item == CFGITEM_SERVICE_DHCPCLIENT &&
Status == CIS_SRV_STATUS_ENABLED &&
(Report == (NETTOOLS_STAT_RUNNING|DHCPCODE_IPADD) ||
Report == (NETTOOLS_STAT_RUNNING|DHCPCODE_IPRENEW)) )
{
uint32_t IPTmp;
// Manually add the DNS server when specified. If the address
// string reads "0.0.0.0", IPTmp will be set to zero.
IPTmp = inet_addr(DNSServer);
if( IPTmp )
CfgAddEntry( 0, CFGTAG_SYSINFO, CFGITEM_DHCP_DOMAINNAMESERVER,
0, sizeof(IPTmp), (unsigned char *)&IPTmp, 0 );
}
}
2.1.4.2 Controlling NDK and OS Options via the Configuration¶
Along with specifying IP addresses, routes, and services, the configuration system allows you to directly manipulate the configuration structures of the OS adaptation layer and the NDK. The OS configuration structure is discussed in the Operating System Configuration section of the NDK API Reference Guide, and the NDK configuration structure is discussed in the Configuring the Stack section in the appendices. The configuration interface to these internal structures is consolidated into a single configuration API as specified in the Initialization and Configuration section.
Although the values in these two configuration structures can be modified directly, adding the parameters to the system configuration is useful for two reasons. First, it provides a consistent API for all network configuration, and second, if the configuration load and save feature is used, these configuration parameters are saved along with the rest of the system configuration.
As a quick example of setting an OS configuration option, the following code makes a change to the debug reporting mechanism. By default, all debug messages generated by the NDK are output to the CCStudio output window. However, the OS configuration can be adjusted to print only messages of a higher severity level, or to disable the debug messages entirely.
// We do not want to see debug messages less than WARNINGS
rc = DBG_WARN;
CfgAddEntry( hCfg, CFGTAG_OS, CFGITEM_OS_DBGPRINTLEVEL,
CFG_ADDMODE_UNIQUE, sizeof(uint32_t), (unsigned char *)&rc, 0 );
2.1.4.3 Shutdown¶
There are two ways the stack can be shut down. The first is a manual shutdown that occurs when an application calls NC_NetStop(). Here, the calling argument to the function is returned to the NETCTRL thread as the return value from NC_NetStart(). Calling NC_NetStop(1) reboots the network stack, while calling NC_NetStop(0) shuts down the network stack.
The second way the stack can be shut down is when the stack code detects a fatal error. A fatal error is an error above the fatal threshold set in the configuration. This type of error generally indicates that it is not safe for the stack to continue. When this occurs, the stack code calls NC_NetStop(-1). It is then up to you to determine what should be done next. The way the NC_NetStart() loop is coded determines whether the system will shut down or simply reboot.
Note that the critical threshold to shut down can also be disabled. The following code can be added to the configuration to disable error-related shutdowns:
// We do not want the stack to abort on any error
uint32_t rc = DBG_NONE;
CfgAddEntry( hCfg, CFGTAG_OS, CFGITEM_OS_DBGABORTLEVEL,
CFG_ADDMODE_UNIQUE, sizeof(uint32_t), (unsigned char *)&rc, 0 );
2.1.4.4 Saving and Loading a Configuration¶
Once a configuration is constructed, the application may save it off into non-volatile RAM so that it can be reloaded on the next cold boot. This is especially useful in an embedded system where the configuration can be modified at runtime using a serial cable, Telnet, or an HTTP browser.
If you are using XGCONF for configuration, saving and reloading configurations is not automatically supported by XGCONF. However, internally, the same configuration database used by the Cfg*() C functions is populated when the *.cfg file is built. You may want to use the functions in the following subsections as hook functions to save the configuration created with XGCONF and reload if from non-volatile memory on startup.
2.1.4.4.1 Saving the Configuration¶
To save the configuration, convert it to a linear buffer, and then save the linear buffer off to storage. Here is a quick example of a configuration save operation. Note the MyMemorySave() function is assumed to save off the linear buffer into non-volatile storage.
int SaveConfig( void *hCfg )
{
unsigned char *pBuf;
int size;
// Get the required size to save the configuration
CfgSave( hCfg, &size, 0 );
if( size && (pBuf = malloc(size) ) )
{
CfgSave( hCfg, &size, pBuf );
MyMemorySave( pBuf, size );
Free( pBuf );
return(1);
}
return(0);
}
2.1.4.4.2 Loading the Configuration¶
Once a configuration is saved, it can be loaded from non-volatile memory on startup. For this final NetworkTest() example, assume that another Task has created, edited, or saved a valid configuration to some storage medium on a previous execution. In this network initialization routine, all that is required is to load the configuration from storage and boot the NDK using the current configuration.
For this example, assume that the function MyMemorySize() returns the size of the configuration in a stored linear buffer and that MyMemoryLoad() loads the linear buffer from non-volatile storage.
int NetworkTest()
{
int rc;
void *hCfg;
unsigned char *pBuf;
Int size;
//
// THIS MUST BE THE ABSOLUTE FIRST THING DONE IN AN APPLICATION!!
//
rc = NC_SystemOpen( NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT );
if( rc )
{
printf("NC_SystemOpen Failed (%d)\n",rc);
for(;;);
}
//
// First load the linear memory block holding the configuration
//
// Allocate a buffer to hold the information
size = MyMemorySize();
if( !size )
goto main_exit;
pBuf = malloc( size );
if( !pBuf )
goto main_exit;
// Load from non-volatile storage
MyMemoryLoad( pBuf, size );
//
// Now create the configuration and load it
//
// Create a new configuration
hCfg = CfgNew();
if( !hCfg )
{
printf("Unable to create configuration\n");
free( pBuf );
goto main_exit;
}
// Load the configuration (and then we can free the buffer)
CfgLoad( hCfg, size, pBuf );
mmFree( pBuf );
//
// Boot the system using this configuration
//
// We keep booting until the function returns less than 1. This allows
// us to have a "reboot" command.
//
do
{
rc = NC_NetStart( hCfg, networkOpen, networkClose, networkIPAddr );
} while( rc > 0 );
// Delete Configuration
CfgFree( hCfg );
// Close the OS
main_exit:
NC_SystemClose();
return(0);
}
2.1.5 NDK Initialization¶
Before an application can use the network, the stack must be properly configured and initialized. To facilitate a standard initialization process, and yet allow customization, source code to the network control module (NETCTRL) is included in the NDK. The NETCTRL module is the center of the stack’s initialization and event scheduling. A solid comprehension of NETCTRL’s operation is essential for building a solid networking application. This section describes how to use NETCTRL in a networking application. An explanation of how NETCTRL works and how it can be tuned is provided in Section 3.
The process of initialization of the NDK is described in detail in Chapter 4 of the NDK API Reference Guide. This section closely mirrors the initialization procedure described in the NDK Software Directory of that document. Here we describe the information with a more practical slant. Programmers concerned with the exact API of the functions mentioned here should refer to the NDK API Reference Guide for a more precise description.
2.1.5.1 The NETCTRL Task Thread¶
If you use Cfg*() API calls for configuration, you must create a Task thread that contains a call to NC_NetStart(), which in turn runs the network scheduler function.
If you use XGCONF for configuration, this thread is automatically generated, and the code that calls NC_NetStart() is in the generated C file.
This Task thread (called the scheduler thread) is the thread in which nearly all the NETCTRL activity takes place. This thread acts as the program’s entry-point and performs initialization actions. Later, it becomes the NETCTRL scheduler thread. Therefore, control of this thread is not returned to the caller until the stack has been shut down. Application Tasks - network-oriented or otherwise - are not executed within this thread.
2.1.5.2 Pre-Initialization¶
If you use Cfg*() API calls for configuration, your application must call the primary initialization function NC_SystemOpen() before calling any other stack API functions. This initializes the stack and the memory environment used by all the stack components. Two calling arguments, Priority and OpMode, indicate how the scheduler should execute. For example:
rc = NC_SystemOpen( NC_PRIORITY_LOW, NC_OPMODE_INTERRUPT );
if( rc )
{
printf("NC_SystemOpen Failed (%d)\n",rc);
for(;;);
}
2.1.5.3 Invoking New Network Tasks and Services¶
Some standard network services can be specified in the NDK configuration; these are loaded and unloaded automatically by the NETCTRL module. Other services, including those written by an applications programmer should be launched from callback functions.
If you use Cfg*() API calls for configuration, you can use the Start callback function supplied to NC_NetStart() to add a callback function. As an example of a network start callback, the networkOpen() function below opens a user SMTP server application by calling an open function to create the main application thread.
static SMTP_Handle hSMTP;
//
// networkOpen
//
// This function is called after the configuration has booted
//
static void networkOpen( )
{
// Create an SMTP server Task
hSMTP = SMTP_open( );
}
The above code launches a self-contained application that needs no further monitoring, but the application must be shut down when the system shuts down. This is done via the networkClose() callback function. Therefore, the networkClose() function must undo what was done in networkOpen().
//
// networkClose
//
// This function is called when the network is shutting down
//
static void networkClose()
{
// Close our SMTP server Task
SMTP_close( hSMTP );
}
The above example assumes that the network scheduler Task can be launched whether or not the stack has a local IP address. This is true for servers that listen on a wildcard address of 0.0.0.0. In some rare cases, an IP address may be required for Task initialization, or perhaps an IP address on a certain device type is required. In these circumstances, the networkIPAddr() callback function signals the application that it is safe to start.
The following example illustrates the calling parameters to the networkIPAddr() callback. Note that the IFIndexGetHandle() and IFGetType() functions can be called to get the type of device (HTYPE_ETH or HTYPE_PPP) on which the new IP address is being added or removed. This example just prints a message. The most common use of this callback function is to synchronize network Tasks that require a local IP address to be installed before executing.
//
// networkIPAddr
// This function is called whenever an IP address binding is
// added or removed from the system.
//
static void networkIPAddr( IPN IPAddr, uint32_t IfIdx, uint32_t fAdd )
{
IPN IPTmp;
if( fAdd )
printf("Network Added: ");
else
printf("Network Removed: ");
// Print a message
IPTmp = ntohl( IPAddr );
printf("If-%d:%d.%d.%d.%d\n", IfIdx, (unsigned char)(IPTmp>>24)&0xFF,
(unsigned char)(IPTmp>>16)&0xFF, (unsigned char)(IPTmp>>8)&0xFF, (unsigned char)IPTmp&0xFF );
}
2.1.5.4 Network Startup¶
If you use Cfg*() API calls for configuration, your application must call the NETCTRL function NC_NetStart() to invoke the network scheduler after the configuration is loaded. Besides the handle to the configuration, this function takes three additional callback pointer parameters; a pointer to a Start callback function, a Stop function, and an IP Address Event function.
The first two callback functions are called only once. The Start callback is called when the system is initialized and ready to execute network applications (note there may not be a local IP network address installed yet). The Stop callback is called when the system is shutting down and signifies that the stack will soon not be able to execute network applications. The third callback can be called multiple times. It is called when a local IP address is either added or removed from the system. This can be useful in detecting new DHCP or PPP address events, or just to record the local IP address for use by local network applications. The call to NC_NetStart() will not return until the system has shut down, and then it returns a shutdown code as its return value. How the system was shut down may be important to determine if the stack should be rebooted. For example, a reboot may be desired in order to load a new configuration. The return code from NC_NetStart() can be used to
determine if NC_NetStart() should be called again (and hence perform the reboot).
For a simple example, the following code continuously reboots the stack using the current configuration handle if the stack shuts down with a return code greater than zero. The return code is set when the stack is shut down via a call to NC_NetStop().
//
// Boot the system using our configuration
//
// We keep booting until the function returns 0. This allows
// us to have a "reboot" command.
//
do
{
rc = NC_NetStart( hCfg, networkOpen, networkOpen, networkIPAddr );
} while( rc > 0 );
2.1.5.5 Adding Status Report Services¶
The configuration system can also be used to invoke the standard network services found in the NETTOOLS library. The services available to network applications using the NDK are discussed in detail in Chapter 4 of the NDK API Reference Guide. This section summarized the services described in that chapter.
When using the NETTOOLS library, the NETTOOLS status callback function is introduced. This callback function tracks the state of services that are enabled through the configuration. There are two levels to the status callback function. The first callback is made by the NETTOOLS service. It calls the configuration service provider when the status of the service changes. The configuration service provider then adds its own status to the information and calls back to the application’s callback function. A pointer to the application’s callback is provided when the application adds the service to the system configuration.
If you use Cfg*() API calls for configuration, the basic status callback function follows:
//
// Service Status Reports
//
static char *TaskName[] = { "Telnet","HTTP","NAT","DHCPS","DHCPC","DNS" };
static char *ReportStr[] = { "","Running","Updated","Complete","Fault" };
static char *StatusStr[] = { "Disabled", "Waiting", "IPTerm", "Failed", "Enabled" }
static void ServiceReport( uint32_t Item, uint32_t Status, uint32_t Report, void *h )
{
printf( "Service Status: %-9s: %-9s: %-9s: %03d\n",
TaskName[Item-1], StatusStr[Status], ReportStr[Report/256], Report&0xFF );
}
Note that the names of the individual services are listed in the TaskName[] array. This order is specified by the definition of the service items in the configuration system and is constant. See the file inc/nettools/netcfg.h for the physical declarations.
Note that the strings defining the master report code are listed in the ReportStr[] array. This order is specified by the NETTOOLS standard reporting mechanism and is constant. See the file inc/nettools/nettools.h for the physical declarations.
Note that the strings defining the Task state are defined in the StatusStr[] array. This order is specified by the definition of the standard service structure in the configuration system. See the file inc/nettools/netcfg.h for the physical declarations.
The last value this callback function prints is the least significant 8 bits of the value passed in Report. This value is specific to the service in question. For most services this value is redundant. Usually, if the service succeeds, it reports Complete, and if the service fails, it reports Fault. For services that never complete (for example, a DHCP client that continues to run while the IP lease is active), the upper byte of Report signifies Running and the service specific lower byte must be used to determine the current state.
For example, the status codes returned in the 8 least significant bits of Report when using the DHCP client service are:
DHCPCODE_IPADD Client has added an IP address
DHCPCODE_IPREMOVE IP address removed and CFG erased
DHCPCODE_IPRENEW IP renewed, DHCP config space reset
These DHCP client specific report codes are defined in inc/nettools/inc/dhcpif.H. In most cases, you do not have to examine state report codes down to this level of detail, except in the following case. When using the DHCP client to configure the stack, the DHCP client controls the first 256 entries of the CFGTAG_SYSINFO tag space. These entries correspond to the 256 DHCP option tags. An application may check for DHCPCODE_IPADD or DHCPCODE_IPRENEW return codes so that it can read or alter information obtained by DHCP client. This is discussed further in Constructing a Configuration using the DHCP Client Service.
2.2 Configuring the NDK with XGCONF¶
As an alternative to configuring NDK applications with C code that calls Cfg*() functions, you can use the XGCONF configuration tool within CCStudio. Graphical displays let you enable and set properties as needed. XGCONF may be used to configure the TI-RTOS Kernel. The same configuration in one project can configure both NDK and TI-RTOS Kernel modules and objects.
NOTE: XGCONF is not supported for applications that use FreeRTOS.
When you create a project using a TI-RTOS Kernel template, the project will contain a configuration file (*.cfg) that can be edited with the XGCONF graphical editor in CCStudio. If you checked the boxes to enable use of the NDK and NSP when you created the project, you can configure your application’s use of the NDK modules. The configuration file is processed during the build to generate code that configures your application.
You can configure the NDK modules through the XGCONF configuration editor. (Internally, the same configuration database is updated when the *.cfg file is built.)
NOTE: You must choose one method or the other to configure your application. You should not mix configuration methods. If you have NDK applications that use the C-based configuration method, you should either continue to use that method or convert the configuration entirely to an*.cfgfile configuration. If a project uses both methods, there will be unpredictable conflicts between the two configurations.
- To open XGCONF, simply double-click the
*.cfgfile in your application’s project. See the steps in Configuring NDK Modules for how to use XGCONF with the NDK. For more details, see Chapter 2 of the TI-RTOS Kernel (SYS/BIOS) User’s Guide (SPRUEX3). - When XGCONF opens, you see the Welcome sheet for TI-RTOS Kernel. You should see categories for the NDK Core Stack and your NSP in the Available Products area. If you do not, your CCStudio Project does not have NDK support enabled. See to correct this problem. (If the configuration is shown in a text editor instead of XGCONF, right-click on the
.cfgfile in the Project Explorer and choose Open With > XGCONF.) - Click the Global item in either the Available Products view (under the NDK Core Stack category) or in the Outline view
- You see the Welcome sheet for NDK configuration. This sheet provides an overview of the NDK, configuration information, and documentation for the NDK.
- Click the System Overview button for a diagram of the configurable NDK modules. Notice the green checkmarks next to some modules. These checkmarks indicate that the modules have been enabled.
Fig. 3 NDK Configuration System Overview
The XGCONF configuration automatically performs the following actions for you:
- Generates C code to create and populate a configuration database.
- Generates C code to act as the network scheduling function and to perform network activity.
The following C functions are generated as a result of using the NDK Global module for configuration. You should take care not to write application functions with these names.
ti_ndk_config_Global_stackThread(): The NDK stack thread function.networkOpen(): function that is called automatically byNC_NetStart().NetworkClose(): function that is called automatically byNC_NetStart().NetworkIPAddr(): function that is called automatically byNC_NetStart().ti_ndk_config_Global_serviceReport(): Service report callback function.
2.2.1 Opening the XGCONF Configuration Editor¶
When you create a project using a TI-RTOS Kernel template, the project will contain a configuration file (*.cfg) that can be edited with the XGCONF graphical editor in CCStudio. If you checked the boxes to enable use of the NDK and NSP when you created the project, you can configure your application’s use of the NDK modules. The configuration file is processed during the build to generate code that configures your application.
This section provides an overview of how to use the XGCONF graphical editor. For more details, see Section 2.2 of the TI-RTOS Kernel (SYS/BIOS) User’s Guide (SPRUEX3)
To open XGCONF, follow these steps:
- Make sure you are in the CCS Edit perspective of CCStudio. If you are not in that perspective, click the CCS Edit icon to switch back.
- Double-click on the
*.cfgconfiguration file in the Project Explorer tree. While XGCONF is opening, the CCStudio status bar shows that the configuration is being processed and validated. - When XGCONF opens, you see the Welcome sheet for the TI-RTOS Kernel. You should see categories for the NDK Core Stack and your NSP in the Available Products area. If you do not, your CCStudio Project does not have NDK support enabled. See to correct this problem. (If the configuration is shown in a text editor instead of XGCONF, right-click on the
.cfgfile in the Project Explorer and choose Open With > XGCONF.) - Click the Global item in either the Available Products view (under the NDK Core Stack category) or in the Outline view
- You will see the Welcome sheet for NDK configuration. This sheet provides an overview of the NDK, configuration information, and documentation for the NDK.
- Click the System Overview button for a diagram of the configurable NDK modules. Notice the green checkmarks next to some modules. These checkmarks indicate that the modules have been enabled.
2.2.2 Adding a Module to Your Configuration¶
To add support for a module to your configuration, follow these steps:
- Click on a module that you want to use in your application in the System Overview diagram or in the Available Products view.
- In the Module Settings sheet, check the box to Add the <module> to my configuration. (You can also right-click on a module in the Available Products view and choose Use <module> from the context menu.)
- Notice that the module you added to the configuration is now listed in the Outline view.
2.2.3 Setting Properties for a Module¶
To set properties for a module, go to the Module Settings sheet and type or select the settings you want to use.
If you want information about a property, point to the field with your mouse cursor.
For details about properties, right-click and choose Help from the context menu. This opens the CDOC online reference system for the NDK. The properties names listed in this online help are the names used in the configuration source file. You can click the Source tab at the bottom of the XGCONF editor window to see the actual source statements.
2.2.4 Adding an Instance for a Module¶
Some of the NDK modules allow you to create instances of that type. For example, you can create instances of DHCP servers, DNS servers, HTTP servers, NAT servers, and Telnet servers. To create such instances, follow these steps:
- Go to the property sheet for the module for which you will add an instance.
- Click the Instance button at the top of the Module Settings sheet.
- Click the Add button to open a property window for a new instance. You can set properties here or in the Instance Settings sheet.
- Click OK to create the instance.
- Notice that the instance you created is also listed in the Outline view.
2.2.5 Saving Changes to the Configuration¶
To save changes to your configuration, press Ctrl+S. You can also choose File > Save from the CCStudio menus.
When you make changes to the configuration or save the configuration, your settings are validated. Any errors or warnings found are listed in the Problems view and icons in the Outline view identify any modules or instances that have problems.
2.2.6 Linked Libraries Configuration¶
The Global module is required in NDK applications. It determines which core libraries and which flavor of the NDK stack library are linked with your application. By default, it is also used to configure global stack settings and generate NDK configuration code. The following libraries are linked in by default via the Global module:
- stack
- cgi
- console
- dlc
- netctrl
- nettool
- os
- servers
In addition, the appropriate version of the stack library (stk*) is linked in depending on whether you enable the NAT, PPP, PPPoE modules in your configuration.
Click the System Overview button in Global NDK sheet. Notice that if you have the IP module enabled, you can check or uncheck the Enable IPv6 box. This setting controls whether the application is linked with libraries that support IPv4 or IPv6 when you build the application.
2.2.7 Global Scheduling Configuration¶
In addition to the Welcome tab that described the NDK and the System Overview tab that provides a diagram of the NDK modules in use, the Global module also provides several tabs that let you set various configuration options for the NDK stack. The next tab is the Scheduling tab, which lets you control how Task threads are scheduled and how much stack memory they can use.
2.2.7.1 Network Scheduler Task Options¶
You can configure the Network Scheduler Task Priority with XGCONF by selecting the NDK’s Global module and then clicking the Scheduling button.
Network Scheduler Task Priority is set to either Low Priority (NC_PRIORITY_LOW) or High Priority (NC_PRIORITY_HIGH), and determines the scheduler Task’s priority relative to other networking Tasks in the system.
2.2.7.2 Priority Levels for Network Tasks¶
The stack is designed to be flexible, and has an OS adaptation layer that can be adjusted to support any system software environment that is built on top of the TI-RTOS Kernel. Although the environment can be adjusted to suit any need by adjusting the HAL, NETCTRL and OS modules, the following restrictions should be noted for the most common environments:
- The Network Control Module (NETCTRL) contains a network scheduler thread that schedules the processing of network events. The scheduler thread can run at any priority with the proper adjustment. Typically, the scheduler priority is low (lower than any network Task), or high (higher than any network Task). Running the scheduler thread at a low priority places certain restrictions on how a Task can operate at the socket layer. For example:
- If a Task polls for data using the
NDK_recv()function in a non-block mode, no data is ever received because the application never blocks to allow the scheduler to process incoming packets. - If a Task calls
NDK_send()in a loop using UDP, and the destination IP address is not in the ARP table, the UDP packets are not sent because the scheduler thread is never allowed to run to process the ARP reply. These cases are seen more in UDP operation than in TCP. To make the TCP/IP behave more like a standard socket environment for UDP, the priority of the scheduler thread can be set to high priority. See Section 3 for more details on network event scheduling.
- If a Task polls for data using the
- The NDK requires a re-entrance exclusion methodology to call into internal stack functions. This is called kernel mode by the NDK, and is entered by calling the function
llEnter()and exited viallExit(). Application programmers do not typically call these functions, but you must be aware of how the functions work.
By default, priority inversion is used to implement the kernel exclusion methods. When in kernel mode, a Task’s priority is raised to OS_TASKPRIKERN. Application programmers need to be careful not to call stack functions from threads with a priority equal to or above that of OS_TASKPRIKERN, as this could cause illegal reentrancy into the stack’s kernel functions. For systems that cannot tolerate priority restrictions, the NDK can be adjusted to use Semaphores for kernel exclusion. This can be done by altering the OS adaptation layer as discussed in Choosing the llEnter()/llExit() Exclusion Method, or by using the Semaphore based version of the OS library: OS_SEM.
2.2.7.2.1 Stack Sizes for Network Tasks¶
Care should be taken when choosing a Task stack size. Due to its recursive nature, a Task tends to consume a significant amount of stack. A stack size of 3072 is appropriate for UDP based communications. For TCP, 4096 should be used as a minimum, with 5120 being chosen for protocol servers. The thread that calls the NETCTRL library functions should have a stack size of at least 4096 bytes. If lesser values are used, stack overflow conditions may occur.
2.2.7.3 Priorities for Tasks that Use NDK Functions¶
In general, Tasks that use functions in the network stack should be of a priority no less than OS_TASKPRILOW, and no higher than OS_TASKPRIHIGH. For a typical Task, use a priority of OS_TASKPRINORM. The values for these #define variables can be changed with XGCONF by selecting the NDK’s Global module and then clicking the Scheduling button.
In addition, Task priorities can be altered by adjusting the OSENVCFG structure as described in the NDK API Reference Guide; however, this is strongly discouraged. When altering the priority band, care must be taken to account for both the network scheduler thread and the kernel priority.
2.2.8 Global Buffer Configuration¶
You can configure the buffers used by the NDK by selecting the NDK’s Global module and then clicking the Buffers button. This page lets you configure the sizes and locations of the NDK Packet Buffer Manager (PBM) and the Memory Manager Buffer.
The NDK defines some special memory segments via the pragma:
#pragma DATA_SECTION( memory_label, "SECTIONNAME" )
The NDK sections are defined by default as subsections of the .far memory segment. External memory is usually used for the .far section. The additional section names are shown below.
.far:NDK_PACKETMEM – This section is defined in the HAL and OS adaptation layers for packet buffer memory. The size required is normally 32k bytes to 48k bytes. You can configure this buffer with XGCONF by selecting the NDK’s Global module and then clicking the Buffers button.
.far:NDK_MMBUFFER – This section is defined by the memory allocation system for use as a scratchpad memory resource. The size of the memory declared in this section is adjustable, but the default is less than 48k bytes. You can configure this buffer with XGCONF by selecting the NDK’s Global module and then clicking the Buffers button.
.far:NDK_OBJMEM – This section is a catch-all for other large data buffer declarations. It is used by the OS adaptation layer (for print buffers).
You can use the Program.sectMap[] configuration array to configure section placement. For details about controlling the placement of sections in memory, see Chapter 6 on Memory in the TI-RTOS Kernel (SYS/BIOS) User’s Guide (SPRUEX3).
The Memory Allocation Support section of the NDK API Reference Guide describes the memory allocation API provided by the OS library for use by the various stack libraries. Although the stack’s memory allocation API has some benefits (it is portable, bucket based to prevent fragmentation, and tracks memory leaks), the application code is expected to use the standard malloc()/free() or equivalent Memory module allocation routines provided by the TI-RTOS Kernel.
2.2.9 Global Hook Configuration¶
You can configure callback (hook) functions by selecting the NDK’s Global module and then clicking the Hooks button. You can specify functions to be called at the following times:
- Stack Thread Begin. Runs at the beginning of the generated
ti_ndk_config_Global_stackThread()function, before the call toNC_SystemOpen(). Note that no NDK-related code can run in this hook function because theNC_SystemOpen()function has not yet run. - Stack Thread Initialization. Runs in the
ti_ndk_config_Global_stackThread()function, immediately after the function call to create a new configuration,CfgNew(). - Stack Thread Delete. Runs in the
ti_ndk_config_Global_stackThread()function, immediately after exiting from the while() loop that callsNC_NetStart(), but before the calls toCfgFree()andNC_SystemClose(). (Configuration database calls, such asCfgNew(), are still made internally even if you use the XGCONF configuration method. These calls are described in System Configuration, but generally you do not need to be concerned with them if you are using XGCONF for configuration.) - Status Report. Runs at the beginning of the generated
ti_ndk_config_Global_serviceReport()function. - Network Open. Runs at the beginning of the generated
networkOpen()function, when the stack is ready to begin creating application supplied network Tasks. Note that this function is called during the early stages of stack startup, and must return in order for the stack to resume operations. - Network Close. Runs at the beginning of the generated
networkClose()function, when the stack is about to shut down. - Network IP Address. Runs at the beginning of the generated
networkIPAddr()function, when an IP address is added to or removed from the system. Note if you are using a static IP address, this hook will likely fire off before any network interfaces are ready to start sending and receiving data. TheNC_setLinkHook()function described in section 3.2.4 will allow you to register a hook that fires when a network interface reports itself as up.
Hook functions must be defined using the following format:
Void functionName(Void)
If you specify a hook function in the configuration, but do not define the function in your C code, a linker error will result.
For example, the following function could be called as the Stack Thread Initialization hook function to open an SMTP server application:
static SMTP_Handle hSMTP;
//
// SmtpStart
// This function is called after the configuration has been loaded
//
static void SmtpStart( )
{
// Create an SMTP server Task
hSMTP = SMTP_open( );
}
The above code launches a self-contained application that needs no further monitoring, but the application must be shut down when the system shuts down. This is done via the Stack Thread Delete callback function.
//
// SmtpStop
// This function is called when the network is shutting down
//
static void SmtpStop()
{
// Close SMTP server Task
SMTP_close( hSMTP );
}
The above code assumes that the network scheduler Task can be launched whether or not the stack has a local IP address. This is true for servers that listen on a wildcard address of 0.0.0.0. In some rare cases, an IP address may be required for Task initialization, or perhaps an IP address on a certain device type is required. In these circumstances, the NetworkIPAddr() callback function signals the application that it is safe to start.
If you are using XGCONF for configuration, saving and reloading configurations via the CfgSave() and CfgLoad() functions is not automatically supported by XGCONF. However, internally, the same configuration database used by the Cfg*() C functions is populated when the *.cfg file is built. You may want to use the example functions in Saving and Loading a Configuration as hook functions to save the configuration created with XGCONF and reload if from non-volatile memory on startup.
2.2.10 Global Debug Configuration¶
There are two ways the stack can be shut down. The first is a manual shutdown that occurs when an application calls NC_NetStop(). The calling argument to the function is returned to the NETCTRL thread as the return value from NC_NetStart(). Calling NC_NetStop(1) reboots the network stack, while calling NC_NetStop(0) shuts down the network stack.
The second way the stack can be shut down is when the stack code detects a debug message above the level you have set for shutdown control. You can configure this level by selecting the NDK’s Global module and then clicking the Debug button.
The Debug Print Message Level controls which messages are sent to the debug log. For example, if you set this level to “Warning Messages”, then warnings and errors will go to the debug log, but informational errors will not. By default, all messages are sent to the debug log.
The Debug Abort Message Level controls what types of messages trigger a stack shutdown. For example, if you set this level to “No Messages”, then the stack is never shut down in response to an error. In this case, your application must detect and respond to messages. By default, only error messages trigger a stack shutdown.
2.2.11 Advanced Global Configuration¶
You can configure additional global NDK properties by selecting the NDK’s Global module and then clicking the Advanced button. You should be careful when setting these properties. In general, it is best to leave these properties set to their defaults. Some advanced properties include:
Global.ndkTickPeriodlets you adjust the NDK heartbeat rate. The default is 100 ticks. This matches the default TI-RTOS Kernel Timer object, which drives the Clock and is configured so that 1 tick = 1 millisecond. However, you can configure a new Timer and use that to drive the Clock module. If that new Timer is not configured such that 1 tick = 1 millisecond, then you should also adjust the NDK tick period accordingly.Global.ndkThreadPriandGlobal.ndkThreadStackSizelet you control the priority and stack size of the main NDK scheduler thread.Global.netSchedulerOpModeis set to either Polling Mode (NC_OPMODE_POLLING) or Interrupt Mode (NC_OPMODE_INTERRUPT), and determines when the scheduler attempts to execute. Interrupt mode is used in the vast majority of applications. Note that polling mode attempts to run continuously, so when polling is used, a low priority must be used.Global.multiCoreStackRunModelets you control which cores (on a C6000 multi-core processor) run the NDK stack. By default, only core 0 runs the NDK stack. Set this property only if you are an advanced user.Global.enableCodeGenerationis set to true by default. If you set it to false, no C code is generated by the configuration, but the configuration still controls which NDK libraries are linked into the application.
2.2.12 Adding Clients and Servers¶
You can easily add support for additional modules to your application by enabling them in the configuration. For example, the following steps configure a static IP address:
- Click on the IP module in the System Overview diagram or in the Available Products view.
- In the IP Settings: General Settings page, check the box to Add the IP to my configuration.
- Uncheck the box to Obtain IP Address Automatically to enable setting a static IP address.
- Make settings similar to the following in this sheet.
- If you want information about a property, point to the field with your mouse cursor. Right-click on any field to get reference help for all the configurable IP module properties.
- In addition to the properties listed on the General Settings page, a number of additional properties can be set if you click the Advanced button.
2.3 Creating a Task¶
Applications that use the NDK may create Task threads in either of the following ways:
- Call TaskCreate() as described in the NDK API Reference Guide.
- Call the native thread create APIs corresponding to the RTOS you are using. For example, SYS/BIOS users would call
Task_create(). Note that if you use native thread APIs to create task threads directly, the code of your thread function needs to initializes the file descriptor table (see “Initializing the File Descriptor Table”) by callingfdOpenSession()at the beginning, andfdCloseSession()at the end.
Internally, TaskCreate() calls fdOpenSession() and fdCloseSession() automatically, using the native OS API (corresponding to the RTOS the app is using) to create a thread.
Priority-based exclusion makes it important that your application use only a priority in the range of the configured NDK priorities (OS_TASKPRILOW to OS_TASKPRIHIGH). Setting a thread to a higher priority than the NDK’s high-priority thread level may disrupt the system and cause unpredictable behavior if the thread calls any stack-related functions.
The following example uses TaskCreate() to create a Task with a normal priority level:
void *taskHandle = NULL;
taskHandle = TaskCreate(entrypoint, "TaskName", OS_TASKPRINORM, stacksize, arg1, arg2, arg3);
The following example uses SYS/BIOS APIs to create a Task for use by the NDK. The thread has a normal priority level and a stack size of 2048.
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/ndk/inc/netmain.h>
void myNetThreadFxn()
{
fdOpenSession((void *)Task_self());
/* do socket calls */
fdCloseSession((void *)Task_self());
}
void createNdkThread()
{
int status;
Task_Params params;
Task_Handle myNetThread;
Task_Params_init(¶ms);
params.instance->name = "myNetThread";
params.priority = OS_TASKPRINORM;
params.stackSize = 2048;
myNetThread = Task_create((Task_FuncPtr)myNetThreadFxn, ¶ms, NULL);
if (!myNetThread) {
/* Error */
}
}
2.3.1 Initializing the File Descriptor Table¶
Each Task thread that uses the sockets or file API must allocate a file descriptor table and associate the table with the Task handle. The following code shows how to do this using SYS/BIOS Task APIs. This process is described fully in the NDK API Reference Guide. To accomplish this, a call to fdOpenSession() must be performed before any file descriptor oriented functions are used, and then fdCloseSession() should be called when these functions are no longer required.
void mySocketsFunction()
{
fdOpenSession((void *)Task_self());
/* do socket calls */
fdCloseSession((void *)Task_self());
return (NULL);
}
2.4 Application Debug and Troubleshooting¶
Although there is no instant or easy way to debug an NDK application, the following sections provide a quick description of some of the potential problem areas. Some of these topics are discussed elsewhere in the documentation as well.
2.4.1 Troubleshooting Common Problems¶
One of the most common support requests for the NDK deals with the inability to either send or receive network packets. This may also take the form of dropping packets or general poor performance. There are many causes for this type of behavior. For potential scheduling issues, see “Priority Levels for Network Tasks”. It is also recommended that application programmers fully understand the workings of the NETCTRL module. For this, see Section 3.
Here is a quick list. If you are using XGCONF for configuration, many of the potential configuration problems cannot occur.
All socket calls return “error” (-1)
- Make sure there is a call to
fdOpenSession()in the Task before it uses sockets, and a call tofdCloseSession()when the Task terminates.
No link indication, or will not re-link when cable is disconnected and reconnected.
- Make sure there is a Timer object in your configuration that is calling the driver function
llTimerTick()every 100 ms.
Not receiving any packets - ever
- When polling for data by making
NDK_recv(),fdPoll(), orfdSelect()calls in a non-blocking fashion, make sure you do not have any scheduling issues. When the NETCTRL scheduler is running in low priority, network applications are not allowed to poll without blocking. Try running the scheduler in high priority (viaNC_SystemOpen()). - The NDK assumes there is some L2 cache. If the DSP or ARM is configured to all internal memory with nothing left for L2 cache, the NDK drivers will not function properly.
Performance is sluggish. Very slow ping response.
- Make sure there is a Timer object in your configuration that is calling the driver function
llTimerTick()every 100 ms. - If porting an Ethernet driver and running NETCTRL in interrupt mode, make sure your device is correctly detecting interrupts. Make sure the interrupt polarity is correct.
UDP application drops packets on NDK_send() calls.
- If sending to a new IP address, the very first send may be held up in the ARP layer while the stack determines the MAC address for the packet destination. While in this mode, subsequent sends are discarded.
- When using UDP and sending multiple packets at once, make sure you have plenty of packet buffers available (see “Packet Buffer Pool”).
- Verify you do not have any scheduling issues. Try running the scheduler in high priority (via
NC_SystemOpen()).
UDP application drops packets on NDK_recv() calls.
- Make sure you have plenty of packet buffers available (see “Packet Buffer Pool”)).
- Make sure the packet threshold for UDP is high enough to hold all UDP data received in between calls to
NDK_recv()(seeCFGITEM_IP_SOCKUDPRXLIMITin the NDK Programmer’s Reference Guide). - Verify you do not have any scheduling issues. Try running the scheduler in high priority (via
NC_SystemOpen()). - It is possible that packets are being dropped by the Ethernet device driver. Some device drivers have adjustable RX queue depths, while others do not. Refer to the source code of your Ethernet device driver for more details (device driver source code is provided in NDK Support Package for your hardware platform).
Pings to NDK target Fails Beyond 3012 Size
The NDK’s default configuration allows reassembly of packets up to “3012” bytes. To be able to ping bigger sizes, the stack needs to be reconfigured as follows:
- Change the
MMALLOC_MAXSIZEdefinition in thepbm.cfile. (i.e.#define MMALLOC_MAXSIZE 65500) and rebuild the library. - Increase the Memory Manager Buffer Page Size in the Buffers tab of the Global configuration.
- Increase the Maximum IP Reassembly Size property of the IP module configuration.
Sending and Receiving UDP Datagrams over MTU Size
The size of sending and receiving UDP datagrams are dependent on the following NDK configuration options, socket options, and OS Adaptation Layer definitions:
- NDK Configuration Options:
- Increase the Minimum Send Size property of the IP module socket configuration. See the NDK API Reference Guide.
- Increase the Minimum Read Size property of the IP module socket configuration.
- If you use
Cfg*()API calls for configuration, you can configure these IP module properties by using the following C code:
uint32_t tmp = 65500;
// configure NDK
CfgAddEntry(hCfg, CFGTAG_IP, CFGITEM_IP_IPREASMMAXSIZE,
CFG_ADDMODE_UNIQUE, sizeof(uint32_t), (unsigned char*) &tmp, 0);
CfgAddEntry(hCfg, CFGTAG_IP, CFGITEM_IP_SOCKUDPRXLIMIT,
CFG_ADDMODE_UNIQUE, sizeof(uint32_t), (unsigned char*) &tmp, 0);
// set socket options
NDK_setsockopt(s, SOL_SOCKET, SO_RCVBUF, &tmp, sizeof(int) );
NDK_setsockopt(s, SOL_SOCKET, SO_SNDBUF, &tmp, sizeof(int) );
- Socket Options:
SO_SNDBUF: See the NDK API Reference GuideSO_RCVBUF_- See the NDK API Reference Guide
- OS Adaptation Layer Definitions:
- Change the
MMALLOC_MAXSIZEdefinition in thepbm.cfile. (i.e.#define MMALLOC_MAXSIZE 65500) and rebuild the library - Increase the Memory Manager Buffer Page Size in the Buffers tab of the Global configuration.
- If you use
Cfg*()API calls for configuration, you can edit theMMALLOC_MAXSIZEdefinition in thepbm.cfile andRAW_PAGE_SIZEdefinition in themem.cfile. Then rebuild the appropriate OS Adaptation Layer library in/ti/ndk/os/lib.
- Change the
Timestamping UDP Datagram Payloads
The NDK allows the application to update the payload of UDP datagrams. The typical usage of this is to update the timestamp information of the datagram. This way, transmitting and receiving ends can more accurately adjust delivery delays depending on changing run-time characteristic of the system.
On the transmitting end:
- The application can register a call-out function per socket basis by using the
NDK_setsockopt()function. - The call-out function is called by the stack before inserting the datagram into driver’s transmit queue.
- It is the call-out function’s responsibility to update the UDP checksum information in the header.
- The following code section is a sample of how to control it:
void myTxTimestampFxn(unsigned char *pIpHdr) {
...
}
NDK_setsockopt(s, SOL_SOCKET, SO_TXTIMESTAMP, (void*) myTxTimestampFxn, sizeof(void*));
On the receiving end:
- The application can register a call-out function per interface basis by using the
EtherConfig()function. It is set in theNC_NetStart()function ofnetctrl.c. - The call-out function is called by the stack scheduler just before processing the packet.
- It is the call-out function’s responsibility to update the UDP checksum information in the header.
- The following code section is a sample of how to control it:
void myRcvTimestampFxn(unsigned char *pIpHdr) {
...
}
EtherConfig( hEther[i], 1518, 14, 0, 6, 12, 4, myRcvTimestampFxn);
In General
- Do not try to tune the Timer function frequency. Make sure it calls
llTimerTick()every 100 ms. - Watch for out of memory conditions. These can be detected by the return from some functions, but will also print out warning messages when the messages are enabled. These messages contain the acronym OOM for out of memory. (Out of memory conditions can be caused by many things, but the most common cause in the NDK is when TCP sockets are created and closed very quickly without using the
SO_LINGERsocket option. This puts many sockets in the TCP timewait state, exhausting scratchpad memory. The solution is to use theSO_LINGERsocket option.)
2.4.2 Debug Messages¶
Debug messages for TI-RTOS Kernel are handled using the System_printf() API, which is provided by XDCtools for use by TI-RTOS.
Debug output for FreeRTOS is not currently supported, so the subsections that follow do not apply to applications that use FreeRTOS. You may modify the OS Adaptation Layer source code to add other types of program output as desired.
2.4.2.1 Controlling Debug Messages¶
Debug messages for TI-RTOS Kernel also include an associated severity level. These levels are DBG_INFO, DBG_WARN, and DBG_ERROR. The severity level is used for two purposes. First, it determines whether or not the debug message will be printed, and second, it determines whether or not the debug message will cause the NDK to shut down.
By default, all debug messages are printed, and messages with a level of DBG_ERROR causes a stack shutdown. This behavior can be modified in the configuration as described in “Global Debug Configuration”. Or, you can modify it through the system configuration as described in “Controlling NDK and OS Options via the Configuration” and “Shutdown”. Also see the NDK API Reference Guide.
2.4.2.2 Interpreting Debug Messages¶
The following is a list of some of the TI-RTOS Kernel debug messages that may occur during stack operation, along with the most commonly associated cause.
2.4.2.2.1 TCP: Retransmit Timeout: Level DBG_INFO¶
This message is generated by TCP when it has sent a packet of data to a network peer, and the peer has not replied in the expected amount of time. This can be just about anything; the peer has gone down, the network is busy, the network packet was dropped or corrupted, and so on.
2.4.2.2.2 FunctionName: Buffer OOM: Level DBG_WARN¶
This message is generated by some modules when unexpected out of memory conditions occur. The stack has an internal resource recovery routine to help deal with these situations; however, a significant number of these messages may also indicate that there is not enough large block memory available, or that there is a memory leak. See the notes on the memory manager reports in this section for more details.
2.4.2.2.3 mmFree: Double Free: Level DBG_WARN¶
A double free message occurs when the mmFree() function is called on a block of memory that was not marked as allocated. This can be caused by physically calling mmFree() twice for the same memory, but more commonly is caused by memory corruption. See “Memory Corruption” for possible causes.
2.4.2.2.4 FunctionName: HTYPE nnnn: Level DBG_ERROR¶
This message is generated only by the strong checking version of the stack. It is caused when a handle is passed to a function that is not of the proper handle type. Since the object oriented nature of the stack is hidden from the network applications writer, this error should never occur. If it is not caused by the attempt to call internal stack functions, then it is most likely the result of memory corruption. See the notes on memory corruption in this section for possible causes.
2.4.2.2.5 mmAlloc: PIT ???? Sync: Level DBG_ERROR¶
This message is generated by the scratch memory allocation system. PIT is an acronym for page information table. Table synchronization errors can only be caused by memory corruption. See “Memory Corruption” for possible causes.
2.4.2.2.6 PBM_enq: Invalid Packet: Level DBG_ERROR¶
This message is generated by the packet buffer manager (PBM) module driver in the OS adaptation layer. When the PBM module initially allocates its packet buffer pool, it marks each packet buffer with a magic number. During normal operation, packets are pushed and popped to and from various queues. On each push operation, the packet’s magic number is checked. When the magic number is invalid, this message results. It is possible for an invalid packet to be introduced into the system when using the non copy sockets API extensions, but the vastly more common cause is memory corruption. See the notes on memory corruption in this section for possible causes.
2.4.3 Memory Corruption¶
Memory corruption errors may occur as NDK debug messages. This is because it is easy to corrupt memory on devices with cache. Often, applications are configured to use the full L2 cache. In this mode, any read or write access to the internal memory range of the CPU can cause cache corruption and hence cause memory corruption. Since the internal memory range starts at address 0x00000000, a NULL pointer can cause problems when using full cache.
To check to see if corruption is being caused by a NULL pointer, change the cache mode to use less cache. When there is some internal memory available, reads or writes to address 0x0 do not cause cache corruption (the application still may not work, but the error messages should stop).
Another way to track down any kind of cache corruption is to break on CPU reads or writes to the entire cache range. Code Composer Studio has the ability to trap reads or writes to a range of memory, but both cannot be checked simultaneously. Therefore, a couple of trials may be necessary.
Of course, it is possible that the memory corruption has nothing to do with the stack. It could be a wild pointer. However, since corrupting the cache can corrupt memory throughout the system, the cache is the first place to start.
2.4.4 Program Lockups¶
Many lockup conditions are caused by insufficiently sized task stacks. Therefore, using large amounts of stack is not recommended. In general, do not use the following code:
myTask()
{
char TempBuffer[2000];
myFun(TempBuffer);
}
but instead, use the following:
myTask()
{
char *pTempBuf;
pTempBuf = malloc(2000);
if (pTempBuf != NULL)
{
myFun(pTempBuf);
free(pTempBuf);
}
}
If calling a memory allocation function is too much of a speed overhead, consider using an external buffer.
This is just an example, with a little forethought you can eliminate all possible stack overflow conditions, and eliminate the possibility of program lockups from this condition.
2.4.5 Memory Management Reports¶
The memory manager that manages scratch memory in the NDK has a built in reporting system. It tracks the use of scratch memory closely (calls to mmAlloc() and mmFree()), and also tracks calls to the large block memory allocated (calls to mmBulkAlloc() and mmBulkFree()). Note that the bulk allocation functions simply call malloc() and free(). This behavior can be altered by adjusting the memory manager.
The memory report is shown below. It lists the max number of blocks allocated per size bucket, the number of calls to mmAlloc() and mmFree(), and a list of allocated memory. An example report is shown below:
48:48 ( 75%) 18:96 ( 56%) 8:128 ( 33%) 28:256 ( 77%)
1:512 ( 16%) 0:1536 0:3072
(21504/46080 mmAlloc: 61347036/0/61346947, mmBulk: 25/0/17)
1 blocks alloced in 512 byte page
38 blocks alloced in 48 byte page
18 blocks alloced in 96 byte page
8 blocks alloced in 128 byte page
12 blocks alloced in 256 byte page
12 blocks alloced in 256 byte page
Here, the entry 18:96 (56%) means that at most, 18 blocks were allocated in the 96 byte bucket. The page size on the memory manager is 3072, so 56% of a page was used. The entry 21504/46080 means that at most 21,504 bytes were allocated, with a total of 46,080 bytes available.
The entry mmAlloc: 61347036/0/61346947 means that 61,347,036 calls were made to mmAlloc(), of which 0 failed, and 61,346,947 calls were made to mmFree(). Note that at any time, the call to mmAlloc() plus the failures must equal the calls to mmFree() plus any outstanding allocations. Therefore, on a final report where the report is mmAlloc: n1/n2/n3, n1+n2 should equal n3. If not, there is a memory leak.
2.4.5.1 mmCheck - Generate Memory Manager Report¶
Syntax
void _mmCheck( uint32_t CallMode, int (*pPrn)(const char *,...) );
| Parameter | Description |
|---|---|
CallMode |
Specifies the type of report to generate |
pPrn |
Pointer to printf() compatible function |
Description
Prints out a memory report to the printf() compatible function pointed to by pPrn. The type of report printed is determined by the value of CallMode. The reporting function has the option of printing out memory block IDs. This means that the first uint32_t sized field in the memory block of each allocated block is printed in the report. This is a useful option when the first field of allocated memory stores an object handle type, or some other unique identifier.
Call Mode
Can be set to one of the following:
MMCHECK_MAP– Map out allocated memory, but do not dump ID’sMMCHECK_DUMP– Dump allocated block IDsMMCHECK_SHUTDOWN– Dump allocated block IDs & free scratchpad memoryNOTE: Do not attempt to use any
mmAlloc()functions after requesting aMMCHECK_SHUTDOWNreport!
Returns
None