Creating a Kernel Interface

Jump to: navigation, search
<< Building the Library
Hooking in the Timer >>



This section describes how to setup the RTOS/Kernel Interface. Treck Real-Time TCP/IP is designed so that it can use nearly any Real Time Operating System. If you do not have an RTOS, then you will still need to read this section so that you can understand how the "No RTOS" version of the kernel interface is put together. There are already pre-configured RTOS/kernel Interfaces. These are located below the `source` directory in the following locations:


RTOS Processor Mfg. Processor Type Compiler Location
uC/OS Motorola CPU32 SDS `source/kernel/ucos/motorola/cpu32/sds`
uC/OS Motorola CPU32 MRI `source/kernel/ucos/motorola/cpu32/mri`
uC/OS Motorola PowerPC Diab `source/kernel/ucos/motorola/ppc/ddi`
uC/OS Intel x86 Real Borland v4.5/5.0 `source/kernel/ucos/intelx86/real/boland`
uC/OS Intel x86 Real Microsoft v6.0 `source/kernel/ucos/intelx86/real/msc60`
AMX Motorola CPU32 SDS `source/kernel/amx/cj`
AMX Intel x86 Real Borland `source/kernel/amx/aj`
ThreadX Motorola PowerPC Green Hills `source/kernel/threadx/motorola/ppc/ghs`


The file name for the kernel interface is normally named as follows: trXXX.c. Where XXX is the name of the kernel. For example the uC/OS interface is named trucos.c. In each of these files you will find the RTOS/kernel interface functions. You will find the TM_FAR macro used in these interface functions. This macro is defined to be far on the Intel processor running in real/small model. You will also notice that these files all include <trsocket.h>. This single file contains all of the types that you will need to build the RTOS/Kernel interface.


Note Note: Treck provides these RTOS interface files as examples only. If you are using one of the sample RTOS interfaces provided, please inspect the file as it may require some customization. The comments provided in the files will indicate any special configuration required by the user.


Initialization

The function tfKernelInitialize() is used to initialize the Treck/kernel interface. It is not needed for all RTOS/Kernels. The system calls this function prior to any other kernel calls. You should think of this function as a way to get things initialized for your environment. If this function is not needed, it should be a stub function. tfKernelUnInitialize() is used to uninitialize the kernel when the stack is stopped with tfStopTreck().

Example:

void tfKernelInitialize (void)
{
/* Initialize the Treck to Kernel Interface */
    return;
}

Memory Allocation and Free

The functions tfKernelMalloc() and tfKernelFree() are used to allocate and free memory that is to be used by the Treck protocol stack. They are the same definition as the ANSI malloc() and free() functions. We have provided these functions to give you the flexibility to use the memory allocation that you want for your environment. You can use your RTOS malloc() and free(). These functions are used to pass memory blocks to the Treck Memory System. Unless you have disabled the dynamic memory feature, the Treck Memory System will not free these blocks unless they are larger than the Treck Memory System keeps track of. By definition, there will be more mallocs than frees, however, as time progresses there will be fewer and fewer mallocs.

Examples:

void TM_FAR *tfKernelMalloc (unsigned size)
{
    return(malloc(size));
}
void tfKernelFree (void TM_FAR *memoryBlock)
{
    free(memoryBlock);
}

Treck Simple Heap

When an external memory manager is not available or not desired due to overhead, Treck can operate with its own simple heap implementation. The Treck simple heap can be enabled and configured using the following compile time macros.


When dynamic allocation of the simple heap is enabled, the following function must be used.


Critical Section Handling

The functions tfKernelSetCritical() and tfKernelReleaseCritical() are used to set critical sections around code that is NOT reentrant. The critical sections are used to prevent ANY other calls into the protocol stack. These are used to prevent one task from updating a pointer while another task is accessing it. Without critical sections, there is the chance of corrupting pointers if a pointer update is interrupted by another task or an ISR. The Treck protocol stack is designed to keep these critical sections to a minimum (usually about five assembly instructions).


Example for an Intel x86:

void tfKernelSetCritical (void)
{
    _asm("cli");
}
void tfKernelReleaseCritical (void)
{
    _asm("sti");
}
Note Note: If you can guarantee that no preemption of Treck code will occur by another task or ISR calling a Treck function (this includes the tfNotifyInterfaceIsr() function), then the critical sections are not needed. It is recommended that you continue to use the critical sections unless you are absolutely sure that no preemption of Treck code will occur.

