10

Integrating a New Network Interface Driver



10.1    Introduction

The purpose of the MUX is to provide an interface that insulates network services from the particulars of network interface drivers, and vice versa. Currently, the MUX supports two network driver interface styles, the Enhanced Network Driver (END) interface and the Network Protocol Toolkit (NPT) driver interface.

The END is the original MUX network driver interface. ENDs are frame-oriented drivers that exchange frames with the MUX. The NPT-style drivers are packet-oriented drivers that exchange packets with the MUX. These packets are stripped of all datalink layer information. Currently, all network drivers supplied by Wind River are ENDs, as is the generic driver template defined in templateEnd.c. There is no generic template for an NPT driver.

Support for the NPT-style driver is part of a set of MUX extensions designed to facilitate the implementation of MUX-compatible network services. These extensions include a registration mechanism for an alternative address resolution utility and support for back ends that let you extend the sockets API so that applications can use sockets to access a new network service. Both of these features are described in 11. Integrating a New Network Service.

10.1.1   The MUX and the OSI Network Model

The OSI Network Model describes seven layers through which data passes when it is transmitted from an application on one machine to a peer on a remote machine reachable through a network. Transmitted data passes through these layers in the order shown in Figure 10-1

Figure 10-1 :   The OSI Network Model and the MUX

.

Starting in the application layer, data passes down through each layer of the stack to the physical layer, which handles the physical transmission to the remote machine. After arriving on the remote machine, data passes up through each layer from the physical to the application.

In the abstract, each layer in the stack is independent of the other layers. A protocol in one layer exchanges messages with peers in the same layer on remote machines by passing the message to the layer immediately below it. Whether the message passes down through other layers is not its concern. Ideally, the protocol is insulated from such details.

In practice, network stacks that implement each layer with perfect independence are rare. Within TCP/IP, the protocols that manage the Transport and Network layer functions are sufficiently coupled that they effectively comprise a single layer, which this manual sometimes refers to as the protocol layer.

The MUX is an interface between the datalink and this protocol layer. However, the MUX is not a new layer. There are no MUX-level protocols that communicate with peers in the MUX of a remote machine. The MUX concerns itself solely with standardizing communication between the protocol and datalink layers of a single stack. Because of the MUX, no protocol or network driver needs direct knowledge of the other's internals.

For example, when a network driver needs to pass up a received packet, the driver does not directly access any structure or function within the destination network service. Instead, when the driver is ready to pass data to the service, the driver calls a MUX function that handles the details. The MUX does this by calling the receive function that the network service registered with the MUX. This design lets any MUX-compatible network service use any MUX-compatible network driver.

10.1.2   The Protocol-to-MUX Interface

To interact with the MUX, a protocol or service calls either muxBind( ) or muxTkBind( ). These routines bind the protocol or service to at least one network driver through the MUX. Within the bind call, the network protocol or service supplies pointers to functions that the MUX uses to:

  • shut down the service
  • pass an error message up to the service
  • pass a packet up to the service
  • restart the service

The exact prototypes for these functions vary slightly depending on whether you use muxBind( ) or muxTkBind( ). After the protocol or service has bound itself to a driver through the MUX, it can then call MUX-supplied functions, such as muxSend( ) or muxTkSend( ), to transmit a packet or request other MUX services.

When working with the default VxWorks stack, you do not need to make a direct call to muxBind( ). This is handled for you by the internals of ipAttach( ). For the boot device, the built-in TCP/IP stack initialization code automatically calls ipAttach( ). To bind the VxWorks stack to additional interfaces, you must make an explicit ipAttach( ) call for each additional interface. Again, the internals of ipAttach( ) handle the muxBind( ) call.

To free a network service from a binding to a driver in the MUX, you can call muxUnbind( ). Although, if the binding was created using ipAttach( ), you should call ipDetach( ), which handles the muxUnbind( ) call and other details for you.

10.1.3   The Datalink-to-MUX Interface

To add an END or NPT device to the MUX, call muxDevLoad( ) for each driver you want to add. This is done for you automatically in the standard network initialization code if you name the driver's load function in the endDevTbl[ ]. The stack initialization code uses this function name as input to a muxDevLoad( ) call.

Internally, the driver's load function must allocate and partially populate an END_OBJ structure and a NET_FUNCS structure. The END_OBJ structure provides the MUX with a description of the device, and the NET_FUNCS structure provides the MUX with pointers to the driver's standard MUX interface functions, xStart( ), xStop( ), xReceive( ), xIoctl( ), and so on.

After a driver is loaded in to the MUX and has been bound to a protocol, it can pass received packets up to the MUX by calling muxReceive( ), if it is an END, or muxTkReceive( ), in NPT drivers.1


*      
NOTE: The standard VxWorks stack expects to borrow the buffers it receives and thus avoid data copying. If a device cannot transfer incoming data directly into clusters, the driver must explicitly copy the data from private memory into a cluster in sharable memory before passing it in an mBlk up to the MUX.

The receive function calls the stackRcvRtn( ) function registered by the network service to which the driver wants to send a packet. If the mux[Tk]Receive( ) call returns OK, the driver can consider the data delivered. After a packet is delivered, the driver must free or schedule a free (pending a transmit interrupt) of the buffers it allocated for the packet.

To remove a network interface from the MUX, call muxDevUnload( ).

10.1.4   How ENDs and NPT Drivers Differ

The NPT driver is a packet-oriented equivalent to the frame-oriented END. Both the NPT driver and the END are organized around the END_OBJ and the NET_FUNC structures, and both driver styles require many of the same entry points:

xLoad( ) - load a device into the MUX and associate a driver with the device

xUnload( ) - release a device, or a port on a device, from the MUX

xSend( ) - accept data from the MUX and send it on towards the physical layer

xMCastAddrDel( ) - delete a multicast address registered for a device

xMCastAddrGet( ) - get a list of multicast addresses registered for a device

xMCastAddrAdd( ) - add a multicast address to those registered for a device

xPollSend( ) - send packets in polled mode rather than interrupt-driven mode

xPollReceive( ) - receive frames in polled rather than interrupt-driven mode

xStart( ) - connect device interrupts and activate the interface

xStop( ) - stop or deactivate a network device or interface

xIoctl( ) - support various ioctl commands2

xBind( ) - exchange data with the protocol layer at bind time (optional)3

For the most part, the prototypes for these entry points are identical. The exceptions are the send and receive entry points.

  • NPT send entry points take these additional parameters:

  • a MAC address character pointer

  • a networks service type value

  • a void* pointer for any network service data the driver might need in order to prepare the packet for transmission on the physical layer

  • NPT receive entry points likewise take additional parameters:

  • a frame type

  • a pointer to the start of the network frame

  • a void* pointer for any addition network service data that is important to the protocol layer

The three END entry points not included in an NPT driver are:4

xAddressForm( ) - add addressing information to a packet

xAddrGet( ) - extract the addressing information from a packet

xPacketDataGet( ) - separate the addressing information and data in a packet

The above functions were removed from the NPT driver because they are frame-oriented and so irrelevant to a packet-oriented driver.

The following registration interface lets you manage an address resolution function for a protocol/interface pair.

muxAddrResFuncAdd( ) - add an address resolution function

muxAddrResFuncGet( ) - get the address resolution function for ifType/protocol

muxAddrResFuncDel( ) - delete an address resolution function

For Ethernet devices, the standard VxWorks implementation automatically assigns arpresolve( ) as the address resolution function. If you are writing an END that does not run over Ethernet, you also need to implement the xAddressForm( ), xAddrGet( ), and xPacketDataGet( ) entry points explicitly. ENDs running over Ethernet typically use the endLib implementations of these functions.

10.1.5   Managing Memory for Network Drivers and Services

The default VxWorks stack uses netBufLib to manage its internal system and data memory pools. Similarly, almost all the shipped ENDs use netBufLib to manage memory pools for their receive buffers. If the standard netBufLib implementation is not suitable to your needs, you can replace it. If you are careful to preserve the API of the current netBufLib routines, the standard VxWorks stack and the shipped ENDs should be able to use your new memory allocation routines without modification.


