Adding a Device Driver

Jump to: navigation, search
<< Using Ethernet or PPP
Testing the Device Driver >>



This step is the most complicated because the device driver interface is so versatile. The device driver interface is the same for all link layers (including PPP).


Note Note: You will find existing device drivers in the drivers directory. If we have provided a device driver, chances are that you will still need to modify it for your hardware platform.

Device drivers can almost never be moved between different hardware platform types without modification.


To allow you to hook up your driver in many different ways, we provide these additional functions that you would call from your driver and task level to allow for a receive task, transmit task, and send complete task or to poll the device driver. The use of these functions is optional (depending on your environment):



Contents

tfRecvInterface()

tfRecvInterface() is used to move received data into the stack, and tfSendCompleteInterface() is used to notify the stack that data has been sent. The use of these functions is required:


tfXmitInterface() and tfInterfaceSpinLock()

tfXmitInterface() and tfInterfaceSpinLock() are used only if the user uses a separate transmit task to send packets in the context of the transmit task, and to let the separate transmit task yield the CPU from the device driver send function while waiting for the device to be ready to transmit.


tfInterfaceSetOptions()

tfInterfaceSetOptions() is used by the user to set interface specific options, such as turning on a transmit task for that interface, or making tfRecvInterface() copy device driver buffers whose size are below a specified option threshold.


tfUseInterfaceXmitQueue() and tfIoctlInterface()

tfUseInterfaceXmitQueue() and tfIoctlInterface() are called by the user when he wishes to use a transmit queue to queue the buffers to a Treck device driver transmit buffer queue, as opposed to using the overhead of a transmit task. Note that the 2 methods are exclusive. You need to decide whether you want to use a transmit queue, or a transmit task, or none of the above. In addition, you cannot use a device driver transmit queue for a serial device interface (i.e. with SLIP, or PPP).


Summary of functions that you may have to write in your device driver:

  • driverOpen (Optional)
  • driverClose (Optional)
  • driverIoctl (Optional)
  • driverGetPhysicalAddress (Only for Ethernet)
  • driverSend
  • driverReceive
  • driverFreeReceiveBuffer (Optional)
  • driverIsrHandler (Optional)


For Ethernet device drivers, The Treck stack provides the following additional optional functions:

For all device drivers, the Treck stack provides the following additional functions:

For platforms that support data cache, Treck provides some helper functions for flushing and invalidating the data buffers. Please see the Data Cache Management section for more details on the following APIs.


Some Ethernet chips (for example the Crystal LAN (cs8900)) won't dismiss a receive interrupt, until the data is copied. If this is the case, you will need to get a buffer from within the ISR, to copy the data into.

The Treck stack does provide a set of tools to allow you to get a Treck buffer from within the ISR:


Note Note: Calling tfPoolIsrGetBuffer() effectively removes a Treck buffer from the available pool of buffers. A call to tfRecvInterface() will return that buffer to the pool, but only if the flag TM_POOL_REFILL_IN_LINE was specified in the call to tfPoolCreate() when allocating the pool. Otherwise, all of the Treck pool buffers will get used up, and your device driver won't be able to receive any more packets. You can also refill the Treck buffer pool by periodically calling tfIoctlInterface() and specifying the flag TM_DEV_IOCTL_REFILL_POOL_FLAG.

It is recommended that when using the Treck buffer pool, you use both methods to refill the pool.


Note Note: You will only be using a subset of the functions listed on this page. You will find that the hardest part about the device driver having to read and interpret the device data sheet. A key thing to remember here is that it is easier than it looks. Just take it one step at a time, and before you know it you will have your device driver up and running!

tfNotifyInterfaceIsr(), tfNotifyInterfaceTask(), tfCheckReceiveInterface(), tfWaitReceiveInterface(), and tfRecvInterface() Functions

This group of functions is used to allow the user to notify the stack that a receive complete event has occurred, so that the event can be processed at main/task level instead of from the ISR. In other words, the first four functions are used to tell the user when to call tfRecvInterface(). tfRecvInterface() may NOT be called directly from an interrupt handler. The tfNotifyInterfaceIsr() function is used to notify the stack from an ISR that there is data waiting to be received. The tfNotifyInterfaceTask() function is used to notify the stack from a task that there is data waiting to be received. Usually, only tfNotifyInterfaceIsr() is needed. On some occasions, there is a need to let the stack know that there is more data to be received in the context of a task (instead of an ISR), and this is the reason tfNotifyInterfaceTask() is provided. The tfCheckReceiveInterface() function is used to poll the device layer for receive events. The tfWaitReceiveInterface() function is used to "block" a task until the event has occurred. You must choose if you want a task to block until data has been received or poll to see if data has been received. The choice that you make defines which of the functions (tfWaitReceiveInterface() or tfCheckReceiveInterface()) that you will use.


Warning Warning: tfNotifyInterfaceIsr() (and/or tfNotifyInterfaceTask()) need to be used whether the user chooses to use tfCheckReceiveInterface() (polling method), or tfWaitReceiveInterface() (pending method). If you do not have an RTOS you cannot use tfWaitReceiveInterface(). If you do have an RTOS, you can use tfWaitReceiveInterface(), but only if you define the TM_TASK_RECV macro in <trsystem.h>.


Warning Warning: Our device driver interface does NOT allow you to process packets directly from an ISR. The major reason that we do not allow this is because our stack is designed for real-time systems. Under real-time constraints, we need to keep interrupt latency to a minimum.


It is not required to use the functions tfNotifyInterfaceIsr(), (or/and tfNotifyInterfaceTask()), tfCheckReceiveInterface() and tfWaitReceiveInterface() as long as you can guarantee that the tfRecvInterface() call is made when there are packets to be received from the device driver. Remember, tfRecvInterface() may not be called from an interrupt handler.

An example of using these calls for receive processing from a separate task is as follows:

void recvTask(void)
{
    while(1)
    {
/* Wait for the receive event from an ISR */
        tfWaitReceiveInterface(myInterfaceHandle);
/* Move the data into the stack */
        tfRecvInterface(myInterfaceHandle);
    }
}

An example of using these calls for receive processing from a main line loop is as follows:

void main(void)
{
    ttUserInterface interfaceHandle;
    int             errorCode;
 
    errorCode = tfStartTreck();
    treckStarted = 1;
/* Other main processing, like adding, and opening an interface */
                     .
                     .
                     .
    for (;;)
    {
/* Check if Treck timers have expired */
        tfTimerExecute();
/* Check for received packets */
        if (tfCheckReceiveInterface(myInterfaceHandle) == TM_ENOERROR)
        {
/* 
 * Call the stack to move the data from the driver
 * and process it
 */
            tfRecvInterface(myInterfaceHandle);
        }
/* Application code */
    }
}

An example ISR handler for both of the methods would look something like this:

deviceIsrHandler(void)
{
    int receivedPacketCount;
/*
 * Store number of packets ready to be received in
 * receivedPacketCount.
 */
/* 
 * Notify the stack that data is waiting to be
 * processed
 */
    tfNotifyInterfaceIsr(myInterfaceHandle,
                         receivedPacketCount, 0, 0, 0UL);
}


tfRecvInterface() and tfInterfaceSetOptions() functions

