www.beck-ipc.com

@CHIP-RTOS - User specific TCP/IP Device driver


    IPC@CHIP® Documentation Index

Introduction


This documentation page explains how to implement and install a user specific device driver/link layer to the TCP/IP stack.   All required TCP/IP related data types and constants are declared in the RTOS-CLIB TCP/IP header files.

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

    1. Ethernet controller
    2. PPP server
    3. PPP client
    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 TCP/IP device driver API functions 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 manner as 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 Beck C-library.   This C-library with source files is 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 BIOS_Set_Ini_String() API.  

2.   Setting the default gateway (reachable via the installed interface) is now possible with the expanded AddDefaultGatewayEx() 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 Dev_Open_Interface() API.   These are 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 IO Control 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 or Paradigm Beck IPC Edition 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_Interface() 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. 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_Send_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 )
     {
         // 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);  // C-Lib
         }
         return 0;
     }

    Related Topics

    Dev_Send_Complete() API
    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 using your DevRecv vector 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 is optional but recommended to store incoming data in a buffer from the TCP/IP pre-allocated memory pool (see TCPIPMEM).   The Dev_Get_Buffer() API 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_Interface() call.

    Two generic examples for receiver callback 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
        // This API call also sets the bufferHandle for internal use by TCP/IP stack.
        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_Buffer() API
    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 IO Control function

    This function allows to react on IO control requests of the TCP IP stack. The IO Control function will be called when the IP configuration of the specific interface was modified.

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

    Generic example:

                
    int huge myDevIoctl(DevUserIfaceHandle ifaceHandle,
                        int        flag,
                        void far * optionPtr,
                        int        optionLen )
    {
         char ipStr[50];
         char nameStr[20];
    
         if (flag == DEV_IOCTL_IPCFG_NOTIFY)
         {
           // do update ip address in the CHIP.INI file
           DevIpv4IfaceCfg   *pDevIpv4IfaceCfg = optionPtr;
           if (pDevIpv4IfaceCfg->IpAddr)
           {
              // setup ip address
              InetToAscii(&pDevIpv4IfaceCfg->IpAddr, ipStr);
              snprintf(nameStr, sizeof(nameStr), "ADDRESS%01u", pDevIpv4IfaceCfg->multiHomeIndex);
              BIOS_Set_Ini_String("IP_MYITF", nameStr, ipStr );
    
              // setup netmask
              InetToAscii(&pDevIpv4IfaceCfg->Netmask, ipStr);
              snprintf(nameStr, sizeof(nameStr), "NETMASK%01u", pDevIpv4IfaceCfg->multiHomeIndex);
              BIOS_Set_Ini_String("IP_MYITF", nameStr, ipStr );
    
              // disable DHCP, when mulit home index 0 becomes static
              if (pDevIpv4IfaceCfg->multiHomeIndex == 0)
              {
                BIOS_Set_Ini_String("IP_MYITF", "DHCP", "0" );
              }
           }
           else
           {
              // DHCP is allowed for multi home index 0 only
              if (pDevIpv4IfaceCfg->multiHomeIndex != 0) return 0;
    
              // enable DHCP
              BIOS_Set_Ini_String("IP_MYITF", "DHCP", "1" );
           }
         }
    
         if (flag == DEV_IOCTL_IPV6CFG_NOTIFY)
         {
            DevIpv6IfaceCfg   *pDevIpv6IfaceCfg = optionPtr;
    
            // do update IPv6 address (analog to procedure above)
            // ...
         }
    
         return 0;
    }

    Related Topics

    DevIpv4IfaceCfg type definition
    DevIpv6IfaceCfg 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_Interface() (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 RTX type ISR with the hal_install_rtx_isr() 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_Recv_Wait() API
    Dev_Recv_Interface() API
    RTX_Sleep_Time() API

    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 myDevReceive(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 = DEV_ETHERNET;          // 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.DevClose       = (void far *)mydevClose;
        mydevdriver.DevSend        = (void far *)mydevSend;
        mydevdriver.DevRecv        = (void far *)myDevReceive;
        mydevdriver.DevFreeRecv    = (void far *)0;  // Since using TCP/IP buffers
        mydevdriver.DevGetPhysAddr = (void far *)myDevGetPhysAddr;
        mydevdriver.DevIoctl       = (void far *)myDevIoctl;
    
        //***********************************************************************
        // 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 receiver task to a high priority.
        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

    inet_addr() API
    Dev_Open_Interface() API
    Dev_Wait_DHCP_Complete() API
    RTX_Create_Task() API
    RTX_Sleep_Time() API
    DevUserDriver data structure type definition

    Supported since or modified in @CHIP-RTOS version

      SC12SC13SC11SC1x3SC2x
      V1.10V1.00V1.00V0.90V1.00

    Top of list
    Index page


    End of document