You can also in-line these functions by defining tm_kernel_set_critical and tm_kernel_release_critical to be the assembly for these functions to save the extra function call in the <trsystem.h> file. You can find examples in <trmacro.h>. This will enhance the data transfer speed.

Error Logging

The function tfKernelError() is used to report unrecoverable error messages and cause a restart of the system. Errors that are reported include corrupt pointers and invalid memory. The Treck protocol stack must not continue once this function has been called.

Example:

void tfKernelError (char TM_FAR *functionName,
                    char TM_FAR *errorMessage)
{
    printf("Fatal Error in Function %s: %s \n",
           functionName, errorMessage);
    while(1)
    {
/*
* LOOP Forever until the watchdog timer fires and
* reboots
*/
        ;
    }
}

Warning Information Logging

The function tfKernelWarning() is used to convey to the user any abnormalities that occur during the normal operation of the Treck protocol stack. These would include such items as bad checksums. These are not fatal errors and the stack should continue to operate normally. This function is provided to help the user diagnose network problems.

Example:

void tfKernelWarning (char TM_FAR *functionName,
                      char TM_FAR *warningMessage)
{
    printf("Warning in Function %s: %s \n",
           functionName, warningMessage);
}

Task Suspend and Resume

This set of functions is the most complex portion of the RTOS/Kernel Interface. For task suspend and remove, we rely on a primitive that is available in most RTOS/Kernels. This primitive is a counting semaphore with an associated counter initialized to zero. The features that we are looking for in the primitive are:

  • It can be posted to before it is pended on
  • Only one task waiting on the primitive will be re-scheduled when a post occurs
  • The primitive is not tied to a specific task
Note Note: If your RTOS does not provide a counting semaphore, but does have an event flag, please use the implementation provided in 'source/kernel/trcousem.c`, or `source/kernel/trctsem2.c`. Follow the instructions given in the file you picked and in RTOS Notes.

An example of why we need this feature is as follows:

  1. The user calls the socket recv() API in blocking mode.
  2. There is no data to receive so the Treck stack starts to call the 'pend' routine.
  3. A context switch occurs for a receive task, before the user task finished the 'pend' call.
  4. The receive task processes the incoming packet and queues it to the socket the user had opened, and calls 'post'.
  5. A context switch then occurs back to the user task that will then finish calling 'pend'.

Without the ability to post before the pend, the user task calling recv() will wait forever. The other option is to call 'pend' in a critical section (which is VERY undesirable for a real-time system). A counting semaphore with an associated counter initialized to zero solves this problem, so we use this primitive for task suspend and resume.

We must first look at the data type that is used to carry the counting semaphore. For this we use a union of all basic types. Because we use this to hold the counting semaphore, we are able to integrate to any RTOS/Kernel. This union is defined in the file <trsocket.h>, as follows:

#define trsocket.h
 
/*
 * Generic Parameter Passing Union Typedef
 */
typedef union tuUserGenericUnion
{
    ttUser32Bit     gen32bitParm;
    ttUserS32Bit    genSlongParm;
    void TM_FAR    *genVoidParmPtr;
    unsigned short  gen16BitParm;
    short           genS16BitParm;
    unsigned int    genUintParm;
    int             genIntParm;
    unsigned char   gen8BitParm;
    char            genCharParm;
} ttUserGenericUnion;
 
typedef ttUserGenericUnion TM_FAR * ttUserGenericUnionPtr;


By using the ttUserGenericUnion data type, you will be able to pass your counting semaphore between Treck protocols and your RTOS. A set of three functions provides access between the Treck protocol stack and your RTOS. These functions are used to create, pend on, and post on a counting semaphore that will be used by the protocol stack. Because a task cannot pend more than once at any given time, and because the Treck stack re-uses unused counting semaphores dynamically, the maximum number of counting semaphores that will be needed would be equal to the number of tasks that are calling Treck. The RTOS functions below are only examples. You will need to map these calls into your own RTOS. If you are not using an RTOS, then these functions should be stubs.

The tfKernelCreateCountSem() function is used to create a counting semaphore that is provided by the operating system with an associated counter initialized to zero. If the counting semaphores are created at compile time with your operating system, then you simply pass one of these back to Treck. An example of this function is as follows:

int tfKernelCreateCountSem (ttUserGenericUnionPtr countingSemaphore)
{
    int retCode;
 
/* Create and Initialize the semaphore to zero */
    countingSemaphore->genVoidParmPtr = RTOSSemCreate(0);
    if (countingSemaphore->genVoidParmPtr != (void TM_FAR *)0)
    {
        retCode = 0;
    }
    else
    {
        retCode = -1;
    }
    return(retCode);
}

The function tfKernelPendCountSem() is used to pend or wait on a counting semaphore. This function must wait indefinitely until the post occurs. We pass back in the counting semaphore in the union that was used for the create. An example of this is as follows:

int tfKernelPendCountSem (ttUserGenericUnionPtr countingSemaphore)
{
    int retCode;
 
    if((RTOSSemPend(countingSemaphore>genVoidParmPtr)) == RTOS_NO_ERR)
    {
        retCode = 0;
    }
    else
    {
        retCode = -1;
    }
    return(retCode);
}

The function tfKernelPostCountSem() is used to wake up another task or thread that has called tfKernelPendCountSem(). An example of this function is as follows:

int tfKernelPostCountSem (ttUserGenericUnionPtr countingSemaphore)
{
    int retCode;
 
    if ((RTOSSemPost(countingSemaphore->genVoidParmPtr)) == RTOS_NO_ERR)
    {
        retCode = 0;
    }
    else
    {
        retCode = -1;
    }
    return(retCode);
}

ISR Interface

The ISR interface is used to provide an interface for device drivers (as device drivers are the only items that need an interrupt service routine). In the ISR interface, we need to be able to install an interrupt service routine and provide a mechanism to notify the protocol stack about ISR events. The function tfKernelInstallIsrHandler() is used to install an ISR handler. Device drivers written by Treck, Inc. normally call this function. It is not called directly by the Treck protocol stack. If you do not use a Treck device driver, then you will not need this function in your interface. If you are using an Treck driver, then this function is used to install the device drivers interrupt handler. Our drivers do not do any packet processing within the ISR. They simply notify task level routines that an event has occurred.

void tfKernelInstallIsrHandler(ttUserIsrHandlerPtr funcPtr,
                               unsigned long offSet)
{
/*
* funcPtr is the Function To Be Called as the Handler
* offSet is the offset into the Interrupt Vector Table
*/
    RTOSInstallISR((void TM_FAR *)funcPtr, offSet);
}

Device Interface Routines

The set of four functions tfKernelCreateEvent(), tfKernelPendEvent(), tfKernelIsrPostEvent(), tfKernelTaskPostEvent() are used internally by the Treck device interface routines. They are only needed if the user wishes to use, and to block in, the receive task, the transmit task, or the send complete task. tfKernelTaskYield() is only used if the user uses a transmit task, and wishes to call the Treck function tfInterfaceSpinLock() inside the device driver send function to let the other tasks run, while waiting for the device driver to be ready to transmit.


No RTOS or event scheduler with main loop:

If you do not have any RTOS, or if you have an event scheduler with a main loop implementation, then you have neither defined TM_TRECK_PREEMPTIVE_KERNEL, nor TM_TRECK_NONPREMTIVE_KERNEL in your <trsystem.h>. In that case, then the first four functions are not needed, because they will not be called, since in that case the user is not allowed to block.


Preemptive or non-preemptive kernel:

If you are using a preemptive kernel, or a non-preemptive kernel, then you have defined either TM_TRECK_PREEMPTIVE_KERNEL or TM_TRECK_NON_PREEMPTIVE_KERNEL in your <trsystem.h>. In that case, you will need some or all of these first 4 functions, as shown in the following table, depending on which of the following additional macros you have defined in your <trsystem.h>.

TM_TASK_RECV TM_TASK_XMIT TM_TASK_SEND
tfKernelCreateEvent
tfKernelCreateEvent
tfKernelCreateEvent
tfKernelPendEvent
tfKernelPendEvent
tfKernelPendEvent
tfKernelIsrPostEvent
tfKernelIsrPostEvent
tfKernelTaskPostEvent

Note that if you have not defined any of these macros, you do not need to provide any of these 4 functions, since they will not be called.


tfKernelCreateEvent()

The function tfKernelCreateEvent() is used to create a counting semaphore, or binary semaphore, of event flag. It is only called from task level. One event will be created per interface, and per TM_TASK_XXXX macro defined. For example, if you have configured 2 interfaces, and have defined TM_TASK_RECV, TM_TASK_XMIT, and TM_TASK_SEND, then you will need 6 events. At most one task will be waiting on that kernel primitive.