*      
NOTE: The standard VxWorks stack expects to borrow the buffers it receives and thus avoid data copying. If a device cannot transfer incoming data directly into clusters, the driver must explicitly copy the data from private memory into a cluster in sharable memory before passing it in an mBlk up to the MUX.

10.1.6   Supporting Scatter-Gather in Your Driver

Some devices support breaking up a single network packet into separate chunks of memory. This makes it possible to handle the outgoing network packets as a chain of mBlk/clBlk/cluster constructs without any copying.

When a driver gets a chain of mBlks, it can decide how to transmit the clusters in the chain. If it is able to do a gather-write, it does not need to do any data copying. If it cannot, then it must collect all of the data from the chain into a single memory area before transmitting it.

10.1.7   Early Link-Level Header Allocation in an NPT Driver

The NPT driver and its MUX support functions are automatically configured to allocate extra room at the beginning of an outgoing packet to hold the network- and transport-layer header information. This allows those layers to copy in their headers without additional overhead in the form of memory allocation and buffer chaining.

You can configure the NPT-supporting MUX functions to also allocate extra room for the data-link-layer header by setting the USR_MAX_LINK_HDR configuration parameter or #define in the configNet.h file for your BSP. You should set this value to the largest of the data-link-layer header sizes used by the drivers in your system. For instance, if you add the following line:

#define USR_MAX_LINK_HDR 16

sixteen extra bytes will be prepended to outgoing packets, and drivers with data-link-layer headers of 16 bytes or fewer will have that space available in the packet without having to prepend a new mBlk during their endAddressForm( ) or nptSend( ) functions.


*      
NOTE: The USR_MAX_LINK_HDR configuration parameter only applies to header information within the AF_INET addressing family.

When using the M_PREPEND( ) macro to add a header to a packet, this extra space will be automatically used (or, if the space has not been pre-allocated, a new mBlk will automatically be generated and prepended). See A.6 Macros for Buffer Manipulation, p.257.

10.1.8   Buffer Alignment

Some microprocessors, most notably those from MIPS and ARM, restrict data access for long words (32-bit values) to the four-byte boundary. Accessing the data in a long word at any other point, such as at a two-byte boundary, results in a segmentation fault. When this restriction is applied to buffers, it requires that all long-word fields within the buffer must align on absolute four-byte boundaries.5

Many protocols (IP, TCP, and the like) specify four-byte fields in their headers. Conveniently, these protocol packets are usually set up so that these four-byte fields align on four-byte boundaries relative to the start of the protocol packet. Thus, if your network driver passes up a buffer in which the payload data (for example, an IP packet) always starts at an absolute four-byte boundary, the fields within the data should align correctly.


*      
WARNING: When working with alignment-sensitive hardware, if the payload data contains fields that the stack accesses as long words, those fields must align on absolute four-byte boundaries. Otherwise, the stack crashes when it tries to access those long word fields.

Unfortunately, when a packet first arrives, its datalink layer header might not end on an absolute four-byte boundary. For example, the RFC 894 Ethernet packet (see Figure 10-2) has a 14-byte header that precedes the payload data. If you receive this type of Ethernet packet on a long word boundary, any four-byte aligned payload data (such as an IP packet) following such a header would be misaligned because the payload data would start on a two-byte boundary. If you pass this misaligned data up to the MUX, the stack crashes.

Figure 10-2 :   RFC 894 Ethernet Packets Are Problematic for Alignment-Sensitive Hardware

To guarantee correctly aligned payload data, your driver can:

  • copy the data (the worst-case solution)
  • offset the receive buffers (often impossible on hardware that needs it)
  • use a scatter-gather receive (the most elegant solution)
Copying the Data

The simplest solution to the alignment problem is for the driver to copy each packet upon reception such that the data aligns properly. The driver can then safely pass the data up to the MUX. Because such a copy is time consuming, it is a worst-case solution.

Offsetting the Receive Buffers

On some hardware, the driver can offset the receive buffers that it gives to the hardware such that the protocol headers within the received packets start at a four-byte boundary. This misaligns the MAC header, but this is not usually a problem. Neither the stack nor the MUX ever access Ethernet header data as anything but eight-bit and 16-bit quantities. Thus, the access restriction that applies to long words only does not apply.

To succeed, this approach relies on the ability to DMA data into a misaligned (non four-byte aligned) buffer. Unfortunately, some DMA controllers do not allow misaligned buffers. In fact, on boards that have CPUs with alignment issues, these issues are usually pushed down into the rest of the hardware infrastructure, including the DMA engines. Thus, this approach is often impossible on the very hardware that requires it.

Figure 10-3 :   Receiving an RFC 894 Packet into an Offset Buffer

In Figure 10-3, the Ethernet header no longer starts on a four-byte boundary. Therefore, neither the destination address nor the type fields in the Ethernet header fall on four-byte boundaries. However, because the stack does not access either of these fields as long words, no segmentation fault occurs.

Using a Scatter-Gather Receive

If the hardware can handle scatter-gather lists, your driver can use this feature to handle the alignment problem elegantly. For each packet, your driver allocates two buffers: one buffer for the MAC layer header data (Ethernet header), and a second buffer for the payload data (such as an IP datagram). See Figure 10-4.

Figure 10-4 :   Receiving an RFC 894 Packet into Two Linked Scatter-Gather Buffers

On reception, the device places the MAC header into one buffer and the payload data into the other. The driver receive function then links these together and processes them as a buffer chain in which each component buffer automatically starts on a four-byte boundary. Therefore, when the driver hands the buffer chain up to the MUX, the payload data is correctly aligned.



10.2    END Implementation

This section presents an overview of how an END operates followed by implementation recommendations for the standard END entry points. If you compare this section with 10.3 NPT Driver Implementation, p.205, you will notice a strong parallelism. This is because the NPT is a generalized extension of the END. Thus, the implementation recommendations for both driver styles are nearly identical. However, the few differences that do exist are critical.

If you are writing an END, you are writing a driver that passes its frames up to the standard VxWorks implementation. As a starting point for your END, you should use the generic END in templateEnd.c.

10.2.1   END Operation

This subsection presents an overview of the following END operations:

  • adding an END to VxWorks
  • launching an END
  • responding to a service bind event
  • receiving frames


*      
NOTE: For instructions on starting additional drivers at run time, see Manually Starting Additional Network Interfaces at Run-Time, p.66.

For the most part, the NPT and the END handle these operations identically. The major exception is in the area of receiving frames. NPT drivers pass up the received frames stripped of all datalink header information. ENDs include the frame header information in the packets they pass up to the MUX.

Adding an END to VxWorks

Adding your driver to the target VxWorks system is much like adding any other component. The first step is to compile and include the driver code in the VxWorks image. A description of the general procedures can be found in the Tornado User's Guide.

Because VxWorks allows you to create more than one network device, you must set up a table that groups the #define statements that configure these devices into device-specific groups. This table, endDevTbl[ ],is defined in the configNet.h file in your target/src/config/bspname directory, where bspname is the name of your board support package, such as mv162 or pc486. For example, to add an ln7990 END, you would edit configNet.h to contain lines such as:

/* Parameters for loading ln7990 END supporting buffer loaning. */ 
#define LOAD_FUNC_0 ln7990EndLoad
#define LOAD_STRING_0 "0xfffffe0:0xffffffe2:0:1:1"
#define BSP_0 NULL

You should define three constants, like those shown above, for each of the devices you want to add. To set appropriate values for these constants, consider the following:

LOAD_FUNC

#define LOAD_FUNC_n ln7990EndLoad
LOAD_STRING

You must also edit the definition of the endDevTbl[ ] (a table in configNet.h that specifies the ENDs included in the image) to include entries for each of the devices to be loaded:

END_TBL_ENTRY endDevTbl [] = 
{
{ 0, LOAD_FUNC_0, LOAD_STRING_0, BSP_0, NULL, FALSE },
{ 1, LOAD_FUNC_1, LOAD_STRING_1, BSP_1, NULL, FALSE },
{ 0, END_TBL_END, NULL, 0, NULL, FALSE },
};

The first number in each table entry specifies the unit number for the device. The first entry in the example above specifies a unit number of 0. Thus, the device it loads is deviceName0. The FALSE at the end of each entry indicates that the entry has not been processed. After the system has successfully loaded a driver, it changes this value to TRUE in the run-time version of this table. To prevent the system from automatically loading your driver, set this value to TRUE.

