Running Multiple Instances of Treck

Jump to: navigation, search

Table of Contents >> Appendix A: Configuration Notes


A set of new APIs described in this section have been added to allow users to run multiple instances of the Treck TCP/IP stack. Each instance of the Treck stack needs to be run in a given context. If the user wants to run multiple instances of the Treck TCP/IP stack, any Treck TCP/IP stack function should be called after a context has been set, except for the context insensitive functions described below.


Context Insensitive Functions

These Treck TCP/IP stack functions can be called from any context or prior to setting any context.

Function Comments
tfInitTreckMultipleContext() This function initializes the Treck global variables, prior to any context creation.
tfTimerUpdate() Called from a Timer task.
tfTimerUpdateIsr() Called from Timer ISR.


Initialization Sequence

First initialization, called before any other Treck TCP/IP stack call:

tfInitTreckMultipleContext(void);

For each context:

contextHandle = tfCreateTreckContext();
if (contextHandle != (ttUserContext)0)
{
    tfSetCurrentContext(contextHandle);
    errorCode = tfStartTreck();
}


Summary of New Context API's

Function Comments
tfInitTreckMultipleContext() Initializes multiple context global variables (valid across all contexts).
tfCreateTreckContext() Create a Treck context, i.e. allocate a structure containing all Treck variables for an instance of the Treck stack, and returns a pointer to the newly allocated structure.
tfSetCurrentContext() Set the current context handle to the one passed in (i.e. set the Treck global variable 'tvCurrentContext' to the passed parameter).
tfGetCurrentContext() Get the current context handle (i.e. the context handle stored in 'tvCurrentContext').


Enabling the Multiple Instances Code in Treck

To enable the multiple instances code in the Treck stack, enable the following macro in your <trsystem.h>:

#define TM_MULTIPLE_CONTEXT

For details and more compile time macros please see the Compile Time Macros page.

Blocking Mode/Non-Blocking Mode

The following indicates the modifications needed for each type of embedded kernel:

  1. No kernel
  2. Non-preemptive kernel
  3. Preemptive kernel


No kernel

All applications have to run in non-blocking mode. All calls to the Treck stack are made from a main loop. Example with 2 contexts and 2 interfaces per context:

errorCode = tfInitTreckMultipleContext();
contextHandle1 = tfCreateTreckContext();
contextHandle2 = tfCreateTreckContext();
 
tfSetCurrentContext(contextHandle1);
errorCode = tfStartTreck();
context1InterfaceHandle1 = tfAddInterface(...);
errorCode = tfOpenInterface(context1InterfaceHandle1, ...);
context1InterfaceHandle2 = tfAddInterface(...);
errorCode = tfOpenInterface(context1InterfaceHandle2, ...);
 
tfSetCurrentContext(contextHandle2);
errorCode = tfStartTreck();
context2InterfaceHandle1 = tfAddInterface(...);
errorCode = tfOpenInterface(context1InterfaceHandle1, ...);
context2InterfaceHandle2 = tfAddInterface(...);
errorCode = tfOpenInterface(context2InterfaceHandle2, ...);
 
for (;;)
{
    tfSetCurrentContext(contextHandle1);
    tfTimerExecute();
    if (tfCheckReceiveInterface(context1InterfaceHandle1) == TM_ENOERROR)
    {
        tfRecvInterface(context1InterfaceHandle1);
    }
    if (tfCheckReceiveInterface(context1InterfaceHandle2) == TM_ENOERROR)
    {
        tfRecvInterface(context1InterfaceHandle2);
    }
    <...Non-blocking application code for context 1...>
 
    tfSetCurrentContext(contextHandle2);
    tfTimerExecute();
    if (tfCheckReceiveInterface(context2InterfaceHandle1) == TM_ENOERROR)
    {
        tfRecvInterface(context2InterfaceHandle1);
    }
    if (tfCheckReceiveInterface(context2InterfaceHandle2) == TM_ENOERROR)
    {
        tfRecvInterface(context2InterfaceHandle2);
    }
    <...Non-blocking application code for context 2...>
}


Note Note: This sample code does not check for errors for ease of reading.