void tfKernelCreateEvent (ttUserGenericUnionPtr eventPtr)
{
    void *semaphorePtr;
 
/* Initialize the semaphore to zero */
    semaphorePtr = RTOSSemCreate(0);
    if (semaphorePtr != (void *)0)
    {
        eventPtr->genVoidParmPtr = (void *)semaphorePtr;
    }
    else
    {
        tfKernelError ("tfKernelCreateEvent",
                       "Unable to Create Semaphore");
        tm_thread_stop;
    }
}


tfKernelPendEvent()

The function tfKernelPendEvent is used to pend on an event that will be posted to. The task calling this function must wait indefinitely while waiting for the post event call. It is only called from task level, either from tfWaitReceiveInterface(), or from tfWaitXmitInterface(), or from tfWaitSentInterface().

/*
 * Wait on an Event
 */
void tfKernelPendEvent (ttUserGenericUnionPtr eventPtr)
{
    int kernelError;
 
    RTOSSemPend(eventPtr->genVoidParmPtr, 0, &kernelError);
    if (kernelError != RTOS_NO_ERR)
    {
        tfKernelError ("tfKernelPendEvent",
                       "Unable to Pend on Semaphore");
        tm_thread_stop;
    }
}


tfKernelIsrPostEvent()

The function tfKernelIsrPostEvent() is used to signal that an event has occurred and resume tasks that are waiting via tfKernelPendEvent(). This is called from tfNotifyInterfaceIsr(), itself called from an interrupt handler.

Note Note: Most RTOS have special calls or wrappers when a system call is called from an interrupt handler. Please consult your RTOS manual for the proper way to post from an interrupt handler.
void tfKernelIsrPostEvent (ttUserGenericUnionPtr eventPtr)
{
    int kernelError;
 
    kernelError = RTOSSemPost(eventPtr->genVoidParmPtr);
    if (kernelError != RTOS_NO_ERR)
    {
        tfKernelError ("tfKernelIsrPostEvent",
                       "Unable to Post on Semaphore");
    }
}


tfKernelTaskPostEvent()

The function tfKernelTaskPostEvent() is used to signal that an event has occurred and resume the transmit task waiting via tfKernelPendEvent(). This call is only called from the Treck stack in the context of a task. This call is needed if the user has defined TM_TASK_XMIT macro in <trsytem.h>, or if the user calls tfNotifyInterfaceTask() and has defined either TM_TASK_RECV or TM_TASK_SEND.

void tfKernelTaskPostEvent (ttUserGenericUnionPtr eventPtr)
{
    int kernelError;
 
    kernelError = RTOSSemPost(eventPtr->genVoidParmPtr);
    if (kernelError != RTOS_NO_ERR)
    {
        tfKernelError ("tfKernelPostEvent",
                       "Unable to Post on Semaphore");
    }
}


tfKernelTaskYield()

The function tfKernelTaskYield is used to yield the CPU, and let other tasks run. This function is called only if the user uses a transmit task, and calls the Treck function tfInterfaceSpinLock() from within the device driver send function to let other tasks run and access the device driver routines, while waiting for the device driver to be ready to transmit.

void tfKernelTaskYield (void)
{
     RTOSYield ();
}

This completes the RTOS interface. After you have made these functions map onto your RTOS, you are ready to move onto the next phase. We suggest that you compile the code that you have written in this phase.

Timekeeping

Some Treck application software (e.g. HTTPD, SMTP) needs to timestamp headers with the current time and date. The user provides the API to the kernel or hardware timekeeping facility with the tfKernelGetSystemTime() function prototyped below. Treck calls tfKernelGetSystemTime() with pointers to variables through which the user's code supplies the standard UTC time in days and/or seconds since January 1, 1970.

int tfKernelGetSystemTime(
    ttUser32Bit * daysPtr,
    ttUser32Bit * secondsPtr)
{
    /*
     * Store the UTC time as *daysPtr + *secondsPtr.
     * Typically, *daysPtr is set to 0 and *secondsPtr is set
     * to the number of seconds since January 1, 1970.
     * Return TM_KERN_OKAY on success, TM_KERN_ERROR otherwise.
     */
}
Note Note: Function tfGetUtcTime() can be called from the user's application to convert from seconds/days since 1970 to structured date/time format.