The Treck tfRecvInterface() function will process incoming data. If incoming data is a PING echo request, this function will generate, and send the reply in the context of the receive task. If incoming data is for a user application socket, then the tfRecvInterface() function will process the incoming data, and then will queue the buffer (containing the user application data) in the application socket receive queue. The problem is that on a non-serial device, the device driver receive buffers will be tied up in the socket receive queue, until the application actually recv the data. Typically a device driver will have pre-allocated maximum size Ethernet packet, and if there is little data in each Ethernet frame, this could end up consuming a lot of memory. To fix this, two techniques are used:

  1. Copy at the socket receive queue level
  2. Copy inside tfRecvInterface before processing the data (disallowed for PPP or SLIP interfaces)


Copy at the socket receive queue level

If there is no data in the application socket receive queue, the device driver buffer will be queued. If there is a buffer already queued in the application socket receive queue, by default the Treck stack will attempt to copy to a new buffer (for UDP), or to append to the previous buffer (for TCP), and free the device driver buffer. By default the Treck stack will attempt to do so only if the ratio of the allocated block size to the buffer size is at or above 4 (i.e if the data uses less than, or 25 % of the allocated block). However, if the device driver recv function gives back a driver buffer, which is not a Treck buffer, then the Treck stack won't know the block allocation size. In that case, the Treck stack will assume that the block allocation size is the same as the data size, and no copy will take place, unless the user changes the default ratio to 1. The user can change this ratio, using the per socket setsockopt() function. For example to change the ratio to 2.

int optionValue;
optionValue = 2;
errorCode = setsockopt(socketDescriptor, SOL_SOCKET, TM_SO_RCVCOPY,
                       &optionValue, sizeof(int));


Copy inside tfRecvInterface before processing the data (disallowed for PPP or SLIP interfaces)

The user can set an interface option, so that the device driver buffer can get copied into a new buffer, if the device driver buffer data size is below a configurable threshold. To set that option to 64 bytes for example:

short optionValue;
optionValue = 64; /* Copy only if size is below or at 64 bytes */
errorCode = tfInterfaceSetOptions(myInterfaceHandle,
                                  TM_DEV_OPTIONS_RECV_COPY,
                                  &optionValue,
                                  sizeof(short));


Warning Warning: This option is not allowed on a PPP, or SLIP interface, since PPP, or SLIP will copy the buffer into a new buffer while processing the incoming data. So there is no need to copy the data an extra time.


tfUseInterfaceXmitQueue() and tfIoctlInterface() Functions (only for non serial devices)

If you do not want the overhead of a transmit task to send data to the device driver, then you can use a Treck device transmit queue interface. This method allows the driver to return an error, in case the chip is not ready to transmit. In that case a pointer to the buffer that could not be transmitted (along with its length, and flag) is stored in an empty slot, in the Treck device transmit queue. The device transmit queue should be big enough to hold pointers to all the buffers that will be sent by the application. The size of the data sent by an application is limited by the socket send queue size. So, an interface transmit queue should be big enough to hold pointers to buffers sent from all the application sockets through that particular interface. The space allocation overhead for a x entries device transmit queue is 12 + x * 8 bytes. So for a device transmit queue containing 1000 slots, the overhead is 8012 bytes.


What happens if the interface transmit queue is not big enough?

If the device transmit queue is not big enough to hold all the buffer pointers that the device driver could not send, the Treck stack will drop the packets corresponding to the buffers that could not be queued. If the user uses the device driver scattered send capability, some Ethernet frames could therefore be partially sent. In that case, the Treck stack will ensure that at least a minimum Ethernet size packet will be sent.


Re-transmission of buffers that have been queued to the device transmit queue.

If a new buffer is being sent, and the device transmit queue is not empty, the Treck stack will first try and empty the device transmit queue. If it fails to empty it completely, then it will try to queue the current buffer to the device transmit queue, storing the buffer pointer, data length and flag in the next available slot at the end of the Treck device transmit queue. Also when tfSendCompleteInterface() is called, the Treck stack will try and empty the device transmit queue, but only if tfSendCompleteInterface() is not called from the device driver send function to avoid recursion. Also the user should call tfIoctlInterface periodically with the TM_DEV_IOCTL_EMPTY_XMIT_FLAG flag to try and flush the Treck device transmit queue.


Note Note: Using a Treck device transmit queue is not allowed for a SLIP or PPP serial device interface, because the SLIP or PPP link layer functions send data to the device driver in a unique per interface buffer. Since the same per interface buffer is being re-used, a pointer to it cannot be kept in the device transmit queue.


Example on using a device transmit queue of a 1000 entries for a given interface:

void main(void)
{
    ttUserInterface interfaceHandle;
    int             errorCode;
    short           optionValue;
 
    errorCode = tfStartTreck();
    treckStarted = 1;
/* Other main processing, like adding an interface */
                     .
                     .
    if (errorCode == TM_ENOERROR)
    {
        errorCode = tfUseInterfaceXmitQueue(interfaceHandle, 1000);
    }
/* Other main processing, like opening the interface */
                     .
                     .
}

Also every time tfTimerExecute() is called, then the tfIoctlInterface() function should be called as follows:

errorCode = tfIoctlInterface(interfaceHandle, 
                             TM_DEV_IOCTL_EMPTY_XMIT_FLAG,
                             (void TM_FAR *)0, 0);


Advantages Disadvantages
Transmit Task Calling the device driver send is done in the context of a separate transmit task, not in the context of the user application, the recv task or the timer task. The transmit task can wait inside the device driver send function for the device to be ready to transmit and can call tfInterfaceSpinLock() allowing other tasks to access the other device driver functions. For example, the recv task could access the driver recv function. In blocking mode, extra context switch on every packet sent.

In polling mode, extra processing to check on packets ready to be sent.

Transmit Queue The device driver send can return an error if it is not ready to transmit the current buffer. Hence, the sending thread (i.e. the user application task, recv task, or timer task) does not have to wait inside the device driver send function for the device to be ready to transmit. Extra memory required.

One extra function call required in the send path. Cannot be used with SLIP or PPP interface.

No Transmit Task

No Transmit Queue

No overhead. The sending thread (i.e. the user application task, recv task, or timer task) has to wait in a busy loop inside the device driver send function for the device to be ready to transmit. This is minimal with an Ethernet device.

While the sending thread is waiting, no other task can access any other device driver function. In particular, the recv task could not access the driver recv function.


The tfNotifyInterfaceIsr(), tfCheckSentInterface(), tfWaitSentInterface(), and tfSendCompleteInterface() Functions

This group of functions is used to allow the user to notify the stack that a send complete event has occurred, so that the event can be processed at main/task level instead of from the ISR. In other words, the first three functions are used to tell the user when to call tfSendCompleteInterface(). tfSendCompleteInterface() may not be called directly from an interrupt handler. The tfNotifyInterfaceIsr() function is used to notify the stack from an ISR that the data buffer is not in use by the device driver anymore.

The tfCheckSentInterface() function is used to poll the device layer for "send complete" notify events. The tfWaitSentInterface() function is used to "block" a task until the send complete event has been notified. You must choose if you want a task to block until data has been sent or poll to see if data has been sent. The choice that you make defines which of the functions (tfCheckSentInterface() or tfWaitSentInterface()) that you will use.


Warning Warning: tfNotifyInterfaceIsr() needs to be used whether the user chooses to use tfCheckSentInterface() (polling method), or tfWaitSentInterface() (pending method). If you do not have an RTOS you cannot use tfWaitSentInterface(). If you do have an RTOS, you can use tfWaitSentInterface(), but only if you define the TM_TASK_SEND macro in <trsystem.h>.


