2.3   Writing a New WDB Back End

The back ends provided by Wind River Systems support a variety of communication alternatives, but not all cases are supported. Customers interested in using an unsupported communication link must write a new back end.

2.3.1   Overview of Writing a WDB Back End

Developing a back end to communicate with the WDB agent is simple: because all the services are already implemented in the WDB agent, the new back end only needs to transport WDB RPC messages.

The key development steps are:

2.3.2   WDB Protocol

This section describes the Wind DeBug (WDB) version 1.0 protocol. It is the protocol used by the Tornado target server back ends that communicate with the WDB agent (WDB RPC, WDB Serial, and NetROM). See Figure 2-1 for a diagram of how these back ends connect to the WDB agent.


*

NOTE: This section is provided for completeness only. If you want to create a new back end for Tornado, knowledge of the WDB protocol is not needed because the support libraries handle all aspects of the protocol not related to transport. In fact, in order to assure that your back end is not affected by changes in WDB, you should gain access to WDB facilities through the support libraries as shown in the example back ends. To create a non-WDB back end that communicates with a device such as an ICE or a ROM monitor, you also need to implement all the target services required. For details, refer to 2.4 Writing a Non-WDB Back End.

Protocol Overview

WDB is an RPC-based protocol which uses UDP. The WDB agent acts as a server for requests sent by the target server. For more information, see one of the many generally available references on UDP and Sun RPC.

See installDir/share/src/agents/wdb/wdbP.h for some key definitions:

Requests Sent to the Agent

A WDB request packet sent to the agent contains the following parts, as shown in Figure 2-3:  

  

The WDB parameter wrapper contains three 4-byte words. The first word is a checksum over the whole RPC packet (RPC header plus XDR stream). The second word is the packet size. These two words enable the agent to determine if a corrupted packet has arrived. The third word is a sequence number. The low order two bytes of the sequence number are used to allow the agent to ignore old or duplicated requests (which can occur with UDP transport). The high order two bytes are the host ID. When a host issues a WDB_TARGET_CONNECT request, the host ID portion of the sequence number is recorded. If a request arrives from a non-connected host, the RPC fails with the RPC error status PROG_UNAVAIL or SYSTEM_ERR, depending on whether the agent is already connected to another target server or not. The WDB_TARGET_CONNECT request always establishes a new connection, if necessary by dropping the old one. If the host wants to test whether or not the agent is already connected before trying to establish a connection, it should first issue a WDB_TARGET_PING request and see if the RPC fails with error status PROG_UNAVAIL. If so the connection is busy.

The routine xdr_WDB_PARAM_WRAPPER is used to encode or decode the entire XDR stream (the procedure parameters plus the 12-byte parameter wrapper). The following example is a code stub from the host routine rpcCoreClntCall( ):

seqNumber++ ; 
... 
wdbParamWrapper.xdr = inProc;         /* xdr func for proc params */ 
wdbParamWrapper.pParams = in;         /* pointer to proc params */ 
wdbParamWrapper.seqNum = processId | seqNumber;     /* seq nb */ 
... 
clntStatus = clnt_call (pWdbClnt, procNum, xdr_WDB_PARAM_WRAPPER, 
&wdbParamWrapper,...);

Replies Sent by the Agent

A WDB reply packet sent by the agent contains the following parts, as shown in Figure 2-4:

Like the WDB parameter wrapper, the WDB reply wrapper contains three 4-byte words. The first word is a checksum over the whole RPC packet (RPC header plus XDR stream). The second word is the packet size. These two words enable the host to determine if a corrupted reply was returned (and, if so, to reissue the request). The third word is the WDB error status. The high order bit is set if there are events pending on the target, in which case the host can issue a WDB_EVENT_GET request to upload the event. The rest of the word is the actual error status.       

Reply Errors

After a remote procedure is called, the program should perform error checking. Error status can be communicated in one of two ways: in the RPC header or in the reply wrapper. If the failure is reported in the third word of the RPC header, then the host's clnt_call returns an RPC error status. These have conventional meanings according to the RPC specification. In addition, the WDB agent uses a couple of special codes:

RPC_PROCUNAVAIL
The RPC procedure requested is not configured into the agent. The agent is scalable, so this usually means the agent was built without that service.

RPC_PROGUNAVAIL
The agent refused to execute the procedure because it is connected to another host.

RPC_SYSTEMERROR
The agent refused to execute the procedure because it is not connected to any host. If you were previously connected, the target has rebooted.

Even if the RPC call succeeds (meaning that the agent tries to execute your command), the command may still fail. The errCode field in the reply wrapper can be checked; if the lower 31 bits are zero, the command succeeded. (Remember that the high order bit is set if there are events pending on the target.) If the value is non-zero, then the procedure failed and the word contains the error code. Error codes have the format WDB_ERR_XXX. The error-code definitions are located in installDir/share/src/agents/wdb/wdb.h.

The routine xdr_WDB_REPLY_WRAPPER is used to encode or decode the entire XDR stream (the reply data plus the 12-byte reply wrapper). The following pseudo code shows how it works:

wdbReplyWrapper.xdr            = outProc;                            /* reply xdr function               */ 
wdbReplyWrapper.pReply      = out;                                    /* where to decode reply       */ 
wdbReplyWrapper.errCode    = OK;                                     /* just to clear this field         */ 
... 
clntStatus = clnt_call (pWdbClnt, procNum, xdr_WDB_PARAM_WRAPPER, 
&wdbParamWrapper, xdr_WDB_REPLY_WRAPPER, 
&wdbReplyWrapper, timeout); 
check (clntStatus) 
    { 
    if (RPC_TIMEDOUT or RPC_CANTDECODERES or RPC_CANTDECODEARGS) 
        try again 
    if (RPC_SYSTEMERROR) 
        if we were previously connected, then target must have rebooted so resync 
            and reconnect. 
    if (RPC_PROCUNAVAIL) 
        procedure not configured into agent. Try to rebuild the agent with that 
            facility included (e.g., virtual I/O is an optional agent facility). 
    if (RPC_SUCCESS) 
        agent tried to execute the routine.  
        check high order bit of wdbReplyWrapper.errCode to see if events are  
            pending on the target. If so, execute a WDB_EVENT_GET request after 
            finishing processing this reply.  
        mask off the high order bit of wdbReplyWrapper.errCode. 
        if (wdbReplyWrapper.errCode == 0) 
            success! In this case wdbReplyWrapper.xdr decoded the reply and put 
                it in wdbReplyWrapper.pReply. 
        else 
            wdbReplyWrapper.errCode contains the reason for procedure failure. 
            The error codes are defined in wdb.h. 
    }

Asynchronous Notification Sent By the Agent

Asynchronous events can be generated on the target. These include exceptions, breakpoints, and task exiting. These events are queued on the target until the host uploads them with the WDB_EVENT_GET service. In order to prevent the host from polling for events, the agent has two ways to notify the host that events are pending: (1) by setting the high order bit in the errCode status of the reply wrapper; (2) by sending a notify packet.

Normally the agent only sends data to the host in response to RPC requests. The convention is that if the host receives data when it is not waiting for a reply, it means that an event is pending on the target. This allows the target server to select( )on file descriptors associated with the host tools which are connected as well as with the target. If the target file descriptor becomes active, the host issues a WDB_EVENT_GET request to upload the event (and keeps uploading events until the high order bit in the errCode field is clear). The actual notify packet sent by the agent is a packet that can not be confused with an RPC reply (in case it sends the notify packet just as the host issues an RPC request). In fact, it sends a bogus RPC request.

2.3.3   Host-Side Code

To provide the necessary host-side support for a new communication pathway, you must write a new back-end DLL to transport WDB protocol messages. Fortunately, most of the back-end code is generic RPC1 code to transport WDB protocol messages; thus you can reuse Wind River's rpccore library2 . Consequently, you can only need to write the back end's initialization code and, if necessary, the client-side RPC implementation.

A WDB back end consists of three parts: (1) the initialization routine, which initializes the back end; (2) the RPC core which manages WDB RPC requests, including XDR; and (3) the client-side RPC implementation, which sends and receives the RPC messages over the network medium. (See Figure 2-5.)  

   