Other Treck application software (e.g. SNTP) can make use of a high frequency counter/timer for precise interval timing, if the kernel or hardware support exists. You can provide an API with the tfKernelGetPreciseTime() and tfKernelGetPreciseFreq() functions prototyped below.

int tfKernelGetPreciseTime(ttUser64BitPtr valPtr)
{
    /*
     * Read and return a high precision time value.
     * The time value returned is a 64-bit wrap-around, incrementing counter
     * that is used for precise interval measurement.
     * The value does not necessarily map to UTC time.
     * If the hardware provides less than 64 bits, shift the value left so that
     * the most significant bit is bit 63. Otherwise, there will be a time gap.
     * If wraparound is not 2's complement (e.g. has sequence 997, 998,
     * 999, 0, 1, ...), there will be a time gap. Multiply the value by
     * 2**64 / M, where M is the modulus of the counter (e.g. 1000).
     * Return TM_KERN_OKAY on success, TM_KERN_ERROR otherwise.
     */
}
 
int tfKernelGetPreciseFreq(ttUser64BitPtr valPtr)
{
    /*
     * Return the frequency of the high precision counter/timer in.
     * ticks per second. The value returned is subject to the same arithmetic
     * adjustment that is used in tfKernelGetPreciseTime() to get 64-bit
     * seamless wraparound. For example, a 1000Hz 32-bit hardware counter
     * value is shifted left 32 bits for 64-bit seamless wraparound; it's
     * frequency is 1000<<32.
     * Return TM_KERN_OKAY on success, TM_KERN_ERROR otherwise.
     */
}

Treck will only make use of the tfKernelGetPreciseTime() and tfKernelGetPreciseFreq() functions if you uncomment the following Compile Time Macro in your trsystem.h module. The macro is undefined, by default.

#define TM_USE_KERNEL_PRECISE_TIME

Sample trkernel.c code using Microsoft® Windows functions:

#include <windows.h>
#include <time.h>
#include "trsocket.h"
#include "trmacro.h"
#include "trtype.h"
#include "trproto.h"
#include "trglobal.h"
 
int tfKernelGetSystemTime(ttUser32BitPtr daysPtr, ttUser32BitPtr secondsPtr)
{
    *daysPtr = 0;
    *secondsPtr = (ttUser32Bit)time(NULL); /* cast down on 64-bit CPUs */
    return TM_KERN_OKAY;
}
 
#ifdef TM_USE_KERNEL_PRECISE_TIME
int tfKernelGetPreciseTime(ttUser64BitPtr valPtr)
{
    LARGE_INTEGER   perfCount;
    int             errorCode;
 
    if (QueryPerformanceCounter(&perfCount))
    {
        errorCode = TM_KERN_OKAY;
        tm_user_64Bit_asgn32x2(perfCount.HighPart, perfCount.LowPart, *valPtr);
    }
    else
    {
        errorCode = TM_KERN_ERROR;
    }
    return errorCode;
}
 
int tfKernelGetPreciseFreq(ttUser64BitPtr valPtr)
{
    LARGE_INTEGER   perfCount;
    int             errorCode;
 
    if (QueryPerformanceFrequency(&perfCount) && (perfCount.QuadPart != 0))
    {
        errorCode = TM_KERN_OKAY;
        tm_user_64Bit_asgn32x2(perfCount.HighPart, perfCount.LowPart, *valPtr);
    }
    else
    {
        errorCode = TM_KERN_ERROR;
    }
    return errorCode;
}
#endif /* TM_USE_KERNEL_PRECISE_TIME */

Supporting Multiple Instances of Treck

Running multiple instances of the Treck stack in a multitasking environment can pose challenges. If TM_MULTIPLE_CONTEXT is defined, Treck will keep a pointer to the current context (Treck instance) in a global variable. The user can change the current context at any time by calling tfSetCurrentContext(). If multiple tasks are changing the current context in a preemptive environment, then this presents a problem for any given task, as the current context could change during execution by virtue of a task switch.

Fortunately, most kernels support some form of thread-local storage so that the current context pointer can be saved for each task in memory that no other task can change. The kernel may provide a callback when a task resumes execution that allows the application to get the Treck context pointer from the current task's storage and call tfSetCurrentContext(). If not, then the current context pointer must be retrieved from the task's storage whenever Treck needs to access it's instance variables. The following text discusses how this is accomplished.

Enable the supporting code 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 as part of 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;
}


<< Building the Library
Hooking in the Timer >>