Note Note: It is not required to use the functions tfNotifyInterfaceIsr(), tfCheckSentInterface() and tfWaitSentInterface() as long as you can guarantee that the tfSendCompleteInterface() call is made when the device driver has sent the data. Remember that tfSendCompleteInterface() may not be called from an interrupt handler.


Now let's look at send complete processing. Send complete means that the sending frame is not in use by the driver anymore. Here is send complete processing from a separate task:

void sendCompleteTask(void)
{
    while(1)
    {
/* Wait for the send complete event from an ISR */
        tfWaitSentInterface (myInterfaceHandle);
/* 
 * Tell the stack that the driver is done with the
 * current frame
 */
        tfSendCompleteInterface(myInterfaceHandle,
                                TM_DEV_SEND_COMPLETE_APP);
    }
}

An example of using these calls for send complete processing from a main line loop is as follows:

void main(void)
{
/* 
 * Other main processing, like initialization, adding, opening an
 * interface.
 */
    for (;;)
    {
/* Other main processing, like timer, recv */
                   .
/* Check for sent packets */
        if (tfCheckSentInterface(myInterfaceHandle) == TM_ENOERROR)
        {
/* 
 * Call the stack to tell it that it owns the frame
 * now
 */
            tfSendCompleteInterface(myInterfaceHandle,
                                    TM_DEV_SEND_COMPLETE_APP);
        }
    }
}

An example ISR handler for both of the methods would look something like this:

void deviceIsrHandler(void)
{
    int           receivedPacketCount;
    int           sendCompletePacketCount
    unsigned long totalBytesSent;
 
/*
 * Store number of packets ready to by received in
 * receivedPacketCount.
 * Store number of send complete packets in sendCompletePacketCount
 * and store total number of bytes sent in totalBytesSent.
 * Notify the stack that data is waiting to be
 * processed, and that the driver has transmitted
 * some complete frames.
 */
    tfNotifyInterfaceIsr(myInterfaceHandle,
                         receivedPacketCount,
                         sendCompletePacketCount,
                         totalBytesSent,
                         0UL);
}


Note Note: Our experience tells us that it is usually best NOT to use a send complete task that is the highest priority. The reason is that a send complete task will cause a context switch on every sent packet. Let's look at a couple of methods to avoid using the send complete task.


Methods to avoid using a send complete task.

When one of the two methods outlined here is used, then tfNotifyInterfaceIsr(), tfCheckSentInterface(), or tfWaitSentInterface() need not be called.

  1. Copy the Data
  2. In-line Send Complete Call


Copy the Data

This method is less efficient than the extra context switch on large packets. For drivers that use I/O ports to communicate with the chip, the data is already being copied to the data to the I/O port inside the device driver send function anyways. An example of this method is as follows:

int driverSend(ttUserInterface interfaceHandle,
               char TM_FAR    *dataPtr,
               int             dataLength,
               int             flag)
{
    while (dataLength)
    {
/* Send to I/O Port */
        outb(*dataPtr++);
        dataLength-;
    }
/* We are done with the data pointer */
    if (flag==TM_USER_BUFFER_LAST)
    {
        tfSendCompleteInterface(interfaceHandle,
                                TM_DEV_SEND_COMPLETE_DRIVER);
    }
    return TM_DEV_OKAY;
}


In-line Send Complete Call

In this method, in your device driver send function, look for previous sent packets that have completed transmission, and call send complete for those packets. This method is a little trickier. One must be careful when using this method to make sure to also look for sent packets periodically (outside of the driver send call), in the driver ioctl function. Otherwise, if the Treck stack creates packets faster than the chip can process them, then a deadlock condition can occur, where the send complete never gets called. The trquicc.c driver contains this method.


Device Driver Functions that You May Need to Provide

In this section, we will describe and provide examples for the functions that you may need to provide. In all of these functions, you can be guaranteed of single threaded access to them (provided that you do not call any of these functions directly). We use the internal Treck locking system to provide this facility. Because of this, you should not need any critical sections in the device driver code, except to protect data area that you set, or access in the ISR. Success in all of these calls is returned to the stack via the macro TM_DEV_OKAY while failure is indicated by TM_DEV_ERROR.

Function Prototype Link to function description
ttDevOpenCloseFunc deviceOpen
ttDevOpenCloseFunc deviceClose
ttDevIoctlFunc driverIoctl
ttDevGetPhysAddrFunc driverGetPhysicalAddress
ttDevSendFunc driverSend
ttDevRecvFunc driverReceive
ttDevFreeRecvFunc driverFreeReceiveBuffer
device specific driverIsrHandler


deviceOpen