The bkendInit( ) routine is the back-end DLL's entry point and must initialize the back end. To do this, it performs the following services:

  1. It creates the CLIENT data structure, which the RPC core uses to communicate with the WDB agent. The CLIENT structure is a standard RPC data structure that contains information needed by the RPC core, such as pointers to buffers and to the functions for sending and receiving data. It is important to create a CLIENT structure appropriate to the network medium your back end is using: for example, Wind River's wdbrpc back end calls clnt_udpcreate( ) so that the back end can send UDP messages over the network; the wdbserial back end calls clnt_ttycreate ( )so that the back end can communicate over a serial device.

  1. It initializes the TGT_LINK_DESC information in the TGT_OPS structure.

  1. It calls rpcCoreInit( ) to handle the rest of the back-end initialization. This routine is part of the installDir/host/src/tgtsvr/backend/share/rpccore.c support library, which handles all the generic parts of the protocol including:

  • translating back-end requests into WDB-agent requests, including XDR encoding and decoding

  • packet sequencing and checksum

  • timeout and retransmission

  • asynchronous event notification

  • back-end logging

In the following sections, we examine the wdbserial back end to demonstrate how to write a new WDB back end. The wdbserial back end consists of two main modules, the wdbserial.c module, which implements the back-end initialization routine, and clnt_tty.c, which provides the client-side RPC implementation for a serial device (see Client-Side RPC Implementation).

Back-End Initialization: wdbserial.c

The wdbserial.c module consists of one routine, wdbserialInit( ). The target server invokes wdbserialInit( )after it loads the back end in order to initialize it. The wdbserialInit( ) call creates an RPC CLIENT structure for communicating over a serial device and establishes a link, initializes the generic RPC core library to operate the target agent using the WDB (Wind DeBug) protocol, and initializes a data structure describing the back-end link with the agent. The wdbserial.c code shows how these steps are carried out.

/* wdbserial.c - Remote Procedure Call (RPC) backend library */

After the usual preamble of comments, copyright notice, and inclusion of system header files, wdbserial.c includes the Tornado host header files from the installDir/host/include directory:

... 
/* includes */ 
 
/* system header files go here */ 
...  
 
#include "tgtlib.h" 
#include "tgtsvr.h" 
#include "tssvcmgt.h" 
#include "host.h" 
#include "wdb.h" 
#include "wdbP.h" 
#include "wpwrutil.h" 
#include "bkendlib.h" 
#include "bkendlog.h"