Also, for more than 2 contexts, it would make sense to save the context variables, and interface handles in a 2 dimensional array, and loop on the array indices.


Non-preemptive kernel

In this case, the applications can run in blocking mode.

  1. Each application task will have to set its Treck context prior to making the first Treck call.
  2. Inside each tfKernel...() call, the user must save the current Treck context before calling the OS and then restore the Treck context upon return from the OS call, since the current task could be pre-empted by a higher priority task during the OS call.
int tfKernel...(...)
{
      ttUserContext contextHandle;
      contextHandle = tfGetCurrentContext();
      <Make the OS call>
      tfSetCurrentContext(contextHandle);
}


Preemptive kernel

In this case, the OS could switch out a task at any time.

  1. Each application task will have to set its Treck context prior to making the first Treck call.
  2. Depending on the services provided by your kernel, you may need to save the Treck context handle in memory that is private to each task. Do this before the task makes its first Treck call.
  3. Check the level of support provided by your kernel and do one of the following:
    1. If the kernel provides a callback whenever a task resumes execution, you can use this callback to get the Treck context handle from the current task's private memory (saved in step 2 above) and call tfSetCurrentContext(). The application can run in blocking mode, as described in the previous section.

      If the kernel also provides a callback whenever a task suspends or gets preempted, you can use this callback to get the current Treck context (tfGetCurrentContext()) and save it in the current task's private memory. This means you can skip step 2 above.

    2. If the user can modify the OS to save the current Treck context on a task stack prior to a task being switched out, and restore the task Treck context when the task resumes, then the application can run in blocking mode, as described in the previous section.

      The user will need to modify the OS to save the current Treck context on a task stack prior to a task being switched out and restore the Treck context when the task resumes.

    3. If your kernel provides functions for allocating thread-local storage such that the Treck context handle can be retrieved in a task-specific way whenever it is needed, then follow the directions in the subsection below, Keeping the Current Treck Context Handle in Thread Local Storage.
    4. If none of the above methods can be applied, then the application will have to run in non-blocking mode from a single Treck task. All the calls to the Treck stack will be made from a main loop from within that single task as described in the No Kernel section above.

Keeping the Current Treck Context Handle in Thread Local Storage

Most preemptive, multitasking kernels support some form of storage that is private to each task. The C runtime library errno variable is a perfect example of this. If errno is global and shared by all tasks then it becomes useless because various tasks will be calling C functions that change errno and there is no guarantee that it will contain the correct value by the time that your task is able to read it.

The multitasking issues for the current Treck context are the same as those for errno. The solution is to replace the reference to the global variable with a call to a function that reads the value from the current task's private memory. That way, you are guaranteed to get the same value back that you set earlier in the task.

If your kernel supports thread-local storage, you can configure Treck to keep the current context in thread-local storage by uncommenting the following compile time macro in your <trsystem.h>:

#define TM_USE_KERNEL_CONTEXT

The Treck functions, tfSetCurrentContext() and tfGetCurrentContext(), will now reference functions tfKernelSetContext() and tfKernelGetContext() that you must provide when you create your kernel interface.

void tfKernelSetContext(ttUserContext context)
{
    /*
     * Store 'context' in the current task's local memory.
     */
}
 
ttUserContext tfKernelGetContext(void)
{
    ttUserContext context;
 
    /*
     * Load 'context' from the current task's local memory.
     */
    return context;
}


Device Driver Modifications

Each device driver should be modified as described in the "Further Device Driver Modifications to allow a device driver to be shared by several Ethernet Interfaces" section. The modification to the Device driver ISR is a little bit different, and is described below.


Device Driver ISR

The user should keep a global mapping between an interrupt vector, and an interface handle/context pair, instead of just a global mapping between an interrupt vector and an interface handle. In the device driver ISR, the user should save the current context, and then the user should set the Treck context as found in the global mapping described here, and call tfDeviceGetPointer() in order to access the device driver specific data. Before returning from the ISR, the user should set the context to the saved value at the beginning of the ISR function.


Table of Contents >> Appendix A: Configuration Notes