www.beck-ipc.com

User specific TCPIP Device driver - SC12 @CHIP-RTOS V1.10


    IPC@CHIP Documentation Index

Introduction


Adding a user specific device driver/linklayer interface to the TCP/IP stack

The @CHIP-RTOS of the IPC@CHIP provides four internal TCP/IP device interfaces:

    1. Ethernet controller
    2. PPP server
    3. PPP client (PPP uses one of the serial ports of the IPC@CHIP)
    4. Internal loopback   (Virtual loopback device with IP address 127.0.0.1)

For each of these internal devices the necessary specific driver functions are implemented inside of the @CHIP-RTOS.

The provided TCP/IP API calls 0xA0 - 0xA7 allow the application developer to install an additional TCP/IP driver interface for a connected hardware device (e.g. an additional UART or an Ethernet controller).   This new interface then has its own IP configuration and is used by the TCP/IP stack for IP communication in the same way as with the pre-installed internal devices.

The following sections explain how to implement and install a user specific device driver for TCP/IP.   The generic example code shown here uses C-library functions provided for the TCP/IP API, which can be found in source file TCPIP.C.   We are also using several functions from the C-library files HWAPI.C and RTOS.C.   The C-library files are available at www.beck-ipc.com in the Internet download area of the IPC@CHIP.   All needed TCP/IP related types and constants are declared in the C-library header files TCPIPAPI.H and TCPIP.H.

Important notes:

1.   IP configuration of the user device is not adjustable with the settings in the IP section of chip.ini configuration file.   You can create your own section in chip.ini for storing IP configuration of the new device interface with BIOSINT 0xA0 functions 0x23/0x24.   For possible IP configuration via the @CHIP-RTOS UDP config server see Install UDP Config Server Callback.

2.   Setting the default gateway (reachable via the installed interface) is now possible with the expanded ADD_DEFAULT_GATEWAY API.

In the following sections we describe the set of driver specific functions you may have to provide.   Possible implementations of an interrupt service function and receiver task for the device interface are provided.   These device driver functions must be installed with the API call DEVOPENIFACE.   These a callback functions which are invoked by the internal TCP/IP stack of the @CHIP-RTOS.   Do not call these functions directly from within your application!