Windows `95 and NT hosts must include backend.h by means of the following #ifdef:

#ifdef WIN32 
#include "backend.h" 
extern int dbg_on; 
#endif /* WIN32 */

Next, wdbserial.c imports the prototypes to rpcCoreInit( ), which initializes the RPC core library, and clnttty_creat ( ), which creates an RPC connection over a serial link.

extern STATUS rpcCoreInit (CLIENT *, u_int, u_int, TGT_OPS *); 
extern CLIENT * clnttty_create  (char *, int, u_long, u_long,  
                                 struct timeval);

When the target server calls wdbserialInitialize( ), it passes pointers to the TGT_OPS and BKEND_INFO structures. (For more information on these structures, see The TGT_OPS Structure and The BKEND_INFO Structure.)

STATUS wdbserialInitialize 
    ( 
    char *       tgtName,    /* target name to connect to (unused) */ 
    TGT_OPS *    pTgtOps     /* back-end function */ 
    BKEND_INFO * pBkendInfo  /* Backend notification method */ 
    )

Windows hosts optionally enable logging of debugging messages from the lower-level serial support:

#ifdef WIN32 
    dbg_on = GetDebugFlag(); 
#endif /* WIN32 */

Next, the back end must enable RPC communications over a serial link by creating the CLIENT data structure. If your back end uses an unsupported link type you will need to implement a clntXXX_create( ) call for your communication medium. Wind River implemented clnttty_create( ) for the wdbserial back end.

The back end initializes the RPC client-side transport to the WDB agent: first, struct timeval is initialized with the user's specified timeout; then clnttty_create( ) is called to create the RPC link to the target. Note that the clntty_create( ) call is retried until it succeeds or exceeds the user-specified number of retries.

/* set the connection timeout to the current value */ 
 
tv.tv_sec  = timeout; 
tv.tv_usec = 0; 
... 
resendCnt = recallNum; 
 
do 
{ 
    /* create the backend client and connect the target deamon */ 
 
pClnt = clnttty_create (pTtyDevName, baudRate, WDBPROG, WDBVERS, tv); 
 
} 
while ((pClnt == NULL) && (--resendCnt > 0)); 
...

If the RPC client-side initialization fails, the back end sets errno to the appropriate error number, logs an error message, and returns ERROR to indicate that the called routine failed.

if (pClnt == NULL) 
{ 
... 
errno = WTX_ERR_SVR_INVALID_DEVICE; 
 
WPWR_LOG_ERR ("%s\n",  
            clnt_spcreateerror ("wdbserial backend client create")); 
return (ERROR); 
}

If the RPC client initialization succeeds, the back end calls rpcCoreInit( ) to initialize the rpccore library for use over the serial device. The rpccore library provides all the support necessary to operate the WDB target agent in response to target server requests. In particular, rpcCoreInit( ) initializes the TGT_OPS structure, specifying that the target server should call rpccore routines to perform the various back-end operations.

rpcCoreInit (pClnt, timeout, recallNum, pTgtOps); 
...

Finally, the back end describes the target link type. If your back end uses a new link type, define a TGT_LINK_XXX macro in installDir/host/h/tgtlib.h.

pTgtOps->tgtLink.name = "WDB Agent across serial line"; 
pTgtOps->tgtLink.type = TGT_LINK_SERIAL_RPC; 
pTgtOps->tgtLink.speed = baudRate; 
 
return (OK); 
}

Client-Side RPC Implementation

If your back end must communicate over an unsupported network medium, you must provide a client-side RPC implementation. As a starting point, you should use the clnt_udp.c which is part of Sun Microsystem's public domain RPC distribution and is provided in installDir/target/unsupported/rpc4.0/rpc.

Your implementation must provide the following services:


*

NOTE: The client-side RPC implementation must transmit datagrams in a UDP-like manner. In other words, the client-side RPC must reliably transmit an entire datagram. If data is lost, the back end can drop the datagram and RPC repeats the request. Consequently, if the network medium is character-oriented, like a serial device, the back end must packetize datagrams on both the host and target sides (see 2.3.4 Target-Side Code). One way of doing this is to use the SLIP protocol, as Wind River does in the wdbserial back end.

Building a Back End for UNIX Hosts

By including the generic makefile templates provided with Tornado, it is easy to write a makefile for a back end.

# Makefile - for WDB serial backend

After the modification history, specify the suffixes of the shared library that will be built:

.SUFFIXES:   .so .sl

Next, include the makefile templates provided by the Tornado development environment. These fragments make it easy to build a portable makefile.

include      $(WIND_BASE)/host/include/make/generic.mh 
include      $(WIND_BASE)/host/include/make/$(HOST).mh

Set the INCLUDES macro to specify that the compiler can find header files in the installDir/host/include and the installDir/share/src/agents/wdb directories. Specify any other directories that the compiler needs to search to find your back-end header files:

INCLUDES = $(WIND_INC) $(WIND_SHARE_INC)

Specify which modules should be linked to create the back end. List both the back end modules and the rpccore modules (the modules in ../share):

BKEND_OBJS = clnt_tty.o wdbserial.o  
 
BKEND_XDR_OBJS = ../share/ctx.o ../share/ctxcreat.o \ 
                ../share/ctxstep.o ../share/evtdata.o \ 
                ../share/evtpoint.o ../share/memory.o \ 
                ../share/regs.o ../share/rpccksum.o \ 
                ../share/rpccore.o ../share/tgtinfo.o \ 
                ../share/wrapper.o ../share/xdrcore.o

State the name of the back end:

SH_BKEND_OBJS = wdbserial.$(SHLIB_EXT)

Next, specify any extra compiler flags you need:

LOCAL_CFLAGS = -DPORTABLE -DHOST $(DYN_LK_FLAGS) 
... 
default: lib $(SH_BKEND_OBJS)

Finally, an inference rule states that the back end is built from C source modules that are linked into the shared library. If you need to link the back end with other libraries or are using C++, you must modify this rule.

.c.$(SHLIB_EXT):$(BKEND_OBJS) $(BKEND_XDR_OBJS) 
        $(SHARED_LD) $(SHARED_LDFLAGS) -o $(SH_BKEND_LIB)/$*.$(SHLIB_EXT) \ 
         $(BKEND_OBJS) $(BKEND_XDR_OBJS) 
... 
include     $(WIND_BASE)/host/include/make/generic2.mh

Building a Back End for Windows Hosts

Using Visual C++ 5.0

To build a back end for Windows hosts, you need to create a project for the back end using Microsoft's Visual C++. Because the target server was built with Visual C++ 5.0, we recommend that developers also use Visual C++ 5.0 to avoid incompatibilities between different versions of the standard libraries. Remember to address the following build issues:

  • Define the macros HOST and WIN32 so that Wind River header files have the proper configuration for the Windows host.

  • Add installDir/host/include and installDir/share/src/agents/wdb to the list of directories to search for include files.

  • Link with:

  • wsock32.lib to provide the necessary socket support for RPC.

  • installDir/host/x86-win32/lib/backend.lib to pull in back-end support facilities.

2.3.4   Target-Side Code

The WDB target agent needs a means to send and receive UDP/IP datagrams over the physical network connection. There are two protocol stacks that can be used, the full VxWorks network protocol stack and a lightweight UDP/IP interpreter. The full network protocol stack provides a rich set of functionality, while the lightweight UDP/IP interpreter requires much less target memory. The choice of protocol stacks affects the type of driver that must be written for the physical device. 

  

Writing a Network Interface Driver

Drivers that interface with the VxWorks TCP/IP network stack are called network interface drivers. Details of how to write a network interface driver are covered in The BSP Porting Kit (an optional product). The advantage of creating such a driver is that, in addition to being used as a debug communication path, it can also be used for application network communication. To use a network interface driver, configure the target agent for network communication (the default configuration).

Writing a WDB Packet Driver

Drivers that interface with the target agent's UDP/IP interpreter are called WDB packet drivers. Such drivers have the advantage that they do not require the TCP/IP stack to be present on the target. This can save space on resource-constrained targets. The agent's UDP/IP interpreter has the advantage of small size (only 800 bytes) but it also has limited functionality.

To create a WDB packet driver, start with the template driver in installDir/target/src/drv/wdb/wdbTemplatePktDrv.c. Use of the template is documented in the source file.

You must also modify the agent's startup code to initialize the new communication pathway. The target agent configuration code is provided in installDir/target/src/config/usrWdb.c. To initialize your custom packet driver, add initialization code to usrWdb.c similar to the section bracketed with
#if  (WDB_COMM_TYPE == WDB_COMM_TYPE_CUSTOM).

Build Issues

To build the packet driver, copy it into your BSP directory and use the standard techniques described in the Tornado User's Guide: Projects or the VxWorks Programmer's Guide: Configuration and Build. The makefile templates provided in installDir/target/h/make assist in developing a portable makefile. Modify the makefile in your BSP directory so that these modules are built and linked into your VxWorks image.

2.3.5   Testing

Use the wtxtest test suite to verify the new back-end connection. This facility exercises all back-end functions over your new link. For more information about wtxtest, see the online reference material under Tornado Reference>Tornado Tools.


*

NOTE: wtxtest is available in the Tornado distribution but it is not installed by default. If you want to use it, you must install it using SETUP. In the Select Products dialog box, highlight the appropriate Tornado object and click Details. Check Tornado WTX Test Suite in the Select Parts dialog box and complete your installation.


1:  For more information on RPC, please see the O'Reilley book, Power Programming with RPC.

2:  The rpccore library can be obtained when you request the Back-End Developer's Kit.