At this point, you are ready to rebuild VxWorks to include your new drivers. When you boot this rebuilt image, the system calls muxDevLoad( ) for each device specified in the table in the order listed.


*      
NOTE: The endDevTbl[ ] can contain a mix of NPT drivers and ENDs.

Launching the Driver

At system startup, the VxWorks kernel spawns the user root task, which initializes the network. This task calls muxDevLoad( ), which calls the endLoad( ) function in your driver. This endLoad( ) function creates and partially populates an END_OBJ structure that describes the driver. Among the information that endLoad( )  must supply in the END_OBJ is a reference to a NET_FUNCS structure that endLoad( ) has allocated and populated with references to the driver entry points.

After muxDevLoad( ) loads your driver, a muxDevStart( ) call executes the endStart( ) function in your driver. The endStart( ) function should activate the driver and register an interrupt service routine for the driver with the appropriate interrupt connect routine for your architecture and BSP.

Binding to a Service

An END typically does not react when a service uses binds to a device. However, an END may take advantage of the same facility used by an NPT driver to exchange data with a service during the bind phase. See Responding to Network Service Bind Calls, p.207, for more complete information on this process.

Receiving Frames in Interrupt Mode

When an interrupt is received, VxWorks invokes the interrupt service routine (ISR) that was registered by the endStart( ) function. This ISR should do the minimum amount of work necessary to transfer the frame from the local hardware into accessible memory (ideally a cluster: see 10.1.3 The Datalink-to-MUX Interface, p.184).

To minimize interrupt lockout time, your ISR should handle directly (at interrupt level) only those actions that require minimum execution time, such as error checking or device status change. The ISR should queue all time-consuming work for processing at task level.

To queue frame reception work for processing at the task level, your ISR can use netJobAdd( ). This function takes a function pointer and up to five additional arguments (representing parameters to the function referenced by the function pointer)7 .

The netJobAdd( ) function prototype is:

STATUS netJobAdd 
(
FUNCPTR  routine,
int      param1,
int      param2,
int      param3,
int      param4,
int      param5
)

The routine in this case should be the function in your driver that performs frame processing at the task level. The netJobAdd( ) function puts that function on tNetTask's work queue and gives a semaphore that awakens tNetTask.

Upon awakening, tNetTask dequeues function calls and associated arguments from its work queue. It then executes these functions in its context until the queue is empty.

Your task-level frame reception function should do whatever is necessary to construct an mBlk chain containing the frame to hand off to the MUX, such as assuring data coherency. This function might also use a level of indirection in order to check for and avoid race conditions before it attempts to do any processing on the received data. This routine should also set M_MCAST and M_BCAST flags in the mBlk header, if appropriate. When all is ready, your driver passes the frame up to the MUX by calling the function referenced in the receiveRtn member of the END_OBJ structure that represents your device (see B.3.3 END_OBJ, p.287).

10.2.2   The END Interface to the MUX

This subsection describes the driver entry points and the shared data structures that comprise an END's interface to the MUX.

Data Structures Shared by the END and the MUX

The core data structure for an END is the END object, or END_OBJ. This structure is defined in target/h/end.h (see also B.3.3 END_OBJ, p.287). The driver's load function returns a pointer to an END_OBJ that it allocated and partially populated. This structure supplies the MUX with information that describes the driver as well as a pointer to a NET_FUNCS structure populated with references to the END's standard entry points.

Although the driver's load function is responsible for populating much of the END_OBJ structure, some of its members are set within the MUX when a protocol binds to the device. Specifically, the MUX sets the END_OBJ's receiveRtn member so that it contains a reference to the bound protocol's receive routine. The driver calls this referenced function when it needs to send a packet up to the protocol. The driver could access this function reference with a call to muxReceive( ) or muxTkReceive( ).

END Entry Points Exported to the MUX

Table 10-1 lists the standard driver entry points that the NET_FUNCS structure exports to the MUX. In this manual, the functions use a generic "end" prefix, but in practice this prefix is usually replaced with a driver-specific identifier, such as "ln7990" for the Lance Ethernet driver.

Table 10-1 :   END Functions   


Function
Description

endLoad( )
Load a device into the MUX and associate a driver with the device.
endUnload( )
Release a device, or a port on a device, from the MUX.
endSend( )
Accept data from the MUX and send it on to the physical layer.
endMCastAddrDel( )
Remove a multicast address from those registered for the device.
endMCastAddrGet( )
Retrieve a list of multicast addresses registered for a device.
endMCastAddrAdd( )
Add a multicast address to the list of those registered for the device.
endPollSend( )
Send frames in polled mode rather than interrupt-driven mode.
endPollReceive( )
Receive frames in polled mode rather than interrupt-driven mode.
endStart( )
Connect device interrupts and activate the interface.
endStop( )
Stop or deactivate a network device or interface.
endAddressForm( )
Add addressing information to a packet.
endAddrGet( )
Extract the addressing information from a packet.
endPacketDataGet( )
Separate the addressing information and data in a packet.
endIoctl( )
Support various ioctl commands.

endLoad( )

Before a network interface can be used to send and receive frames, the appropriate device must be loaded into the MUX and configured. The muxDevLoad( ) function calls your driver's endLoad( ).

This function takes an initialization string, the contents of which are user-defined but generally include such items as the unit number identifying the physical interface8 , an interrupt vector number, and the address of memory mapped registers.

The endLoad( ) function must be written as a two-pass algorithm. The MUX calls it twice during the load procedure. In the first pass, the initialization string is blank (all zeros). The endLoad( ) routine is expected to check for the blank string and return with the name of the device copied into the string. A second call is then made to endLoad( ) with the actual initialization string that was supplied to muxDevLoad( ). The endLoad( ) then must return a pointer to the END_OBJ that it allocates or a NULL if the load fails.

Typically, the endLoad( ) function, in its second pass, will:

  • Initialize the device and interface.
  • Allocate and fill the END_OBJ structure.
  • Initialize any necessary private structures.
  • Parse and process the initialization string.
  • Create and populate the MIB II interface table.
  • Create and initialize a private pool of memory using the API in netBufLib.
  • Allocate one or more network buffer pools using netBufLib.
  • Fill the NET_FUNCS table referenced by pNetFuncs in the END_OBJ structure.

The endLoad( ) function is based on the following skeleton:

END_OBJ * endLoad 
(
char * initString, /* defined in endTbl */
void * pBsp         /* BSP-specific information (optional) */
)
{
END_OBJ * newEndObj;

if( !initString )             /* initString is NULL, error condition */
{
/* set errno perhaps */
return( (END_OBJ *) 0 );
}
else if( initString[0] == 0 ) /* initString[0] is NULL, pass one */
{
strcpy( initString, "foo" );
return( (END_OBJ *) 0 );
}
else      /* initString is not NULL, pass two */
{
/* initialize device */
newEndObj = (END_OBJ *) malloc( sizeof(END_OBJ) );
/* fill newEndObj and newEndObj->pFuncTable */
/* create and populate the MIB2 interface table */
/* initialize any needed private structures */
/* parse and process initString, and pBsp if necessary */
/* create a private pool of memory using netBufLib API */
/* create network buffer pools using netBufLib API */
return( newEndObj );
}
}
endUnload( )

An endUnload( ) function is invoked when muxDevUnload( ) is called by the system application. In this routine, the driver is responsible for doing whatever is necessary to "release" the device.

This function is called for each port that has been activated by a call to endLoad( ). If the device has multiple ports loaded, the driver must not free up any shared resources until an unload request has been received for each of the loaded ports.

The endUnload( ) routine does not need to notify services about unloading the device. Before calling endUnload( ), the MUX sends a shutdown notice to each service attached to the device.

The endUnload( ) prototype is:

STATUS endUnload 
(
END_OBJ * pEND /* END object */
)
endSend( )

The network driver send routine is referenced from the NET_FUNCS table that is created during the load process. The MUX calls this function when the network service issues a send request. The send routine is supplied a reference to an mBlk chain representing the link-level frame to be sent.

The endSend( ) prototype is:

STATUS endSend 
(
END_OBJ * pEND,   /* END object */
M_BLK_ID pPkt     /* mBlk chain containing the frame */
)

This function should return a status of:

  • OK , if the send was successful.

  • END_ERR_BLOCK, if the send could not be completed because of a transient problem such as insufficient resources.

  • ERROR, in this case, errno should be set appropriately.

endMCastAddrAdd( )

This function registers a physical-layer multicast address with the device. It takes as arguments a pointer to the END_OBJ returned by endLoad( ) and a string containing the physical address to be added.

This routine should reconfigure the interface in a hardware-specific way that lets the driver receive frames from the specified address.


*      
NOTE: To help you manage a list of Ethernet multicast addresses, VxWorks provides the etherMultiLib library.

The endMCastAddrAdd( ) prototype is:

STATUS endMCastAddrAdd 
(
END_OBJ * pEND,    /* END object */
char * pAddress  /* physical address or a reference thereto */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

endMCastAddrDel( )

This function removes a previously registered multicast address from the list maintained for a device. It takes as arguments a pointer to the END_OBJ returned by endLoad( ), and a string containing the physical address to be removed.

.


*      
NOTE: To help you manage a list of Ethernet multicast addresses, VxWorks provides the etherMultiLib library.

The endMCastAddrDel( ) prototype is:

STATUS endMCastAddrDel 
(
END_OBJ * pEND,    /* END object */
char * pAddress  /* physical address, or a reference thereto */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

endMCastAddrGet( )

This function retrieves a list of all multicast addresses that are currently active on the device. It takes as arguments a pointer to the END_OBJ returned by endLoad( ), and a pointer to a MULTI_TABLE structure into which the list will be put.


*      
NOTE: To help you manage a list of Ethernet multicast addresses, VxWorks provides the etherMultiLib library.

The endMCastAddrGet( ) prototype is:

STATUS endMCastAddrGet 
(
END_OBJ * pEND,       /* driver's control structure */
MULTI_TABLE * pMultiTable  /* container for address list */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

endPollSend( )

This routine provides a polled-mode equivalent to the driver's interrupt-driven send routine. It must either transfer a frame directly to the underlying device, or it must exit immediately if the device is busy or if resources are unavailable.


*      
NOTE: When the system calls your endPollSend( ) routine, it is probably in a mode that cannot service kernel calls. Therefore, this routine must not perform any kernel operations, such as taking a semaphore or allocating memory. Likewise, this routine must not block or delay because the entire system might halt.

Within your endPollSend( ) routine, verify that the device has been set to polled-mode (by a previous endIoctl( ) call). Your endPollSend( ) routine should then put the outgoing frame directly onto the network, without queuing the frame on any output queue.

This routine takes as arguments a pointer to the END_OBJ structure returned by endLoad( ) and a reference to an mBlk or mBlk chain containing the outgoing frame. It should return a status OK or ERROR (in which case, errno should be set).

The endPollSend( ) prototype is:

STATUS endPollSend 
(
END_OBJ *  pEND,   /* END object*/
M_BLK_ID pMblk    /* mBlk chain: data to be sent */
)
endPollReceive( )

This function receives frames using polling instead of an interrupt-driven model. The routine retrieves the frame directly from the network and copies it into the mBlks passed to the routine. If no frame is available, the function returns ERROR.


*      
NOTE: When the system calls your endPollReceive( ) routine, it is probably in a mode that cannot service kernel calls. Therefore, this routine must not perform any kernel operations, such as taking a semaphore or allocating memory. Likewise, this routine must not block or delay because the entire system might halt.

Within the endPollReceive( ) routine, verify that the device has been set to polled-mode (by a previous endIoctl( ) call). The routine should then retrieve the frame directly from the network and copy it into the mBlk passed in to the routine.

It takes as arguments a pointer to the END_OBJ structure returned by endLoad( ) and a reference to an mBlk or mBlk chain into which the incoming data should be put.

The endPollReceive( ) prototype is:

STATUS endPollReceive 
(
END_OBJ *  pEND,   /* returned from endLoad() */
M_BLK_ID pPkt     /* mBlk chain: data being received */
)

This function should return OK or an error value of EAGAIN if the received data is too large to fit in the provided mBlk, or if no data is available.

endStart( )

The driver's endStart( ) function connects device interrupts and makes the interface active and available. This function takes as its argument the unique interface identifier returned from the endLoad( ) call. As with endLoad( ), this call is made for each port that is to be activated within the driver.

The endStart( ) prototype is:

STATUS endStart 
(
END_OBJ * pEND, /* END object */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

endStop( )

The driver's endStop( ) function halts a network device, typically by disconnecting the appropriate interrupt. It does not remove the device by releasing the allocated data structures. This function takes as its argument the unique interface identifier returned from the endLoad( ) call.

The endStop( ) prototype is:

STATUS endStop 
(
END_OBJ * pEND, /* END object */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

endAddressForm( )

The endAddressForm( ) routine generates a frame-specific header which it prepends to the mBlk chain containing outgoing data. After adding the address segment to the mBlk, the routine should adjust the mBlk.mBlkHdr.mLen and mBlk.mBlkHdr.mData members accordingly.

If the incoming mBlk is not large enough to contain the added address information, an additional mBlk/clBlk cluster must be created for this purpose, and inserted at the beginning of the mBlk chain. For information on how to prevent this extra allocation and chaining, see 10.1.7 Early Link-Level Header Allocation in an NPT Driver, p.187.

The network protocol type can be found in the pDst.mBlkHdr.reserved field.

A reference to the new mBlk chain head is returned from endAddressForm( ).

The endAddressForm( ) prototype is:

M_BLK_ID endAddressForm 
(
M_BLK_ID pData        /* mBlk chain containing outgoing data */
M_BLK_ID pSrc,        /* source address, in an mBlk */
M_BLK_ID pDst,        /* destination address, in an mBlk */
BOOL      bcastFlag    /* use link-level broadcast ? */
)


*      
NOTE: The endLib library contains an address formation routine that generates and prepends Ethernet frame headers. Thus, you probably do not need to implement this function if you are running over Ethernet.

endAddrGet( )

This routine retrieves the address values for an incoming frame provided in an mBlk chain. If the additional mBlk parameters are not NULL, it sets the mBlk.mBlkHdr.mData and mBlk.mBlkHdr.mLen fields to indicate the location and size of the corresponding data-link layer addresses. The additional mBlk parameters are:

pSrc

pDst

pESrc

pEDst


*      
NOTE: The endLib library contains a routine for retrieving address values from Ethernet frame headers. Thus, you probably do not need to implement this function if your driver runs over Ethernet.

The endAddrGet( ) prototype is:

STATUS endAddrGet 
(
M_BLK_ID pData, /* mBlk chain containing frame */
M_BLK_ID pSrc, /* local source address, in an mBlk */
M_BLK_ID pDest, /* local destination address, in an mBlk */
M_BLK_ID pESrc, /* actual source address, in an mBlk */
M_BLK_ID pEDest /* actual destination address, in an mBlk */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

endPacketDataGet( )

This routine parses an incoming frame provided in an mBlk chain. It places the address information, including the size and offset of the address information, the offset of the frame payload, and the frame type, in the LL_HDR_INFO structure that is passed in as a parameter.


*      
NOTE: The endLib library contains a routine for retrieving the address and data offsets and sizes and the type information from Ethernet frames. Thus, you probably do not need to implement this function if your driver runs over Ethernet.

The endPacketDataGet( ) prototype is:

STATUS endPacketDataGet 
(
M_BLK_ID pData, /* mBlk chain containing packet */
LL_HDR_INFO * pHeader /* structure to hold header info */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

endIoctl( )

An END may need to support ioctl commands, particularly if it is to be used with the existing IP network service sublayer. See Table 10-4 for a list of commonly used ioctl commands.


*      
WARNING: If you are porting a driver from the BSD 4.3 model, you might be tempted to use the existing xxIoctl( ) routine as your endIoctl( ) routine, skipping the creation of separate routines for the multicast address table maintenance functions. Do not do this! Your driver must implement the multicast address table maintenance routines.

The endIoctl( ) function takes the following arguments:

  • the unique interface identifier returned from the muxDevLoad( ) call

  • the ioctl command being issued

  • a buffer for additional data given in the command or for data to be returned on completion of the command (defined as a caddr_t structure)

Table 10-2 :   ioctl Commands and Data Types   


Command
Function
Data Type

EIOCSFLAGS
Set device flags. See flags in B.3.3 END_OBJ, p.287.
int
EIOCGFLAGS
Get device flags.
int
EIOCSADDR
Set device address.
char *
EIOCGADDR
Get device address.
char *
EIOCMULTIADD
Add multicast address.
char *
EIOCMULTIDEL
Delete multicast address.
char *
EIOCMULTIGET
Get multicast list.
MULTI_TABLE *
EIOCPOLLSTART
Put device in polling mode.
NULL
EIOCPOLLSTOP
Put device in interrupt mode.
NULL
EIOCGFBUF
Get minimum first buffer for chaining.
int
EIOCQUERY
Retrieve the bind function.
END_QUERY *
EIOCGHDRLEN
Get the size of the datalink header.
int
EIOCGMIB2
Get MIB-II counters from the driver.
M2_INTERFACETBL *


*      
NOTE: Wind River reserves command constants (such as EIOCGFBUF) that are in the range of 0-128. If you want to use your own custom ioctl commands, define their command constants to be equivalent to numbers outside this range.

The endIoctl( ) prototype is:

int endIoctl 
(
END_OBJ * pEND,    /* END Object */
int command, /* ioctl command */
caddr_t buffer /* holds response from command */
)

This function returns 0 (zero) if successful, an appropriate error value otherwise, or EINVAL if the command is not supported.



10.3    NPT Driver Implementation

This section presents an overview of how an NPT driver operates followed by implementation recommendations for the standard NPT driver entry points. If you compare this section with 10.2 END Implementation, p.190, you will notice a strong parallelism. This arises from the fact that the NPT is a packet-oriented equivalent to the END. Thus, the implementation recommendations for both NPT drivers and ENDs are nearly identical. However, the few differences that do exist are critical.

Currently, VxWorks does not include an NPT driver implementation, only END implementations. As a starting point for an NPT driver, you might want to use the generic END defined in templateEnd.c.


*      
NOTE: The design situations that require an NPT driver instead of an END are rare. If you are writing a new driver, think first of implementing it as an END, for which there exists a template as well as working driver implementations that you can study. However, if porting packet-oriented driver, feel free to port it as an NPT driver. The VxWorks stack supports mixing the two driver styles.

10.3.1   NPT Driver Operation

This subsection presents an overview of the following NPT driver operations:

  • adding an NPT Driver to VxWorks
  • launching an NPT Driver
  • responding to a service bind event
  • receiving frames

For the most part, the NPT and the END handle these operations identically. The major exception is in the area of receiving frames. NPT drivers pass up the received frames stripped of all datalink header information. ENDs include the Ethernet header information in the packets they pass up to the MUX.

66


*      
NOTE: For instructions on starting additional drivers at run time, see Manually Starting Additional Network Interfaces at Run-Time, p.66.

Adding an NPT Driver to VxWorks

Adding your driver to the target VxWorks system is much like adding any other component. The first step is to compile and include the driver code in the VxWorks image. A description of this procedure is found in the Tornado User's Guide.

Because VxWorks allows you to create more than one network device, you must set up a table that groups the #define configuration statements for these devices into device-specific groups. This table, endDevTbl[ ], is defined in the configNet.h file in your target/src/config/bspname directory where bspname is the name of your board support package, such as mv162 or pc486. For example, to add an ln7990 NPT driver, you would edit configNet.h to contain lines such as:

/* Parameters for loading ln7990 NPT driver supporting buffer loaning. */ 
#define LOAD_FUNC_0 ln7990nptLoad
#define LOAD_STRING_0 "0xfffffe0:0xffffffe2:0:1:1"
#define BSP_0 NULL

Define three constants, like those shown above, for each of the devices you want to add. To set appropriate values for these constants, consider the following:

LOAD_FUNC

#define LOAD_FUNC_n ln7990nptLoad
LOAD_STRING

You must also edit the definition of the endDevTbl[ ] (a table in configNet.h that specifies the drivers included in the image) to list the devices to be loaded:

END_TBL_ENTRY endDevTbl [] = 
{
{ 0, LOAD_FUNC_0, LOAD_STRING_0, BSP_0, NULL, FALSE },
{ 1, LOAD_FUNC_1, LOAD_STRING_1, BSP_1, NULL, FALSE },
...
{ 0, END_TBL_END, NULL, 0, NULL, FALSE },
};

The first number in each table entry specifies the unit number for the device. The first entry in the example above specifies a unit number of 0. Thus, the device it loads is deviceName0. The FALSE at the end of each entry indicates that the entry has not been processed. After the system successfully loads a driver, it changes this value to TRUE in the run-time version of this table. To prevent the system from automatically loading your driver, set this value to TRUE.

After constructing these entries in configNet.h, you are ready to rebuild VxWorks to include your new drivers. When you boot this rebuilt image, the system calls muxDevLoad( ) for each device specified in the table in the order listed.


*      
NOTE: The endDevTbl[ ] can contain a mix of NPT drivers and ENDs.

Launching the Driver

At system startup, the VxWorks kernel spawns the user root task to initialize the network. The task calls muxDevLoad( ) which in turn calls the nptLoad( ) function in your driver. The nptLoad( ) function creates and partially populates an END_OBJ structure and a NET_FUNCS structure. The END_OBJ structure describes the driver to the MUX. The NET_FUNCS structure provides the MUX with references to the MUX-callable driver functions.

After muxDevLoad( ) loads your driver, a muxDevStart( ) call executes the nptStart( ) function in your driver. The nptStart( ) function should activate the driver and register an interrupt service routine for the driver with the appropriate interrupt connect routine for your architecture and BSP.

Responding to Network Service Bind Calls

A driver is not required to respond when a service binds to a device. However, if you want your driver to respond to a bind event, your driver can support an nptBind( ) function.

When a service or protocol binds to an interface controlled by your driver, the MUX uses the driver's nptIoctl( ) function to retrieve a pointer to that driver's nptBind( ) function (if any). The MUX then executes that function before continuing with the bind.

To get a pointer to a driver's nptBind( ), the MUX issues an EIOCQUERY command to the driver's nptIoctl( ) function. As input, the call supplies an END_QUERY structure whose members are used as follows:

query

queryLen

queryData

Receiving Frames in Interrupt Mode

When an interrupt is received, VxWorks invokes the interrupt service routine that was registered by the nptStart( ) function. This interrupt service routine should do the minimum amount of work necessary to transfer the frame from the local hardware into accessible memory (ideally a cluster: see 10.1.3 The Datalink-to-MUX Interface, p.184).

To minimize interrupt lockout time, the routine should handle at interrupt level only those tasks that require minimum execution time, such as error checking or device status change. The routine should queue all time-consuming work for processing at task level.

To queue frame reception work for processing at the task level, use the netJobAdd( ) function. This function takes a function pointer and up to five additional arguments (representing parameters to the function referenced by the function pointer). The netJobAdd( ) function prototype is: 9

STATUS netJobAdd 
(
FUNCPTR  routine,
int      param1,
int      param2,
int      param3,
int      param4,
int      param5
)

The routine in this case should be the function in your driver that completes frame processing at the task level. The netJobAdd( ) function puts the job request on tNetTask's work queue and gives the appropriate semaphore that awakens tNetTask.

Upon awakening, tNetTask dequeues function calls and associated arguments from its work queue. It then executes these functions in its context until the queue is empty.

Your task-level frame reception function should do whatever is necessary to construct an mBlk chain containing the frame to hand off to the MUX, such as assuring data coherency. This routine should also set M_MCAST and M_BCAST flags in the mBlk header if appropriate. When all is ready, your driver passes the frame up to the MUX by calling the function referenced as the receiveRtn member of the END_OBJ structure representing your device.

10.3.2   NPT Driver Interface to the MUX

This subsection describes the driver entry points and the shared data structures that comprise the NPT driver's interface to the MUX.

Data Structures Used by the Driver

The core data structure for an NPT driver is the END object, or END_OBJ. The structure is defined in target/h/end.h (see also B.3.3 END_OBJ, p.287). The driver's load function returns a pointer to an END_OBJ that it allocated and partially populated. This structure supplies the MUX with information that describes the driver as well as a pointer to a NET_FUNCS structure populated with references to the NPT's standard entry points.

Although the driver's load function is responsible for populating much of the END_OBJ structure, some of its members are set within the MUX when a protocol binds to the device. Specifically, the MUX sets the END_OBJ's receiveRtn member so that it contains a reference to the bound protocol's receive routine. The driver calls this referenced function when it needs to send a packet up to the protocol. The driver could access this function reference with a call to muxReceive( ) or muxTkReceive( ).

NPT Driver Entry Points Exported to the MUX

Table 10-3 lists the standard driver entry points that the NET_FUNCS structure exports to the MUX. In this manual, the functions use a generic "npt" prefix, but in practice this prefix is usually replaced with a driver-specific identifier, such as "ln7990" for the Lance Ethernet driver.

Table 10-3 :   NPT Driver Functions   


Function
Description

nptLoad( )
Load a device into the MUX and associate a driver with the device.
nptUnload( )
Release a device, or a port on a device, from the MUX.
nptBind( )
Exchange data with the protocol layer at bind time. (Optional)
nptSend( )
Accept data from the MUX and send it on to the physical layer.
nptMCastAddrDel( )
Remove a multicast address from those registered for the device.
nptMCastAddrGet( )
Retrieve a list of multicast addresses registered for a device.
nptMCastAddrAdd( )
Add a multicast address to the list of those registered for the device.
nptPollSend( )
Send packets in polled mode rather than interrupt-driven mode.
nptPollReceive( )
Receive frames in polled mode rather than interrupt-driven mode.
nptStart( )
Connect device interrupts and activate the interface.
nptStop( )
Stop or deactivate a network device or interface.
nptIoctl( )
Support various ioctl commands.

nptLoad( )

Before you can use a network interface to send and receive frames, you must load the appropriate device driver into the MUX and then configure that driver for the interface. The user root task loads network device drivers into the MUX by calling muxDevLoad( ) for all the drivers referenced in endDevTbl[ ]. The entries in this table provide all the information needed to call muxDevLoad( ). This includes the actual name of your driver's nptLoad( ) function.

As input, the nptLoad( ) function takes an initialization string. The contents this string are user-defined but generally include such items as the unit number identifying the physical interface10 , an interrupt vector number, and the address of memory mapped registers.

You must write your nptLoad( ) function as a two-pass algorithm. The MUX calls it twice during the load procedure. In the first pass, it calls your nptLoad( ) function using a blank (all zeros) initialization string. Your nptLoad( ) routine is expected to check for the blank string and return with the name of the device copied into the string.

The MUX then calls your nptLoad( ) a second time using the actual initialization string that was supplied to muxDevLoad( ). Your nptLoad( ) routine must then return a pointer to the END_OBJ that it allocates, or a NULL if the load fails.

Typically, the nptLoad( ) function, in its second pass, does the following:

  • Initialize the device and interface.
  • Allocate and fill the END_OBJ structure.
  • Initialize any necessary private structures.
  • Parse and process the initialization string.
  • Create and initialize a private pool of memory using the API in netBufLib.
  • Allocate one or more network buffer pools using netBufLib.
  • Create and populate the MIB II interface table.
  • Fill the NET_FUNCS table referenced by pNetFuncs in the END_OBJ structure.

The nptLoad( ) function is based on the following skeleton:

END_OBJ * nptLoad 
(
char * initString, /* defined in endTbl */
void * pBsp         /* BSP-specific information (optional) */
)
{
END_OBJ * newEndObj;

if( !initString )              /* initString is NULL, error condition */
{
/* set errno perhaps */
return( (void *) 0 );
}
else if( initString[0] == 0 ) /* initString[0] is NULL, pass one */
{
strcpy( initString, "foo" );
return( (void *) 0 );
}
else      /* initString is not NULL, pass two */
{
/* initialize device */
newEndObj = (END_OBJ *) malloc( sizeof(END_OBJ) );
/* fill newEndObj and newEndObj->pFuncTable */
/* create and populate a MIB2 interface table */
/* initialize any needed private structures */
/* parse and process initString, and pBsp if necessary */
/* create a private pool of memory using netBufLib API */
/* create network buffer pools using netBufLib API */
return( newEndObj );
}
}
nptUnload( )

The MUX calls your driver's nptUnload( ) when a system application calls muxDevUnload( ). In its nptUnload( ), your driver is responsible for doing whatever it takes to "release" the device. It should also free the memory allocated for the END object.

The MUX calls your driver's nptUnload( ) for each port that has been activated by a call to the driver's nptLoad( ). If the device has loaded multiple ports, the driver's nptLoad( ) must not free up any shared resources until an unload request has been received for each of the loaded ports.

The nptUnload( ) routine does not need to notify services about unloading the device. Before calling nptUnload( ), the MUX first sends a shutdown notice to each service attached to the device.

The nptUnload( ) prototype is:

STATUS nptUnload 
(
END_OBJ * pEND  /* END object */
)
nptBind( )

The nptBind( ) function is an optional driver function that gives your driver the ability to respond to bind events. Using nptBind( ), your driver can support the exchange of information between a service and a driver whenever the service binds to a device through that driver.

The MUX calls your driver's nptBind( ) function (if any), in response to bind events through that driver. To get an executable reference to a driver's nptBind( ) function, the MUX uses the driver's nptIoctl( ) function.

The MUX calls nptBind( ) while processing a bind request from the network service. Arguments passed to nptBind( ) include:

  • a reference to information supplied by the network service
    (although the network service may choose to supply no information at all)

  • a reference to a template for network driver information
    (if your driver cares to provide any)

  • the network service type

The function is expected to return OK if the bind request is accepted. The driver may also reject the bind request by returning ERROR, in which case the MUX will deny the bind request to the network service.

The nptBind( ) prototype is:

STATUS nptBind 
(
END_OBJ * pEND,           /* END object */
void *     pNetSvcInfo, /* info provided by the network service */
void *     pNetDrvInfo, /* template for network driver info */
long       type            /* of network service attempting to bind */
)
nptSend( )

The network driver send routine is referenced from the NET_FUNCS table. The MUX calls this function when the network service issues a send request. The send routine is supplied a reference to an mBlk chain representing the packet to be sent. This routine should prepend the link-level header to the packet (for information on making this more efficient, see 10.1.7 Early Link-Level Header Allocation in an NPT Driver, p.187).

The nptSend( ) prototype is:

STATUS nptSend 
(
END_OBJ * pEND, /* END object */
M_BLK_ID pMblk, /* network packet to transmit */
char * dstAddr, /* destination MAC address */
int netSvcType, /* network service type */
void * pSpare /* optional network service data */
)

This function should return a status OK if the send was successful, i if the send could not be completed because of a transient problem such as insufficient resources, or ERROR (in which case, errno should be set appropriately).

nptMCastAddrAdd( )

This routine registers a physical-layer multicast address with the device. As arguments, it takes:

  • a pointer to the END_OBJ structure returned by nptLoad( )

  • a string containing the physical address to be added
    (or a reference to the address to be added)

This routine should reconfigure the interface in a hardware-specific way that lets the driver receive frames from the specified address and pass those frames along.


*      
NOTE: To help you manage a list of Ethernet multicast addresses, VxWorks provides the etherMultiLib library.

The nptMCastAddrAdd( ) prototype is:

STATUS nptMCastAddrAdd 
(
END_OBJ * pEND,       /* driver's control structure */
char *     pAddress     /* physical address or a reference thereto */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

nptMCastAddrDel( )

This routine removes a previously registered multicast address from the list maintained for a device. It takes as arguments a pointer to the END_OBJ structure returned by nptLoad( ), and a string containing the physical address to be removed or a reference to that address.


*      
NOTE: To help you manage a list of Ethernet multicast addresses, VxWorks provides the etherMultiLib library.

The nptMCastAddrDel( ) prototype is:

STATUS nptMCastAddrDel 
(
END_OBJ *  pEND,        /* END object */
char * pAddress     /* physical address, or a reference thereto */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

nptMCastAddrGet( )

This routine retrieves a list of all multicast addresses that have been registered with the device. It takes as arguments a pointer to the END_OBJ structure returned by nptLoad( ), and a pointer to a MULTI_TABLE structure into which the list will be put.


*      
NOTE: To help you manage a list of Ethernet multicast addresses, VxWorks provides the etherMultiLib library.

The nptMCastAddrGet( ) prototype is:

STATUS nptMCastAddrGet 
(
END_OBJ *      pEND,       /* END object */
MULTI_TABLE * pMultiTable  /* container for address list */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

nptPollSend( )

This routine provides a polled-mode equivalent to the driver's interrupt-driven send routine. Either it must transfer a frame directly to the underlying device, or it must exit immediately if the device is busy or resources are unavailable.


*      
NOTE: When the system calls your nptPollSend( ) routine, it is probably in a mode that cannot service kernel calls. Therefore, this routine must not perform any kernel operations, such as taking a semaphore or allocating memory. Likewise, this routine should not block or delay because the entire system might halt.

Within your nptPollSend( ) routine, verify that the device has been set to polled-mode (by a previous nptIoctl( ) call). Your nptPollSend( ) routine should then put the outgoing packet directly onto the network, without queuing the packet on any output queue.

This routine takes as arguments a pointer to the END_OBJ structure returned by nptLoad( ) and a reference to an mBlk or mBlk chain containing the outgoing frame.

The nptPollSend( ) prototype is:

STATUS nptPollSend 
(
END_OBJ *  pEND,      /* END object */
M_BLK_ID pPkt,       /* network packet to transmit */
char *     dstAddr,    /* destination MAC address */
long       netType,    /* network service type */
void *     pSpareData  /* optional network service data */
)

This function should return a status OK or ERROR (in which case, errno should be set).

nptPollReceive( )

This routine receives frames using polling instead of an interrupt-driven model. The routine retrieves the frame directly from the network and copies it into the mBlk passed to the routine. If no frame is available, it returns ERROR.


*      
NOTE: When the system calls your nptPollReceive( ) routine, it is probably in a mode that cannot service kernel calls. Therefore, this routine must not perform any kernel operations, such as taking a semaphore or allocating memory. Likewise, this routine must not block or delay because the entire system might halt.

Within the nptPollReceive( ) routine, verify that the device has been set to polled-mode (by a previous nptIoctl( ) call). The routine should then retrieve the frame directly from the network and copy it into the mBlk passed in to the routine.

It takes as arguments a pointer to the END_OBJ structure returned by nptLoad( ) and a reference to an mBlk or mBlk chain into which the incoming data should be put, as well as information about the frame type and the offset within the packet to the network frame.

The nptPollReceive( ) prototype is:

STATUS nptPollReceive 
(
END_OBJ * pEND,       /* END object */
M_BLK_ID  pMblk,      /* received frame */
long *    pNetSvc,    /* payload/network frame type */
long *    pNetOffset, /* offset to network frame */
void *    pSpareData  /* optional network service data */
)

This function should return OK or an error value of EAGAIN if the received data is too large to fit in the provided mBlk, or if no data is available.

nptStart( )

The driver's nptStart( ) function connects device interrupts and makes the interface active and available. This function takes as its argument the END_OBJ structure pointer returned from the nptLoad( ) call. As with nptLoad( ), this call is made for each port that is to be activated within the driver.

The nptStart( ) prototype is:

STATUS nptStart 
(
END_OBJ * pEND, /* END object */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

nptStop( )

The driver's nptStop( ) routine halts a network device, typically by disconnecting the appropriate interrupt. It does not remove the device by releasing the allocated data structures. It takes as its argument the END_OBJ structure pointer returned from the nptLoad( ) call.

The nptStop( ) prototype is:

STATUS nptStop 
(
END_OBJ * pEND, /* END object */
)

This function should return a status OK or ERROR (in which case, errno should be set appropriately).

nptIoctl( )

An NPT driver must support the ioctl command defined as EIOCGNPT. This command is used to determine if the driver is of the NPT variety. All the driver needs to do upon receiving this command is to return 0 (zero), indicating success. For other drivers, this ioctl command is undefined and therefore returns EINVAL, indicating that it is not an NPT driver.

An NPT driver must also support the EIOCGMIB2 command, which returns a populated MIB2 interface table.

An NPT driver may also need to support other ioctl commands, particularly if it is to be used with the existing IP network service sublayer. See Table 10-4 for a list of commonly used ioctl commands.


*      
WARNING: If you are porting a driver from the BSD 4.3 model, you might be tempted to use the existing xxIoctl( ) routine as your nptIoctl( ) routine, skipping the creation of separate routines for the multicast address table maintenance functions. Do not do this! Your driver must implement the multicast address table maintenance routines.

The nptIoctl( ) function takes three arguments:

  • the END_OBJ pointer returned from the nptLoad( ) call

  • the ioctl command being issued

  • a buffer for additional data given in the command or for data to be returned on completion of the command (defined as a caddr_t structure).

Table 10-4 :   Ioctl Commands and Data Types   


Command
Function
Data Type

EIOCSFLAGS
Set device flags. See flags in B.3.3 END_OBJ, p.287.
int
EIOCGFLAGS
Get device flags.
int
EIOCSADDR
Set device address.
char *
EIOCGADDR
Get device address.
char *
EIOCMULTIADD
Add multicast address.
char *
EIOCMULTIDEL
Delete multicast address.
char *
EIOCMULTIGET
Get multicast list.
MULTI_TABLE *
EIOCPOLLSTART
Set device into polling mode.
NULL
EIOCPOLLSTOP
Set device into interrupt mode.
NULL
EIOCGFBUF
Get minimum first buffer for chaining.
int
EIOCGNPT
Indicate NPT-compliance.
void
EIOCQUERY
Retrieve the bind function.
END_QUERY *
EIOCGHDRLEN
Get the size of the datalink header.
int
EIOCGMIB2
Retrieve RFC 1213 MIB-II table.
M2_INTERFACETBL *
EIOCGMIB2233
Retrieve RFC 2233 MIB-II table
M2_ID *


*      
NOTE: Command constants (such as EIOGGFBUF) in the range of 0-128 are reserved for use by Wind River. If you want to use your own custom ioctl commands, you should define their command constants to be equivalent to numbers outside of this range.

The nptIoctl( ) prototype is:

int nptIoctl 
(
END_OBJ *  pEND,    /* END object */
int command, /* ioctl command */
caddr_t buffer /* holds response from command */
)

This function returns 0 (zero) if successful, an appropriate error value otherwise, or EINVAL if the command is not supported.

In the case where command is set to EIOCQUERY, the buffer will be set to point to an END_QUERY structure. The query member of this structure will be set to the type of query (for instance, END_BIND_QUERY), and the queryLen member will be set to the size of the queryData buffer. Upon receipt of an EIOCQUERY command, you should respond either by copying data into this queryData buffer, or by returning an error value such as EINVAL from nptIoctl( ).

Your driver is not required to support EIOCQUERY commands, but you may find this a useful way of communicating with the protocol layer.



10.4    Porting a BSD Driver to the MUX

To convert a BSD driver that communicates directly with the protocol layer into one that communicates with the protocol layer through the MUX, you need to make the following changes:

  • remove unit number references
  • create an END Object to represent the device
  • implement the standard END or NPT entry points

When deciding whether to implement an END or NPT driver, choose the interface style that is most convenient for the driver you are porting. If you are porting a frame-oriented driver, the END is likely to be the more convenient driver style.

10.4.1   Remove Unit Number References

Under the MUX, each device is independent. Your BSD model may consider each device to be part of an array of devices, each with a unit number. BSD driver routines are sometimes written to take unit numbers as parameters, and to distinguish between devices based on these unit numbers. In the MUX model, the END Object is the distinguishing feature of devices, and MUX routines distinguish between devices based on the END Object pointer that is passed in to the routines.

10.4.2   Create an END Object to Represent the Device

The head of your driver control object should be an END_OBJ structure that includes all hardware- and driver-specific elements.

10.4.3   Implementing the Standard END or NPT Entry Points

The END and NPT models for network interface drivers contain standard entry points that are not present in the BSD model. Table 10-5 shows some of the analogies. You should be able to reuse much of the code from the BSD driver.

Table 10-5 :   Required Driver Entry Points and their Derivations   


NPT or END Entry Points
BSD 4.3 Style Entry Points

xLoad( )
xxattach( )
xUnload( )
None--see endUnload( ), p.197, or nptUnload( ), p.212, and templateEnd.c.
N/A
xxReceive( )
xSend( )
xxOutput( )
xIoctl( )
xxIoctl( )
xMCastAddrAdd( )
None--see endMCastAddrAdd( ), p.198, or nptMCastAddrAdd( ), p.213, and templateEnd.c.
xMCastAddrDel( )
None--see endMCastAddrDel( ), p.198, or nptMCastAddrDel( ), p.214, and templateEnd.c.
xMCastAddrGet( )
None--see endMCastAddrGet( ), p.199, or nptMCastAddrGet( ), p.214, and templateEnd.c.
xPollSend( )
N/A--see endPollSend( ), p.199, or nptPollSend( ), p.215, and templateEnd.c.
xPollReceive( )
xStart( )
xStop( )
endAddressForm( )1
endAddrGet( )*
N/A--see also endAddrGet( ), p.202.
endPacketDataGet( )*

1:  These functions are implemented for Ethernet in endLib. If porting the BSD driver to run over Ethernet, you probably do not need to implement these functions.


*      
CAUTION: When porting a BSD network driver to the MUX, you must replace all calls into the protocol with appropriate calls into the MUX. In addition, you must remove all code that implements or uses etherInputHook( ) or etherOutputHook( ) routines.

Rewrite xxattach( ) to Use an npt/endLoad( ) Interface

Rewrite the interface of your xxattach( ) to match the npt/endLoad( ) function described in nptLoad( ), p.209, or endLoad( ), p.195.

Much of the code that handles the specifics of hardware initialization should be the same. However, when allocating the memory for packet reception buffers that are passed up to the service, you should use the MUX buffer management utilities. See 10.1.5 Managing Memory for Network Drivers and Services, p.186, A. Using netBufLib as well as the reference entry for muxBufInit( ).

Remove any code your xxattach( ) included to support the implementation of the etherInputHook( ) and etherOutputHook( ) routines. Etherhooks are no longer supported. Similar functionality is now provided using BPF (see 3.2.1 BPF, the BSD Packet Filter, p.20).

You may also need to add code that clears out the MIB2 variables in the END Object's mib2Tbl structure.

The xxReceive( ) Routine Still Handles Task-Level Packets

Because the MUX does not directly call the driver's packet reception code, there is no npt/endReceive( ) entry point. However, your driver still needs to handle packet reception at the task level. Unfortunately, most of the code in this driver routine will require extensive revision. Instead of calling the service directly, this routine uses a MUX-supplied function to pass a packet up to the service. Likewise, your receive routine should use a MUX-managed memory pool as its receive buffer area.

Rewrite xxOutput( ) to Use an npt/endSend( ) Interface

Rewrite the interface of your output routine to match the npt/endSend( ) entry point described in nptSend( ), p.213 or endSend( ), p.197.

Much of the code that dealt directly with putting the packet on the hardware should need little if any revision. However, you should change your code to use mBlk chains allocated out of an netBufLib-managed memory pool. See the reference entry for netBufLib for details.

The xxIoctl( ) Routine is the Basis of npt/endIoctl( )

Rewrite the interface of your xxIoctl( ) to match the npt/endIoctl( ) function described in nptIoctl( ), p.217 or endIoctl( ), p.203. If your driver used xxIoctl( ) to implement multicasting, you must break that functionality out into the separate npt/endMCastAddrAdd( ), npt/endMCastAddrDel( ), and npt/endMCastAddrGet( ) entry points.

Implement All Remaining Required END or NPT Entry Points

Table 10-5 lists a handful of driver points unique to ENDs and NPT drivers. Both an END and an NPT require you to implement the xSend( ), xStart( ), and xStop( ) entry points. There are no BSD equivalents for these entry points. In addition, if you are implementing an END, you must implement entry points for endAddressForm( ), endAddrGet( ), and endPacketDataGet( ). However, these functions are already implemented for Ethernet in endLib. If your driver will run over Ethernet, you may use the functions supplied in endLib.



10.5    Supporting Multiple Network Interface Drivers

The VxWorks network stack allows you to use multiple network interface cards simultaneously. You can use multiple cards of the same variety, or different types of cards, with a combination of END and NPT drivers.11

10.5.1   Configuring VxWorks for Multiple Drivers

To configure VxWorks to support multiple drivers, make sure that the drivers are compiled into your VxWorks image. Follow the directions in Adding an NPT Driver to VxWorks, p.206 (for an NPT driver) or Adding an END to VxWorks, p.191 (for an END). You may also need to increase the value of the configuration parameters IP_MAX_UNITS and MUX_MAX_BINDS.

10.5.2   Starting Additional Drivers at Run-Time

To start additional network drivers at run-time:

  1. Use muxAddrResFuncAdd( ) to install an address resolution function if necessary. If your driver does not register itself as an Ethernet driver, and if the link layer requires hardware address resolution, you need to install an address resolution function. See B.2.1 muxAddrResFuncAdd( ), p.272.
  1. Use muxBind( ) or muxTkBind( ) to bind the driver to the service. In the case of IP, the binding is done in the ipAttach( ) routine.
  1. Configure the interface. In the case of IP, this is done with calls to ifMaskSet( ) and ifAddrSet( ).


10.6    Avoiding Memory Leaks

Your driver implementation may allocate a semaphore during its initialization phase and store a reference to it in the END object's txSem member. For example, endObjInit( ), which is commonly used to initialize ENDs, does this.12

If this semaphore is not deleted when the driver is unloaded, a memory leak will result equal to the size of the semaphore data structure plus any memory allocation overhead.

This may not be an issue for your application, since the amount of memory that leaks is small, but if drivers are loaded and unloaded frequently, this could add up and become a problem.


1:  The mux[Tk]Receive( ) calls in the shipped ENDs and the template END are hard to identify as such when casually reading the code. When passing a packet up to the MUX, each of these drivers uses the function pointer referenced in the receiveRtn member of its END_OBJ. An earlier call to muxDevLoad( ) set the receiveRtn member to muxReceive( ) or muxTkReceive( ), whichever was appropriate.

2:  Although the API for both the END and the NPT xIoctl( ) are identical, the NPT xIoctl( ) must support two extra ioctl commands, EIOCGNPT and EIOCGMIB2233.

3:  The xBind( ) entry point was not a part of the original END design. It was added with the NPT enhancements, but the MUX supports its use in an END.

4:  For Ethernet, these functions are implemented in endLib. Thus, if your driver runs over Ethernet (using either 802.3 or DIX header formats), you can reference the existing functions and do not need to implement them.

5:  If the buffer contains a four-byte region that you access as an array of 8-bit or 16-bit values, there is no alignment restriction. The restriction applies only when you access long words.

6:  Do not confuse END entry points, indicated as endLoad( ), endStart( ), and so on, with functions in endLib, an END support library that defines functions such as endTxSemTake( ) and endTxSemGive( ).

7:  You cannot call netJobAdd( ) from outside of the kernel protection domain.

8:  Although a driver is only loaded once, the driver's endLoad( ) routine will be called for each port to be activated within the driver. This is so the MUX may allocate an entry for each port. The MUX interface does not impose restrictions on how drivers handle multiple ports as long as a separate END_OBJ is allocated for each port.

9:  You cannot call netJobAdd( ) from outside of the kernel protection domain.

10:  Although a driver is only loaded once, the driver's nptLoad( ) routine is called for each port to be activated within the driver. This lets the MUX allocate an entry for each port. The MUX interface does not impose restrictions on how drivers handle multiple ports as long as the driver allocates a separate END_OBJ for each port.

11:  Some BSPs and drivers may have their own limitations on the number of interfaces and units they support.

12:  Please note that endObjInit( ) is an actual function defined in endLib.c. It is not an END entry point, which would have been indicated as endObjInit( ).