The driver functions are internally locked by semaphores and block every other device driver function. Because of this behavior, it's not advisable to wait (sleep) for long periods of time as this can lead to deadlock situations (primarily between the send and recv calls).


  • Device Open function
  • Device Close function
  • Device Send function
  • Device Receive function
  • Device FreeReceive function
  • Device Get PhysicalAddress function
  • Implementation of an interrupt service routine (ISR) and receiver task
  • Install the device driver

  • Device Open function

    The TCP/IP stack calls this (optional) function to initialize the hardware and (optional) to install an Interrupt Service handler.   If Borland C compilers are used the driver functions must be declared as huge (see below).   Microsoft C users must declare driver functions as far _saveregs _loadds .

    The function should return 0 if initialization was successful.   If initialization failed, return -1.

    Generic Example:

                
    int huge myDevOpen(DevUserIfaceHandle ifaceHandle)
    
    {
    
        // Install (if necessary) a RTOS Interrupt Service function with
    
        //  HWAPI handler function 0xA1 service 0x84
    
        return 0;
    
    }


    Top of list
    Index page

    Device Close function

    The TCP/IP stack will execute this (optional) function when the device driver interface is closed with the DEV_CLOSE_IFACE API call.

    This callback function should return 0 if the closing of the device was successful, otherwise -1 on failure.

    Generic example:

                
    int huge myDevClose(DevUserIfaceHandle ifaceHandle)
    
    {
    
       // DeInitialize the hardware
    
       // Remove the ISR handler
    
       return 0;
    
    }


    Top of list
    Index page

    Device Send function

    This callback function is used by the TCP/IP stack to send the data out the device.   The TCP/IP stack does not call this function from within a separate transmit task.   This callback executes in the thread which made the send call, e.g. API_SEND API.

    This callback function should return 0 if the sending of data was successful, otherwise -1 on error.

    Important:
      If the input parameter flag has value 1 (this indicates this is last frame in block) you must call DEV_SND_COMPLETE to tell the TCP/IP stack that the send buffer is no longer in use.

    Generic example:

                
    int huge myDevSend(DevUserIfaceHandle ifaceHandle,
    
                       unsigned char far * dataPtr,
    
                       int dataLength,
    
                       int flag )
    
     {
    
         int errorcode;
    
    
    
         // Hardware specific: Send the data (dataPtr) out the device
    
    
    
         // Do not wait(sleep) here for indefinite times,
    
         //  to avoid blocking of other function calls.
    
    
    
         // Is this now the last frame in message block?
    
         if (flag & 0x1)    // Bit0 flag set?
    
         {
    
             // Inform TCP/IP stack that transmit buffer is now free.
    
             Dev_Send_Complete(ifaceHandle, &errorcode);  // C-Lib
    
         }
    
         return 0;
    
     }

    Related Topics

    DEV_SND_COMPLETE API - Dev_Send_Complete's implementation
    DevUserIfaceHandle type definition

    Top of list
    Index page

    Device Receive function


    In this function, a received packet is passed back up into the protocol stack.   The TCP/IP stack calls this function to receive a data frame from the device.   TCP/IP calls this function from within your separate receiver task, which you are required to create (see final example).

    The receive callback should return 0 if receiving of data was successful, otherwise -1 on failure.

    Important :
      It's optional but recommended to store incoming data in a buffer from the TCP/IP pre-allocated memory pool (see TCPIPMEM).   API call DEV_GET_BUF returns you a buffer pointer for storing the incoming data (see example below).

    If you are using your own buffer allocation for storing the incoming data, you must null out the location referenced by the input parameter bufferHandle (see example).   In this case, you should also implement and install the device driver callback function:
    int (far * DevFreeRecvFunc)(DevUserIfaceHandle ifaceHandle,
                        unsigned char far * dataPtr);


    The TCP/IP stack calls this function to indicate that the receive buffer is no longer used by TCP/IP.   The vector to this callback is placed in the DevFreeRecv member of the DevUserDriver structure at the DEV_OPEN_IFACE call.

    Two generic examples for receiver functions follow:

      myDevReceive1 : Using buffer from the TCP/IP memory pool
      myDevReceive2 : Using your own receive buffer

                
    // Generic example using TCP/IP memory pool receive buffer:
    
    
    
    int huge myDevReceive1(DevUserIfaceHandle ifaceHandle,
    
                           unsigned char far * far * dataPtr,
    
                           int far * dataLength,
    
                           DevUserBufferHandle bufferHandle)
    
    {
    
        int                 errorCode;
    
        unsigned int        rcvdLength;
    
        unsigned char far  *tcp_buffer ;
    
    
    
        // Hardware specific: Check how many incoming data bytes are available.
    
        rcvdLength = .....;   // =byte count
    
    
    
        // Get a buffer from TCP/IP by calling API service 0xA5 and save at dataPtr
    
        Dev_Get_Buffer(bufferHandle, dataPtr, rcvdLength);  // C-Lib call
    
        tcp_buffer = *dataPtr ;       // Check if memory allocation successful
    
        if (tcp_buffer != (unsigned char far *)0)
    
        {
    
           // Hardware specific: Move received data from device to tcp_buffer
    
           // Do not wait (sleep) here for indefinite times,
    
           //   to avoid blocking of other function calls.
    
    
    
           *dataLength = recvdLength;   // Report number of bytes now in tcp_buffer
    
           return 0;  // success
    
        }
    
        else
    
        {
    
           return -1; // out of memory
    
        }
    
    }
    
    
    
    
    
     // Generic example for using your own receive buffer:
    
    
    
    int huge myDevReceive2(DevUserIfaceHandle ifaceHandle,
    
                           unsigned char far * far * dataPtr,
    
                           int far * dataLength,
    
                           DevUserBufferHandle bufferHandle)
    
    {
    
        // Save the pointer to the beginning of the data
    
        *dataPtr = myBuffer;  // myBuffer somehow allocated by the user
    
    
    
        // Hardware specific: Read data from your device and store in myBuffer
    
    
    
        // Save the length (in bytes) of received data
    
        *dataLength = deviceDataLength;
    
    
    
        // IMPORTANT: Null out the bufferhandle pointer
    
        *bufferHandle = (DevUserBuffer)0;
    
        return 0;
    
    }

    Related Topics

    DEV_GET_BUF API - Dev_Get_Buffer's implementation
    DevUserIfaceHandle type definition

    Top of list
    Index page

    Device FreeReceive function

    Implementation of this function is necessary if you decide to use your own buffers for receiving incoming data.

    The TCP/IP stack will call this function to inform you that the receive buffer (input parameter dataPtr) is no longer used by TCP/IP.

    This callback should return 0 if ok, else -1 on failure.

    Generic example:

                
    int huge myDevFreeRecv(DevUserIfaceHandle ifaceHandle, unsigned char far *dataPtr )
    
    {
    
         // Somehow free your allocated buffer at dataPtr
    
         my_free(dataPtr);
    
    
    
         return 0;
    
    }

    Related Topics

    DevUserIfaceHandle type definition

    Top of list
    Index page

    Device Get PhysicalAddress function

    This function applies only to Ethernet controllers.

    The 6 byte array referenced by the PhysicalAddress input parameter should be filled with the MAC address of your connected Ethernet controller.

    Generic example:

                
    int huge myDevGetPhysAddr(DevUserIfaceHandle ifaceHandle,
    
                              unsigned char far * physicalAddress)
    
    {
    
        // Hardware specific: copy MAC address into physicalAddress
    
        _fmemcpy(physicalAddress, myEthernet_MAC, 6) ;
    
        return 0;
    
    }

    Related Topics

    DevUserIfaceHandle type definition

    Top of list
    Index page

    Implementation of an interrupt service routine (ISR) and receiver task

    The implementation of a device specific ISR is optional.   If your hardware device is able to generate interrupts on device events (e.g. incoming data available), you can implement an ISR like the example below.   The CPU time spent within an ISR must be keep to a minimum, as the length of this interrupts masked period impacts the interrupt latency of the other critical system ISR's.   Consequently, your ISR should only notify events (incoming data, error,..) at the device and not directly handle device events itself (e.g. retrieve incoming data) immediately within the ISR.

    With API call DEV_NOTIFY_ISR the ISR should wakeup a user provided task, which receives the incoming data from the device and moves the data into the TCP/IP stack.   This task should use API call DEV_RECV_WAIT and DEV_RECV_IFACE (see example below).

    Instead of a creating a new task, it is also possible to use your program's main thread for receiving by having it perform the MyReceiveTask() actions shown below.

    If your device doesn't support interrupts, you could create a polling task (or again, simply use your program's main thread for this purpose) which periodically checks your device for incoming data as illustrated in the MyReceiveTask_Polling example below.

    Important : An ISR must be installed as a RTOS ISR with the Install Interrupt Service Routine of the Hardware API.

    Generic examples for an ISR and a two forms of receiver task functions follow.

                
    // Interrupt Service Routine
    
    
    
    void interrupt MyDeviceISRHandler(void)
    
    {
    
       int receivedFrames;
    
       int errorCode;
    
    
    
       // Hardware specific: Check if there are incoming data packets available
    
    
    
       // Wakeup receiver task
    
       Dev_Notify_ISR(MyDevHandle, receivedFrames, 0, &errorCode);  // C-Lib call
    
    
    
       // Note: Issue no EOI here.
    
       //   (EOI for the ISR is issued inside of the CHIP-RTOS.)
    
    }
    
    
    
     // Generic example for a receiver task function, which waits for an event from ISR:
    
    
    
    void huge MyReceiveTask(void)
    
    {
    
        int errorCode;
    
        int statRecv;
    
        // Optional: do some initialization
    
    
    
        while(1)
    
        {
    
           // Wait for a wakeup from ISR
    
           Dev_Recv_Wait(mydevdriver.IfaceHandle, &errorCode);   // C-Lib call
    
    
    
           // After wakeup received and move incoming data into the stack
    
           do
    
           {              // C-Lib call
    
              statRecv = Dev_Recv_Interface(mydevdriver.IfaceHandle, &errorCode);
    
           } while (statRecv != -1);
    
        }
    
    }
    
    
    
    
    
     // Generic example for receiver task, polling for incoming data:
    
    
    
    void huge MyReceiveTask_Polling(void)
    
    {
    
        int errorCode;
    
    
    
        // Optional: do some initialization
    
    
    
        // Wait for completion of interface installation (Intr 0xAC 0xA0)
    
        while (install_done == 0)
    
        {
    
           RTX_Sleep_Time(10); // Go to sleep for a defined time.
    
        }
    
    
    
        while(1)
    
        {
    
           // Check if there is data available inside of your device.
    
           if (myDeviceDataAvail())
    
           {
    
               // Receive and move incoming data into the stack
    
               Dev_Recv_Interface(mydevdriver.IfaceHandle, &errorCode);
    
           }
    
           RTX_Sleep_Time(10); // Go to sleep for a defined time.
    
        }
    
    }

    Related Topics

    DEV_NOTIFY_ISR API - Dev_Notify_ISR's implementation
    DEV_RECV_WAIT API - Dev_Recv_Wait's implementation
    DEV_RECV_IFACE API - Dev_Recv_Interface's implementation
    RTX_SLEEP_TIME API - RTX_Sleep_Time's implementation

    Top of list
    Index page

    Install the device driver

    Based on the previous sections of this document, the following generic example should make clear the main steps required to install a user implemented device driver:

                
    #include "tcpip.h"
    
    #include "rtos.h"
    
    
    
    int huge myDevOpen(DevUserIfaceHandle ifaceHandle);
    
    
    
    int huge myDevClose(DevUserIfaceHandle ifaceHandle);
    
    
    
    int huge myDevSend(DevUserIfaceHandle ifaceHandle,
    
                       unsigned char far * dataPtr,
    
                       int dataLength, int flag);
    
    
    
    int huge myDevReceive1(DevUserIfaceHandle ifaceHandle,
    
                           unsigned char far * far * dataPtr,
    
                           int far * dataLength,
    
                           DevUserBufferHandle bufferHandle);
    
    
    
    int huge myDevGetPhysAddr(DevUserIfaceHandle ifaceHandle,
    
                              unsigned char far * physicalAddress);
    
    
    
    void interrupt MyDeviceISRHandler(void);
    
    
    
    void huge MyReceiveTask(void);
    
    
    
    unsigned int       recvID;              // task ID
    
    unsigned int       myrecv_stack[1024];  // stack for receiver task
    
    unsigned char      install_done = 0;    // waiting flag  for receiver task
    
    unsigned int       errorCode;
    
    DevUserIfaceHandle MyDevHandle;
    
    
    
    TaskDefBlock  myrecv_defblock =
    
    {
    
        MyReceiveTask,                 // task function
    
        {'D','E','V',' '},             // a name: 4 chars
    
        &myrecv_stack[1024],           // top of stack
    
        1024*sizeof(int),              // size of stack
    
        0,                             // attributes, not supported
    
        20,                            // priority 20(high) ... 127(low)
    
        0,                             // no time slicing
    
        0,0,0,0                        // mailbox depth,
    
    };
    
    
    
    char far * mydevicename  = "MyDev";
    
    char far * IPString      = "192.168.200.020";
    
    char far * NetmaskString = "255.255.255.000";
    
    
    
    DevUserDriver mydevdriver;
    
    
    
    int main(void)
    
    {
    
        //***********************************************************************
    
        // Initialize struct mydevdriver;
    
        //***********************************************************************
    
        mydevdriver.DevName = mydevicename;             // Unique device name,
    
        //  max. 13 chars + 0.
    
    
    
        inet_addr(IPString     , &mydevdriver.IpAddr);  // IP address
    
        inet_addr(NetmaskString, &mydevdriver.Netmask); // Netmask
    
    
    
        mydevdriver.iface_type = 1;                     // Ethernet device
    
        mydevdriver.use_dhcp  =  0;                     // no DHCP
    
    
    
        //Important:
    
        // At the first DEV_OPEN_IFACE call for a device, IfaceHandle must be NULL.
    
        mydevdriver.IfaceHandle = 0 ;
    
    
    
        // Note: If the interface should be restarted by calling DEV_CLOSE_IFACE
    
        //  and DEV_OPEN_IFACE (e.g. for changing IP configuration) the
    
        //  IfaceHandle must contain at DEV_OPEN_IFACE the valid IfaceHandle handle
    
        //  from the first DEV_OPEN_IFACE call.
    
    
    
        // Install your driver functions
    
        mydevdriver.DevOpen        = (void far *)mydevOpen;
    
        mydevdriver.DevOpen        = (void far *)mydevClose;
    
        mydevdriver.DevSend        = (void far *)mydevSend;
    
        mydevdriver.DevRecv        = (void far *)myDevReceive1;
    
        mydevdriver.DevFreeRecv    = (void far *)0;  // Since using TCP/IP buffers
    
        mydevdriver.DevGetPhysAddr = (void far *)myDevGetPhysAddr;
    
        mydevdriver.DevIoctl       = (void far *)0;  // Currently not supported,
    
                                                     //   pass a Null pointer.
    
    
    
        //***********************************************************************
    
        // Install the device driver interface
    
        //***********************************************************************
    
        result = Dev_Open_Interface(&mydevdriver, &errorCode);  // C-Lib
    
    
    
        // if(result).....
    
    
    
        //***********************************************************************
    
        // Create the receiver task
    
        //***********************************************************************
    
        result = RTX_Create_Task(&recvID, &myrecv_defblock);   // C-Lib
    
    
    
        // if(result).....
    
    
    
        // Optional, but recommended: Change priority of receiver task to high prio 4
    
        RTX_Change_TaskPrio(recvID, 4, &errorCode);
    
    
    
        //***********************************************************************
    
        //If device interface should be configured by DHCP, wait for completion
    
        //of the DHCP configuration process
    
        //***********************************************************************
    
        if (mydevdriver.use_dhcp == 1)
    
        {
    
            result= Dev_Wait_DHCP_Complete(&mydevdriver, 20, &errorCode); // C-Lib
    
            // if(result)....
    
        }
    
        //  Wait forever, or stay resident with int21h call 31h.
    
        //  It is possible to close and restart the interface inside of an application.
    
        //  But due to the internal architecture of the TCP/IP stack, it is not
    
        //  possible to exit the driver program and restart the interface with the
    
        //  same unique name.
    
        //  Your driver program should run forever.  Avoid killing the receiver task.
    
    
    
        while(1) RTX_Sleep_Time(100);
    
    
    
    }// End of main(void)

    Related Topics

    API_INETADDR API - inet_addr's implementation
    DEV_OPEN_IFACE API - Dev_Open_Interface's implementation
    DEV_WAIT_DHCP_COMPLETE API - Dev_Wait_DHCP_Complete's implementation
    RTX_TASK_CREATE API - RTX_Create_Task's implementation
    RTX_SLEEP_TIME API - RTX_Sleep_Time's implementation
    DevUserDriver data structure type definition

    Top of list
    Index page


    End of document