The stack calls this optional routine to initialize the hardware (and optionally install the ISR handler. If your driver requires pre-allocated buffers to be given to the chip, you could pre-allocate your buffers in this function. If your hardware initializes correctly, then you should return a success value (TM_DEV_OKAY), otherwise you should return TM_DEV_ERROR. This routine is optional if you perform your hardware initialization elsewhere.

Example:

int myDeviceOpen(ttUserInterface interfaceHandle)
{
/* Initialize the Hardware */
                        .
                        .
                        .
/* Install the ISR Handler Routine */
    tfKernelInstallIsrHandler(myHandlerFunctionPtr,
                              myHandlerIsrLocation);
    return (TM_DEV_OKAY);
}

In addition, some chips (for example the Crystal LAN (cs8900)) won't dismiss a receive interrupt, until the data is copied. If this is the case, you must get a buffer from within the ISR. The Treck stack provides a set of tools that allow you to get a Treck buffer from within the ISR. If you want to use these Treck tools, you must to call tfPoolCreate() in the deviceOpen function. For example: we pre-allocate a pool of ten 128-bytes small buffers, and we pre-allocate a pool of five 1518-byte big buffers. The TM_POOL_REFILL_IN_LINE flag, indicates that we want the buffers to be re-allocated in the receive task thread.

#define TM_ID_RECV_BIG_BUFFERS      5
#define TM_ID_RECV_SMALL_BUFFERS    10
#define TM_ID_SMALL_BUFFER_SIZE     128
 
int myDeviceOpen(ttUserInterface interfaceHandle)
{
    int errorCode;
 
/* Initialize the Hardware */
                        .
                        .
                        .
/* Install the ISR Handler Routine */
    tfKernelInstallIsrHandler(myHandlerFunctionPtr,
                              myHandlerIsrLocation);
/* Example where we pre-allocate a pool of 10 128-bytes 
 * small buffers, and 5 maximum Ethernet size big buffers.
 * The TM_POOL_REFILL_IN_LINE flag, indicates that we want
 * the buffers to be re-allocated in the receive task thread.
 */
    errorCode = tfPoolCreate(interfaceHandle,
                             TM_ID_RECV_BIG_BUFFERS,
                             TM_ID_RECV_SMALL_BUFFERS,
                             TM_ETHER_MAX_PACKET_CRC,
                             TM_ID_SMALL_BUFFER_SIZE,
                             0,
                             TM_POOL_REFILL_IN_LINE);
    return errorCode;
}


deviceClose

This optional routine is called by the stack, to turn off Transmit and Receive. It can also be used to remove the ISR. Most people do not need a close for ethernet since the device typically stays connected. It is very useful for PPP devices.

Example:

int myDeviceClose(ttUserInterface interfaceHandle)
{
/* Turn off Ethernet reception, Disable Ethernet transmission, 
 * Uninstall the ISR handler.
 */
    return (TM_DEV_OKAY);
}

If you had called tfPoolCreate() in your deviceOpen function, then you will need to call tfPoolDelete() in your deviceClose function.

Example:

int myDeviceClose(ttUserInterface interfaceHandle)
{
    int errorCode;
 
/* Turn off Ethernet reception, Disable Ethernet transmission, 
 * Uninstall the ISR handler.
 */
 
    errorCode = tfPoolDelete(interfaceHandle);
    return errorCode;
}


driverIoctl

This optional routine is used as a pass through routine in the stack in most cases. It is normally only called when the user calls tfIoctlInterface(). The flags that are passed to tfIoctlInterface() are the same as those passed to driverIoctl. The only exception to this rule is when multicast support is needed. In this case, the Treck stack calls this function with Treck reserved flags: TM_DEV_SET_MCAST_LIST to give the list of multicast addresses the Ethernet chip should receive, or TM_DEV_SET_ALL_MCAST so that the Ethernet chip receives all multicast addresses. Normally this routine is used to periodically refresh the receive pool and perform send completes.

See a detailed API specification at drvIoctlFunc().


Note Note: Flag values bigger than or equal to 0x1000 are reserved by the Treck stack.

Example:

int myDeviceIoctl(ttUserInterface interfaceHandle,
                  int             flag,
                  void    TM_FAR *optionPtr,
                  int             optionLen)
{
    int errorCode;
    switch (flag)
    {
        case REFILL_RECEIVE:
            myDriverReceiveRefill();
            errorCode=TM_DEV_OKAY;
            break;
        default:
            errorCode=TM_DEV_ERROR;
            break;
    }
    return (errorCode);
}

If you are using the Treck stack ISR pool functions, i.e., if you had called tfPoolCreate() in your deviceOpen function, then you need to call the following function periodically:

errorCode = tfIoctlInterface(interfaceHandle,
                             TM_DEV_IOCTL_REFILL_POOL_FLAG,
                             (void TM_FAR *)0,
                             0);                                                           
Note Note: If you are using the Treck stack ISR pool functions tfIoctlInterface() will not call your device driver ioctl function. The pool refill will be done internally by the Treck stack.

driverGetPhysicalAddress

This routine is used to return the physical address of the device to the stack. Currently it is only called for Ethernet devices. The protocol stack needs the physical address to formulate an Ethernet frame.

Example:

int myDeviceGetPhyAddr(ttUserInterface interfaceHandle, 
                       char  TM_FAR *  physicalAddress)
{
/*
 * Get the physical address from hardware or firmware
 * Save it in Network Byte Order
 */
    tfMemCpy(physicalAddress, deviceAddress, TM_ETHERNET_PHY_ADDR_LEN);
    return(TM_DEV_OKAY);
}


driverSend

This routine is used to send the data out the network device. Since Treck protocols support "scatter send" for devices, there is a flag that gets passed into the device driver send routine. There are two possible values for this flag:

  1. TM_USER_BUFFER_MORE: More data to follow for this frame.
  2. TM_USER_BUFFER_LAST: This is the last piece of this frame.

If your device does not support "scatter send", then the flag will always be set to TM_USER_BUFFER_LAST with the exception of PPP or Slip serial devices.


Ethernet devices

The Treck Ethernet, or Treck Null link layer code, will only send scattered data if the device supports "scatter send". If the device supports "scatter send", the only Ethernet scattered data frames sent by the Treck stack are TCP packets. An Ethernet device driver does not need to copy the Treck data, and can keep a pointer to it. The data will not be freed until tfSendCompleteInterface() is called later on by the user, when the user knows that the packet has been transmitted.


tfSendCompleteInterface() Notes

tfSendCompleteInterface() may be called only once per complete frame which is denoted by the TM_USER_BUFFER_LAST flag. Please note that this function will operate on the frames in the order that they were delivered to your driver send routine. If your device driver send function returns an error for a given buffer, which has the TM_USER_BUFFER_LAST flag set, then you do not need to call tfSendCompleteInterface() for that buffer. In that case, if a Treck device transmit queue is used, the Treck stack will try and queue the buffer to the device transmit queue, and try to send it later. If it fails to queue the buffer to the device transmit queue, or if there is no device transmit queue, then the Treck stack will remove the corresponding packet from the send queue, and free it.


PPP or SLIP serial devices

The Treck PPP link layer code, and Treck SLIP link layer code will send scattered data to SLIP or PPP serial devices. This is because of the PPP asynchronous byte stuffing or because of SLIP escaping special characters. The Treck PPP link layer code, and Treck SLIP link layer code will copy the stuffed bytes, or escaped bytes, along with the packet bytes, into a single intermediate buffer. That single intermediate buffer will be repeatedly sent to the driver when it is full, or when the end of the packet has been reached. By default, the intermediate buffer size is one byte. Note that the Treck PPP link layer code, or Treck SLIP link layer code, will re-use the same intermediate buffer, so in a serial device driver send, you need to copy the buffer data immediately. If your serial device driver can handle more than one byte at a time, you can change the size of the intermediate buffer being sent, with tfPppSetOption() for a PPP link layer, or tfSlipSetOptions() for a SLIP link layer. You can change the size of the SLIP intermediate buffer at any time, and the change will take effect immediately. But you need to change the size of the PPP intermediate buffer before opening the interface.

For example to change the intermediate SLIP send buffer size to 1500, you can call:

unsigned short optionValue;
optionValue = 1500;
errorCode = tfSlipSetOptions(interfaceHandle,
                             TM_SLIP_OPT_SEND_BUF_SIZE,
                             (void TM_FAR *)&optionValue,
                             sizeof(unsigned short));

To change the intermediate PPP send buffer size to 1500 for example, you need to call (before calling tfOpenInterface()):

unsigned short optionValue;
optionValue = 1500;
errorCode = tfPppSetOption(interfaceHandle,
                           TM_PPP_PROTOCOL, 0
                           TM_PPP_SEND_BUFFER_SIZE,
                           (const char TM_FAR *)&optionValue,
                           sizeof(unsigned short));


Warning Warning: You must only call send completes for the piece of data that has the TM_USER_BUFFER_LAST flag set to guarantee that only ONE send complete per frame is issued!


Example:

int myDeviceSend(ttUserInterface interfaceHandle,
                   char TM_FAR  *dataPtr,
                   int           dataLength,
                   int           flag)
{
    while (dataLength)
    {
/* Send to I/O Port */
        outb(MY_DEVICE_PORT, *dataPtr++);
        dataLength-;
    }
 
/* We are done with the frame */
    if (flag == TM_USER_BUFFER_LAST)
    {
         tfSendCompleteInterface (interfaceHandle,           
                          TM_DEV_SEND_COMPLETE_DRIVER);
    }
    return TM_DEV_OKAY;
}


driverReceive

In this routine, a received packet is passed back into the protocol stack. The stack calls this routine to retrieve a frame from the device driver. You can use one of our buffers to store the data from the driver into, or you can use your own buffer.


Special Ethernet Considerations for the driverReceive routine:
Warning Warning: You MUST return an entire frame.

On RISC processors, you should be careful that the IP header will be on a four byte boundary. Since the Ethernet header is 14 bytes long, this implies that the start of the Ethernet buffer should be on a 2-bytes boundary, but not 4-byte boundary. If you use our function tfGetEthernetBuffer() you are guaranteed that this will be the case.

If you decide not to use tfGetEthernetBuffer() with an Ethernet device, you should (for optimum performance) make sure the Ethernet buffer is aligned on a two-byte (NOT FOUR BYTE) boundary.

Ethernet Header Offsets (When Long Word Aligned)

0 6 12 14
Destination Addr Source Addr Type IP Header

Notice that the IP Header does not start on a long word boundary. This would not work on most RISC processors. On other processors this may result in poor performance, however, processors such as the M68EN360 require that the received data appear on a long word boundary. This is the why we subtract 2 bytes from the pointer returned by tfGetEthernetBuffer() in our quicc driver.

Ethernet Header Offsets (When Short Word Aligned)

2 8 14 16
Destination Addr Source Addr Type IP Header

tfGetEthernetBuffer() does this short word alignment for you. If you do not use tfGetEthernetBuffer() and allocate your own buffers, then you should verify that they are short word aligned.

We will show examples of using your own buffer or using tfGetEthernetBuffer().

Special PPP Considerations:

For PPP you do not need to pass an entire frame back to the protocol stack. You simply pass as much or as little as you wish, as the PPP Link Layer will determine the framing automatically.


An Example of using tfGetEthernetBuffer() to create a buffer to store the incoming data into:
int myDeviceReceive(ttUserInterface       interfaceHandle,
                    char TM_FAR * TM_FAR *dataPtr,
                    int TM_FAR           *dataLength,
                    ttUserBufferPtr       bufHandlePtr)
{
    int errorCode;
/* Get a buffer to store the data into */
    *dataPtr=tfGetEthernetBuffer (bufHandlePtr);
    if (*dataPtr == (char TM_FAR *)0)
    {
/* No memory so return an error */
        errorCode=TM_DEV_ERROR;
    }
    else
    {
/* Copy from the device driver into the buffer */
        tfMemCpy (*dataPtr,deviceRecvDataPtr,
                 deviceDataLength);
/* Save the length */
        *dataLength=deviceDataLength;
/* Good return value */
        errorCode=TM_DEV_OKAY;
    }
    return(errorCode);
}


An Example of using the driver's buffer to receive a frame and passing it directly to the protocol stack:
Note Note: Note that the *bufHandlePtr is NULL. This is how the stack knows who created the buffer being received.
int myDeviceReceive(ttUserInterface       interfaceHandle,
                    char TM_FAR * TM_FAR *dataPtr,
                    int TM_FAR           *dataLength,
                    ttUserBufferPtr       bufHandlePtr)
{
/* Save the pointer to the beginning of the data */
    *dataPtr=deviceRecvDataPtr;
/* Save the length */
    *dataLength=deviceDataLength;
/* (IMPORTANT) NULL OUT THE BUFFER HANDLE */
    *bufHandlePtr=(ttUserBufferPtr)0;
    return(TM_DEV_OKAY);
}


Note Note: Typically the user calls tfGetEthernetBuffer(), or tfGetDriverBuffer() routine to pre-allocate the receive buffers to receive into. This way the driver (if it is capable), can store the data directly into a protocol stack buffer. This eliminates the extra call to the driver to free the receive buffer that the driver passed it. The trquicc.c driver contains this method.


Special case when the user uses the Treck pool to be able to get a Treck buffer, and to copy the received data to it within the receive ISR.

Recall that in that case, the user has called tfPoolCreate() in the device driver open function. The driver receive function is very simple. The driver receive function could either be tfPoolReceive() (i.e be the driver receive function pointer parameter in tfAddInterface()), or the driver receive function could call tfPoolReceive().

For example:

int myDeviceReceive(ttUserInterface       interfaceHandle,
                    char TM_FAR * TM_FAR *dataPtr,
                    int TM_FAR           *dataLength,
                    ttUserBufferPtr       bufHandlePtr)
{
    return tfPoolReceive(interfaceHandle,
                         dataPtr,
                         dataLength,
                         bufHandlePtr);
}


driverFreeReceiveBuffer

This function is used ONLY if you use your own buffer allocation for the received packets (i.e. do not use tfGetEthernetBuffer(), nor tfGetDriverBuffer(), nor the Treck stack recv ISR pool functions). Since you pass a buffer to the stack (the stack is zero copy), you will need to know when the buffer is not in use anymore. When this happens, we call your driverFreeReceiveBuffer function to let you free or reuse the buffer.

Example:

int myDeviceFreeReceiveBuffer(
                   ttUserInterface interfaceHandle,
                   char TM_FAR    *dataPtr)
{
/* Free the Data here */
    return(TM_DEV_OKAY);
}


driverIsrHandler

The interrupt service routine only need to call the tfNotifyInterfaceIsr() function, if you use the Check or Wait functions described earlier. Normally you need to dismiss the interrupt and notify the stack of the event that occurred. The only call that you can make from a device interrupt handler into the protocol stack is one tfNotifyInterfaceIsr() function.

Tip: you should call tfNotifyInterfaceIsr() only once per ISR, because some RTOS on some CPU's will re-enable interrupt when posting on an event, therefore causing our counter update in tfNotifyInterfaceIsr() to become non-re-entrant.

Example:

void myDeviceIsrHandler(void)
{
    int           recvPacketCount;
    int           sendCompletePacketCount;
    unsigned long totalBytesSent;
 
    recvPacketCount = 0;
    sendCompletePacketCount = 0;
    totalBytesSent = 0;
/* Check for receive interrupt */
    if (receivedData)
    {
/* Accumulate number of packets ready to be received */
        recvPacketCount++;
    }
/* Check for send complete interrupt */
    if (sendComplete)
    {
 /*Accumulate number of packets that have been transmitted */
        sendCompletePacketCount++;
/*Accumulate sent packet data sizes */
        totalBytesSent += packetDataSize;
    }
/* Call this function only once */
    tfNotifyInterfaceIsr(myInterfaceHandle,
                         receivedPacketCount,
                         sendCompletePacketCount,
                         totalBytesSent,
                         0UL);
 
/* Dismiss the interrupt */
}

Special case when the user uses the Treck pool to be able to get a Treck buffer, and to copy the received data to it within the receive ISR.

Recall that in that case, the user has called tfPoolCreate() in the device driver open function. The user calls tfPoolIsrGetBuffer() inside the ISR, to get a pre-allocated Treck buffer from the Treck pool, so that the incoming network data can be copied inside the ISR.

Example:

/* myInterfaceHandle initialized in deviceOpen */
static ttUserInterface myInterfaceHandle; 
 
void myDeviceIsrHandler(void)
{
    int           recvPacketCount;
    int           recvPacketLength;
    int           sendCompletePacketCount;
    unsigned long totalBytesSent;
    char TM_FAR * dataPtr;
 
    recvPacketCount = 0;
    sendCompletePacketCount = 0;
    totalBytesSent = 0;
/* Check for receive interrupt */
    if (receivedData)
    {
/* Retrieve the packet length into recvPacketLength */
/* Get a Treck buffer from the ISR */
 
        dataPtr = tfPoolIsrGetBuffer(myInterfaceHandle,
                                     recvPacketLength);
        if (dataPtr != (char TM_FAR *)0)
        {
/* Copy the data into the Treck buffer pointed to by dataPtr */
/* Accumulate number of packets ready to be received */
            recvPacketCount++;
 
        }
        else
        {
/* Copy the data into a scratch buffer */
        }
    }
/* Check for send complete interrupt */
    if (sendComplete)
    {
 /*Accumulate number of packets that have been transmitted */
        sendCompletePacketCount++;
/*Accumulate sent packet data sizes */
        totalBytesSent += packetDataSize;
    }
/* Call this function only once */
    tfNotifyInterfaceIsr(myInterfaceHandle,
                         receivedPacketCount,
                         sendCompletePacketCount,
                         totalBytesSent,
                         0UL);
 
/* Dismiss the interrupt */
}


Further Device Driver Modifications to allow a device driver to be shared by several Ethernet Interfaces

Modify your device driver as follows, to allow a device driver to be shared by several Ethernet interfaces

Device Driver API Where Used
int tfDeviceStorePointer(ttUserInterface interfaceHandle, ttvoidPtr deviceDriverPtr); Device driver open function
int tfDeviceClearPointer(ttUseInterface interfaceHandle); Device driver close function
ttVoidPtr tfDeviceGetPointer(ttUserInterface interfaceHandle); Any device driver function


Summary of Device Driver API's that are provided to allow a device driver to be shared by several Ethernet interfaces

Device driver open function

First, make sure that you move all device driver local variables to a structure. In the open function, allocate such a structure, and give the pointer to the Treck TCP/IP stack, so that it can be stored on the interface, i.e.,

errorCode = tfDeviceStorePointer(interfaceHandle, deviceDriverPointer );


Device driver close function

In the device driver close function, call tfDeviceClearPointer() to dissociate the device driver structure from the interface handle.

deviceDriverPointer = tfDeviceClearPointer(interfaceHandle);

Then, if the returned deviceDriverPointer is non-null, free the allocated structure pointed to by deviceDriverPointer.


Any device driver function

When the device driver needs access to local structure, tfDeviceGetPointer() should be called. Given an interface handle, tfDeviceGetPointer() will retrieve the pointer to the device driver structure:

deviceDriverPointer = tfDeviceGetPointer(interfaceHandle);

It will return a non-zero pointer on success.

Alternatively the following macro can be used to retrieve the pointer:

deviceDriverPointer = tm_device_get_pointer(interfaceHandle);


Device driver ISR Handler

The user should keep a global mapping between an interrupt vector, and corresponding interface handle so that the user can retrieve the corresponding device driver pointer, with the tfDeviceGetPointer() API.


Adding and Configuring Your New Device Driver

Now that you have your device driver code, you need to tell the protocol stack about it. There are two calls to inform the stack about your driver. These calls should be made after you have called tfStartTreck() and before any sockets calls. These two calls are comprised of an add (tfAddInterface()) and open (tfOpenInterface()).

The following example shows how to inform the protocol stack of the new device driver.

{
/* Location to store Link Layer Handle into */
    ttUserLinkLayer     ethernetLinkLayer;
 
/* Location to save Interface Handle into */
    ttUserInterface     myInterfaceHandle;
 
    ethernetLinkLayer = tfUseEthernet();
 
    myInterfaceHandle = tfAddInterface (
/* name of the device */
                   "MYDEVICE.001",
/* Link Layer to use */
                    ethernetLinkLayer,
/* Open Function */
                    myDeviceOpen,
/* Close Function */
                    myDeviceClose,
/* Send Function */
                    myDeviceSend,
/* Receive Function */
                    myDeviceReceive,
/* Free a Receive Buffer Function */
                    myDeviceFreeReceiveBuffer,
/* IOCTL Function */
                    myDeviceIoctl,
/* Get Physical Address Function */
                    myDeviceGetPhysicalAddress,
/* INT to store error (if one is returned */
                    &errorCode);
 
/* Now open/configure the device */
    errorCode = tfOpenInterface (
/* The handle from tfAddInterface */
                myInterfaceHandle,
/* Our IP Address */
                inet_addr("192.1.1.2"),
/* Out Netmask (Super or subnet) */
                inet_addr("255.255.255.0"),
/* Special Flags Enable Scatter Send */
                TM_DEV_SCATTER_SEND_ENB,
/* Max buffers per frame */
                5);
}


tfAddInterface() Notes

For any functions that you did not implement for your device driver (because they were not needed), you can pass a NULL pointer into tfAddInterface() instead of having a stub routine.


tfOpenInterface() Notes

tfConfigInterface() has been deprecated. Please use tfOpenInterface(). tfConfigInterface() will still function in your code, and may be used to configure additional IP addresses on the same interface (multi homing).


Scattered send

Note that the flag TM_DEV_SCATTER_SEND_ENB is used to inform the protocol stack that the device can support "Scatter Send". If we support scatter send, we have to tell the stack what is the maximum number of pieces that the driver can handle in scatter send mode. If you do not use Scatter Send, then you can pass a 0 flags value and set Max buffers per frame to be one (1).


What if I do not know my IP address / netmask, and want to retrieve them from the net?

You can use the Treck stack BOOTP or DHCP protocols.


Note Note: Please, refer to the BOOTP, or DHCP section in Appendix B: RTOS Notes, and to BOOTP or DHCP function reference calls in Appendix A: Configuration Notes.


What if I do not know my IP address / netmask, and want to retrieve them from the net, but do not wish to use the Treck BOOTP or DHCP protocols?

To be able to open an interface without setting an IP address in the routing table, call tfOpenInterface(), using the TM_DEV_IP_USER_BOOT flag as follows:

errorCode = tfOpenInterface(interfaceHandle,
                            0UL,
                            0UL,
                            TM_DEV_IP_USER_BOOT,
                            1);


Note Note: If you device driver support scatter send, you can OR that flag to the TM_DEV_IP_USER_BOOT flag and change the last parameter accordingly.

You can then open a socket, and try and send data through that interface, calling the function tfSendToInterface(). tfSendToInterface() is identical to sendto, but takes 2 extra arguments: the interfaceHandle as returned by tfAddInterface(), and a multi home index, 0 in our case:

toAddress.sin_addr.s_addr = 0xFFFFFFFFUL;
len = tfSendToInterface(desc, buf, 512, 0,
                        (struct sockaddr TM_FAR *)(&toAddress),
                        sizeof(struct sockaddr),
                        interfaceHandle, 0);

You can also use the regular recvfrom on that socket. Once you have retrieved your IP address and netmask from the net, you can insert those values in the device and routing table using the tfFinishOpenInterface() function:

errorCode = tfFinishOpenInterface(interfaceHandle, IPAddr, mask);


Note Note: For more information on functions used in this section, please refer to the Programmer's Reference.


Single Send Call Send per Frame, Out of Order Send

Single call to the driver send per scattered frame.

By default, when the stack sends a frame scattered among different buffers, and the device driver supports scattered send, the stack will make multiple calls to the device driver send function (one per scattered buffer). This is inefficient. This section describes additional APIs that have been added to the stack in order to support a single call to the device driver send API per frame, even when sending scattered data.


Out of Order Frame Transmission

Also, because the user might want to order frames for transmission in a different order as transmitted by the stack, the user might want to signal out of order frame send completion. This is not allowed with the default device driver send interface, because the frame handle is not given to the user as a parameter, and therefore cannot be given as a parameter to tfSendCompleteInterface().

The modified device driver send API, described in this section, now takes the frame handle as a parameter. A new API (tfSendCompletePacketInterface()) has been added to allow the user to specify which frame has been transmitted. So even if the user device driver does not support scattered send, a user might still want to use the modified device driver send API described in this section, if the user needs to signal out of order frame send completion.


Warning Warning: The modified driver send interface is not supported for point to point link layers (such as PPP, or SLIP), and is not supported in conjunction with a transmit queue.


TM_USE_DRV_ONE_SCAT_SEND

First, in order to allow a single call to the driver send routine for a scattered frame, TM_USE_DRV_ONE_SCAT_SEND needs to be defined in <trsystem.h>.


Modified driverSend

The user driverSend function API must be modified to support a single call to the driver send routine for a scattered frame. The modified driverSend function now takes only two parameters. The first parameter is still the interfaceHandle as before. The second parameter is a pointer to a structure containing the information needed to access the scattered data in a frame.


int ttDevOneScatSendFunc (
ttUserInterface interfaceHandle,
ttUserPacketPtr packetUPtr
);

where ttUserPacketPtr is a pointer to a ttUserPacket structure. ttUserPacket and ttUserPacketPtr are defined as follows:


#define trsocket.h
 
/*
 * Data type used for the linked list of packets passed to the device
 * driver single call scattered send function prototype.
 */
typedef struct tsUserPacket
{
/* Next tsUserPacket for scattered data  */
/*    struct tsUserPacket TM_FAR   *pktuLinkNextPtr; */
    void               TM_FAR   *pktuLinkNextPtr;
/* Pointer to data */
    ttUser8BitPtr                pktuLinkDataPtr;
#ifdef TM_DSP
/* byte offset of start of data, in word pointed to by pktuLinkDataPtr */
    int                          pktuLinkDataByteOffset;
#endif /* TM_DSP */
#if defined(TM_DEV_SEND_OFFLOAD) || defined(TM_DEV_RECV_OFFLOAD)
    ttUserVoidPtr                pktuDevOffloadPtr;
#endif /* TM_DEV_SEND_OFFLOAD || TM_DEV_RECV_OFFLOAD */
/* Size of data pointed by pktuLinkDataPtr */
    ttPktLen                     pktuLinkDataLength;
/*
 * Total packet length (of chained scattered data). Valid in first link
 * only
 */
    ttPktLen                     pktuChainDataLength;
/*
 * Number of links linked to this one (not including this one).
 * Valid in first link only.
 */
    int                          pktuLinkExtraCount;
#ifdef TM_USE_USER_PARAM
/* User-defined data */
    ttUserGenericUnion           pktuUserParam;
#endif /* TM_USE_USER_PARAM */
} ttUserPacket;
 
typedef ttUserPacket TM_FAR * ttUserPacketPtr;

The modified user device driver send function will loop through all the links of the scattered frame in order to send a complete frame.


tfUseInterfaceOneScatSend()

The Treck stack needs to be made aware that the modified driver send function need to be used instead of the default driver send function. So, after the call to tfAddInterface(), and before the call to tfOpenInterface(), the user needs to call tfUseInterfaceOneScatSend():


int tfUseInterfaceOneScatSend (
ttUserInterface interfaceHandle,
ttDevOneScatSendFuncPtr devOneScatSendFuncPtr
);


After the interface is configured, this new device driver send function will be called, where the first parameter to the device driver send function is the interface handle as before, and the second parameter, is a pointer to the ttUserPacket structure as described above.


Note Note: Once tfUseInterfaceOneScatSend() has been called successfully on an interface, the stack will always call the modified device driver send function passed as a second parameter to tfUseInterfaceOneScatSend() for that interface.


Example

Modified driverSend function to support per-frame single call scattered send:

int devOneScatSendFunc (
ttUserInterface interfaceHandle,
ttUserPacketPtr packetUPtr
);


User calls The user calls tfAddInterface() specifying a null device driver send function to add the interface:

interfaceHandle=tfAddInterface(
            "QUICC.SCC1",
            linkLayerHandle,
            tfDevEtherOpen,
            tfDevEtherClose,
            (ttDevSendFuncPtr)0,
            tfDevEtherReceive,
            (ttFreeRecvBufferFuncPtr)0,
            tfDevIoctl,
            tfDevGetPhyAddr,
            &errorCode);

Next the user calls the new tfUseInterfaceOneScatSend() API to specify the modified device driver send function:

errorCode = tfUseInterfaceOneScatSend(ttUserInterface  interfaceHandle,
                                      ttDevOneScatSendFunc devOneScatSendFunc);

If the call does not fail, then the user can now call tfConfigInterface()/tfOpenInterface() to configure an IP address on the interface.

Note that tfConfigInterface()/tfOpenInterface() is unchanged.

if (errorCode == TM_ENOERROR)
{
    ipAddress=inet_addr("1.2.3.4");
    netMask=htonl(0xffffff00); /* 255.255.255.192 */
/* Config the Interface */    
    errorCode=tfOpenInterface(
            interfaceHandle,
            ipAddress,
            netMask,
            TM_DEV_SCATTER_SEND_ENB, 
            5, 
            0);
}


tfSendCompletePacketInterface()

The default device driver interface does not allow the user to specify which frame have been sent by the device driver. The stack assumes that the frames have been sent in the order of transmission to the device driver send function.

When using a the modified single call device driver send interface, the user has the choice of either calling tfSendCompleteInterface() for each frame that has been sent out, or tfSendCompletePacketInterface specifying the frame that has just been sent out. The later choice is useful for device driver where frames might not be sent out in the order they were transmitted.

tfSendCompletePacketInterface is similar to tfSendCompleteInterface(), but takes the frame handle passed to the modified device driver send function as a parameter:


void tfSendCompletePacketInterface (
ttUserInterface interfaceHandle,
ttUserPacketPtr packetPtr,
int devDriverLockFlag
);


Limitations: The single scattered send call is not supported on point to point link layers such as PPP, or SLIP.


Device Driver Scattered recv ("Gather Read")

Description

By default the stack expects all data within a frame given by the user device driver recv function to be contiguous. Some device drivers support receiving data within a frame in scattered buffers, because it is more efficient. The interface to the device driver recv interface can be optionally modified to allow it.


TM_USE_DRV_SCAT_RECV

First, in order to allow a scattered device driver recv, TM_USE_DRV_SCAT_RECV need to be defined in <trsystem.h>.


Modified driver recv routine

int ttDevScatRecvFunc (
ttUserInterface interfaceHandle,
ttDruBlockPtrPtr uDevBlockPtrPtr,
int * uDevBlockCountPtr,
int * flagPtr
);


Function Description

The user driverRecv function API needs to be modified to support giving scattered data within a frame to the stack. As in the case of the non scattered driver recv API, the user can choose to either use pre-allocated stack buffers or non-stack buffers to store the data into.

The modified user device driver recv function will be responsible for initializing the following:

  • (*uDevBlockPtrPtr)
  • (*uDevBlockCountPtr)
  • (*flagPtr)
Parameters
  • interfaceHandle
Initialized by the caller, as before.
  • uDevBlockPtrPtr
Upon return from the device driver scattered recv function, points to an array of user block data of type ttDruBlock.
  • uDevBlockCountPtr
Upon return from the device driver scattered recv function, contains the number of such elements.
  • flagPtr
Upon return from the device driver scattered recv function, indicates whether the stack owns the buffers (TM_DEV_SCAT_RECV_STACK_BUFFER) or whether the device driver owns the buffers (TM_DEV_SCAT_RECV_USER_BUFFER).


#define trsocket.h
 
#ifdef TM_USE_DRV_SCAT_RECV
/*
 * . Data type to convey information from the device driver scattered
 *   recv function to the tfRecvScatInterface() API.
 */
typedef struct tsDruBlock
{
#define druBufferPtr      druBufferUnion.druUnBufferPtr
#define druStackBufferPtr druBufferUnion.druUnStackBufferPtr
    union
    {
/*
 * Device Driver Scattered Recv API:
 * . using non pre-allocated device driver buffers, or
 * . using Turbo Treck pre-allocated device driver buffer (from
 *   tfGetDriverBuffer/tfGetEthernetBuffer) (pointer to buffer as gotten
 *   from the first parameter of tfGetDriverBuffer/tfGetEthernetBuffer).
 *
 * Pointer to buffer that will be passed to the device driver scattered
 * free routine (non pre-allocated device driver buffers, or to the
 * stack (pre-allocated device driver buffers)
 */
        char     TM_FAR * druUnBufferPtr;
        ttUserBuffer      druUnStackBufferPtr;
    } druBufferUnion;
/* Pointer to beginning of data */
    char TM_FAR * druDataPtr;
/* Data length */
    int           druDataLength;
#ifdef TM_USE_USER_PARAM
/* User-defined data */
    ttUserGenericUnion    druUserParam;
#endif /* TM_USE_USER_PARAM */
} ttDruBlock;
 
typedef ttDruBlock    TM_FAR * ttDruBlockPtr;
typedef ttDruBlockPtr TM_FAR * ttDruBlockPtrPtr;
#endif /* TM_USE_DRV_SCAT_RECV */


If the device driver scattered recv function sets *flagPtr to TM_DEV_SCAT_RECV_STACK_BUFFER, then druStackBufferPtr should point to a buffer pointed to by first parameter of either tfGetEthernetBuffer() or tfGetDriverBuffer(), and the stack will be responsible for freeing that buffer when the stack is done processing that buffer.

If the device driver scattered recv function sets *flagPtr to TM_DEV_SCAT_RECV_USER_BUFFER, then druBufferPtr points to a user allocated buffer. When the stack is done processing that buffer, the stack will call the device driver free function (as set in tfAddInterface()).

The user is responsible for managing the memory containing the array of ttDruBlock. It is guaranteed that when the stack calls the modified driver recv function for an interface, the array of ttDruBlock structures previously given to the stack by a previous call to the driver recv function for the same interface will not be accessed anymore. So it is safe for the user to re-use the array itself, then. On the other hand, it is only safe to re-use a user allocated buffer after the device driver free function is called.


tfUseInterfaceScatRecv()

The Treck stack needs to be made aware that the modified driver recv function needs to be used instead of the default driver recv function. So, after the call to tfAddInterface(), and before the call to tfOpenInterface(), the user needs to call tfUseInterfaceScatRecv():


int tfUseInterfaceScatRecv (
ttUserInterface interfaceHandle,
ttDevScatRecvFunc devScatRecvFunc
);


tfRecvScatInterface()

Instead of calling tfRecvInterface(), the user needs to call tfRecvScatInterface(). The modified device driver recv function is itself called from within tfRecvScatInterface().


Scattered recv contiguous length threshold used in tfRecvScatInterface()

Description

tfRecvScatInterface() will automatically copy up to a per device configurable recv contiguous header length, if the data received in the first link of a device driver scattered recv is below this threshold. In that case, a new buffer is allocated by the stack, and the threshold number of bytes (but not more than the total length of the buffer) is copied.

Compile time value: TM_DEV_DEF_RECV_CONT_HDR_LENGTH Default threshold value is given by the TM_DEV_DEF_RECV_CONT_HDR_LENGTH macro and is by default defined to 68 for IPv4, and 88 for IPv6. It can be defined in <trsystem.h> to overwrite the default value.

Run time modification using tfInterfaceSetOptions() That default threshold value can be changed at run time using the tfInterfaceSetOptions() API, with the TM_DEV_OPTIONS_SCAT_RECV_LENGTH option.


Dealing with non contiguous network protocol headers in scattered recv buffers, TM_RECV_SCAT_MIN_INCR_BUF

When the stack processes a received scattered buffer, it checks that the network protocol header is contiguous at a given layer. It if is not, then it means that the header straddles between the first buffer and consecutive buffers in the frame If there is enough room at the end of the first of those consecutive buffers, then the end of the header is copied there. If there is not enough room, then a new buffer is allocated and replaces the first buffer. Data from the first buffer, plus the end of the header is copied into the first buffer. To prevent numerous re-allocation of the first buffer while processing the network headers, we allocate the maximum of TM_RECV_SCAT_MIN_INCR_BUF, and size of data that needs to be contiguous.

By default TM_RECV_SCAT_MIN_INCR_BUF is set to 128. It can be defined to a different value in <trsystem.h>.

#define TM_RECV_SCAT_MIN_INCR_BUF    128

Example New User device driver recv function:

int tfDevEtherScatRecvFunc(ttUserInterface interfaceHandle,
                           ttDruBlockPtrPtr uDevBlockPtrPtr,
                           int * uDevBlockCountPtr,
                           int * flagPtr );

User calls: User calls tfAddInterface() specifying a null device driver recv function:

/* Add the Interface */
interfaceHandle=tfAddInterface(
            "QUICC.SCC1",
            linkLayerHandle,
            tfDevEtherOpen,
            tfDevEtherClose,
            tfDevEtherSend,
            (ttDevRecvFuncPtr)0,
            tfDevFreeRecvBuffer,
            tfDevIoctl,
            tfDevGetPhyAddr,
            &errorCode);

Next the user calls the new tfUseInterfaceScatRecv() API to specify the device driver scattered buffer recv function:

errorCode = tfUseInterfaceScatRecv(ttUserInterface interfaceHandle,
                                     ttDevScatRecvFunc tfDevEtherScatRecvFunc);

If the call does not fail, then the user can now call tfConfigInterface()/tfOpenInterface() to configure an IP address on the interface. Note that tfConfigInterface()/tfOpenInterface() is unchanged.

if (errorCode == TM_ENOERROR)
{
    ipAddress=inet_addr("1.2.3.4");
    netMask=htonl(0xffffff00); /* 255.255.255.0 */    
/* Config the Interface */    
    errorCode=tfConfigInterface(
        interfaceHandle,
        ipAddress,
        netMask,
        TM_DEV_SCATTER_SEND_ENB,  
        5,
        0);
}

Next, the user calls tfRecvScatInterface() instead of tfRecvInterface().


No copy loopback driver

For testing purposes, a loop back device driver has been added below link layer that uses single scattered device driver send calls, and scattered device driver recv calls.


ttUserInterface tfUseScatIntfDriver (
char * namePtr,
ttUserLinkLayer linkLayerHandle,
int * errorCode
);


In order to use this no copy loop back driver:

1. Add the following macros to <trsystem.h>:
#define TM_LOOP_TO_DRIVER
#define TM_USE_DRV_SCAT_RECV
#define TM_USE_DRV_ONE_SCAT_SEND
2. Replace tfAddInterface() with tfUseScatIntfDriver().

In the examples directory txscatIp.c and txscatdr.c use the no copy loopback driver.


<< Using Ethernet or PPP
Testing the Device Driver >>