2

Target Server Back End



2.1    Introduction

The target server acts as a broker for the communication path to the target and provides services commonly required by the Tornado tools. In order to support new CPU architectures, new object-module formats, and new communications back ends with minimum programming effort, the corresponding parts of the target server are structured as shared libraries on UNIX or dynamically linked libraries (DLLs) on Windows.1 The remainder of the target server, known as the target server core, is independent of the target architecture, the target operating system, and of the communications transport layer.

This chapter examines the communications back end. It shows how a back end fits into the architecture of the target server, how the back end is implemented in Tornado, and how to implement new back ends, either for the WDB agent or for non-WDB agents such as emulators. For an overview of the target server and its components, see 1.3 The Target Server and the WTX Protocol. To add a new object-module-format loader, see 3. Object-Module Loader. This chapter gives an overall orientation; for details, see the online reference material under Tornado API Reference>Target Server Back End Interface.

2.1.1   Target Server Overview

A target server session typically involves the following activities:

Initializing the Target Server
  1. The target server is invoked with the following syntax:
UNIX:
% tgtsvr -V -B mybkend ... &
Windows:
c:> tgtsvr -V -B mybkend ...

where -V enables verbose diagnostic messages and -B specifies the back end.

  1. The target server loads the back-end DLL specified with the -B parameter. (In the example, the back end is named mybkend; in the discussion, replace mybkend with the name of the back end you are developing.) The target server asks the back end if it can be customized through special command-line arguments.
  1. If so, the target server parses its command-line argument and feeds the information to the back end.
  1. Next, the target server registers itself with the registry, wtxregd, so that tools can connect to it.
  1. The target server initializes the back end by calling a function named mybkendInitialize( ) in the DLL. Every back end must provide an initialization routine which has the name of the back end followed by Initialize. The routine mybkendInitialize( ) initializes the back end, fills in a TGT_OPS structure with the address of each back end function, and a BKEND_INFO structure with the method chosen to alert the target server when events come to the back end. The TGT_OPS structure is defined in installDir/host/include/host.h and the BKEND_INFO structure is defined in installDir/host/include/bkendlib.h.
  1. The target server connects the back end to the target by calling the back-end target-connect function.
  1. Finally, the target server performs other initialization, such as loading the OMF reader and reading the symbols in the OS core file.
Servicing Tool Requests
  1. The target server launches a thread to wait for WTX protocol requests sent by tools and another to wait for target events. Whenever a target event occurs, the back end notifies the target server using the method specified in BKEND_INFO. The target server then queries the back end for information about the event and notifies the tools that have registered to receive notification of the event.
  1. If a tool makes a request, the target server either satisfies the request itself (for example, by looking up a symbol in the host-resident symbol table) or forwards the request to the back end (for example, by requesting that the back end set a breakpoint or read memory).
Shutting Down the Target Server
  1. When the target server exits, it calls the back-end disconnect routine to clean up all resources that have been allocated by the back end.
  1. Next, the target server disconnects all tools and removes its name from the Tornado registry (wtxregd).
  1. Finally, the target server exits.

2.1.2   Back-End Overview

The portion of the Tornado target server that communicates with the target is called the back end. Many back ends are supported, each dedicated to an alternative host-target connection. All back ends share a single purpose: to provide a set of primitive services for the target server core. Depending on the back end, executing these routines can be as simple as forwarding a request to the target agent (the WDB RPC back end) or as complex as translating the request to an entirely different protocol (an emulator back end).

WDB Back Ends

Some back ends (WDB back ends) are designed to communicate with the target agent supplied with VxWorks, which is called the WDB agent. (See Figure 2-1.) The WDB agent uses the Wind DeBug (WDB) protocol to communicate between the host and target operating system. The key advantage of this protocol, from a programming perspective, is that it corresponds one-to-one with the target server back-end API. There is a back-end function corresponding to each WDB protocol request. When using the WDB agent, the back end manages the mechanics of transmitting the request to the target over whatever hardware medium provides host-target communication. Creating a new back end is a matter of writing the necessary host and target code to transport WDB protocol messages.2  

Figure 2-1:   WDB Back End

  

Non-WDB Back Ends

Other back ends (non-WDB back ends) are designed to communicate with alternative agents, such as an emulator that serves as a hardware debug agent. (See Figure 2-2). If a back end does not use the WDB agent, the back end itself must provide the requested service. The underlying service could be provided by another software agent (for example, a ROM monitor) or by a hardware agent (for example, an in-circuit emulator or ICE). Virtually any cross-development framework can be supported in a back end because the back-end API is general. It is also possible, if necessary, to return a "service not available" error or to fill in the TGT_OPS table with a NULL pointer if it is impossible to implement the service as requested by the target server. 

Figure 2-2:   Non-WDB Back End

  

This chapter provides background information on the structure of a target server back end, followed by descriptions of how to write new back ends for the WDB agent and for a non-WDB agent. Writing a new back end allows you to use a different communication protocol or to use a new method for providing services to the Tornado tools; none of the tools needs to be modified because the tools communicate only with the target server. Our discussion provides an overall orientation; throughout this chapter, refer to the reference entries in Part 2 for details.



2.2    Back-End Implementation

This section discusses several aspects of back-end implementation that are similar for both WDB and non-WDB back ends.

  • It provides a description of the FLAG_DESC data structure and the bkendFlagsGet( ) routine which is used to retrieve those flags so they can be sent to the back end.

  • It also provides a prototype of the back-end initialization routine, with an explanation of the parameters passed to the back end by the target server and a description of the TGT_OPS and BKEND_INFO data structures (filled by the initialization routine).

  • This section also covers event notification requirements and methods, as well as issues relating to target-board rebooting.

  • In addition, message logging facilities provided for WDB back ends are discussed. These facilities may be of interest to writers of non-WDB back ends as a source of sample target-server target-agent interactions.

2.2.1   Attachment and Initialization

A target server can only use one back end at a time. The back end is attached when the target server is started. The -B option specifies which back end to attach. For example, to use the back end named wdbserial, select wdbserial in the target server initialization window, which invokes the target server with the following command:

% tgtsvr ... -B wdbserial ...

Back ends are linked with the target server dynamically using DLLs. At run time, the target server searches installDir/host/hostType/lib/backend, which contains a DLL for each available back end, and loads the specified back end.3 The back end can have any name, but we recommend you choose names that are short, lower case, and suggest the connection strategy to which they refer. If the requested back end is not found, the attachment fails and the target server abandons its start-up sequence, exiting with an error condition.

Once the correct DLL is found, the target server asks if the back end handles special flags with the bkendFlagsGet( ) routine. If the back end exports this routine, the target server calls the routine and appends the returned flags array to its own array. When it finishes parsing the command line, the target server calls an initialization routine that attaches the back end. Besides initializing the back end, this routine fills in a table of function pointers (the TGT_OPS structure) used by the target server to call the back-end functions and a structure (the BKEND_INFO structure) which describes how the back end notifies the target server of asynchronous events. Finally, the initialization routine calls bkendTgtConnect( ) to attach the target server to a specified target.


*      
CAUTION: The target server symbol table is not available until you attach the target.

The Flag Parsing Routine

A back end can export a flag-parsing routine to handle any back-end specific command line parameters. if the loaded back end exports such a routine, the target server runs the routine to get a list of the parameters the back end needs to access. The parameters are contained in the FLAG_DESC structure (defined in installDir/host/include/flagutil.h). The target server adds the flags to its own array of recognized flags and parses its entire command line.


*      
CAUTION: The target server overrides its own flag description if a back end defines the same flag. Back ends should use flags that are different from the target server core flags.

The name of the flag-parsing routine is standardized so that the target server can find it. It is composed of the back-end file name (without extension) plus the word FlagsGet. For our example back end, called mybkend, the flag-parsing routine is mybkendFlagsGet( ), which is defined in mybkend.c.

The back-end flag-parsing routine must match the following prototype:

FLAG_DESC * mybkendFlagsGet (void)

The returned value is a null-terminated FLAG_DESC structure array. This structure describes the arguments handled by the back end and is composed of five fields:

typedef struct flag_desc /* target server flag descriptions */ 
    { 
    char *     flagName,         /* verbose flag name */ 
    char *     flagTerseName,    /* abbreviated name of flag (or NULL) */ 
    PARSE_RTN  parseRoutine;     /* flag processing routine */ 
    int        outputPtr;        /* where to store the output result */ 
    char *     flagHelp;         /* flag help string */ 
    } FLAG_DESC;

When the target server parses its command line, it tries to match the current argument to the flagname or flagtersename field of each FLAG_DESC structure array element. If an element matches the command line argument, the target server executes the parseRoutine field of the matching element, giving three arguments: the remaining arguments number, the command line argument vector starting with the argument which caused the call to the routine, and the value stored in the outputPtr field. The provided routine interprets as many arguments as necessary, fills the provided location with the parsed value(s), and returns the number of parsed command line arguments. The parsing routine must match the following prototype:

/* flag processing routine definition */ 
 
typedef int (*PARSE_RTN) (int argc, char **argv, void * outputPtr)

Store your flagInt( ) routine in the libwpwr shared library and declared it in installDir/host/include/flagutil.h.


*      
CAUTION: The target server drops the number of arguments returned by the parseRoutine routine. So, if an error occurs during the routine parsing phase, the routine should return 0, so the target server can resynchronize itself on the next command line argument.

Example 2-1:   Flag-parsing Routine

Suppose you want to define a back end which can take a serial line speed argument. This argument will be coded with -speed value on the command line. Your code includes declarations and mybkendFlagsGet( ) as follows:

static int mySpeed = 9600;    /* mybackend default speed */ 
static FLAG_DESC mybkendFlags/* my flags descriptions */ 
    { 
        {"-speed", "-sp", flagInt, (void *) &mySpeed,  
        "-sp[eed] myBkend Speed definition (default 9600)."}, 
        {NULL, NULL, NULL, 0, NULL} /* End Of Record delimiter */ 
    }; 
 
... 
 
FLAG_DESC *  mybkendFlagsGet (void) 
    { 
    return (FLAG_DESC *) mybkendFlags; 
    }

Now, suppose you launch the target server with the following command line:

tgtsvr -B mybackend -speed 19200 targetName

When the target server loads mybkend, it calls mybkendFlagsGet( ), appends the mybkendFlags array to its own FLAG_DESC array, and parses the command line. When it parses the -speed argument, it calls flagInt( ) with the first argument set to 3, the second argument pointing to the array "-speed 19200 targetName", and the last argument set to &mySpeed.

This routine interprets the 19200 string as an integer and stores that value at the &mySpeed location. Thus, the back end speed is modified by the command line parameter.

The Initialization Routine

When the target server attaches a back end, it calls the initialization routine of the named back end. The main purpose of this routine is to fill in the TGT_OPS structure defined in installDir/host/include/tgtlib.h, and the BKEND_INFO structure defined in installDir/host/include/bkendlib.h. It also performs whatever back-end initialization is appropriate.

The TGT_OPS structure, whose location is passed as an argument to the back-end initialization routine, is a collection of function pointers. The initialization routine fills this structure with pointers to functions that provide the back-end services. The implementation of the services is entirely back-end dependent. By virtue of the coupling, it is possible to support many back ends, each providing the same service in its own way. Once the TGT_OPS structure is filled in and returned, the target server is ready to start communicating with the target.

The BKEND_INFO structure, whose location is also passed as an argument to the back-end initialization routine, contains information about the newly loaded back end. This information consists of the back-end version and the asynchronous events notification. Once the BKEND_INFO structure is filled, the target server launches a thread which handles the asynchronous events according to the given method.

The name of the initialization routine is standardized so that the target server can find and attach the back end chosen by the user. It is composed of the back-end file name (without extension) plus the word Initialize. For our example back end, called mybkend, the initialization routine is mybkendInitialize( ), which is defined in mybkend.c. This name indicates the DLL that the target server loads as well as the back-end initialization routine to call. The back-end name is passed to the target server by the -B option when the target server is started.

The back-end initialization routine must match the following prototype:

STATUS mybkendInitialize 
    ( 
    char *       tgtName,    /* network unique target name to connect */ 
    TGT_OPS *    pTgtOps     /* vector of back end functions to fill */ 
    BKEND_INFO * pBkendInfo  /* Backend notification method */ 
    )

The first parameter, tgtName, passed to the back-end initialization routine from the target server specifies the target name. The meaning of the parameter is completely back-end dependent. For example, the wdbrpc back end uses the tgtName as the target's IP address while the wdbserial back end doesn't use tgtName. The only restriction is that tgtName must be unique for each target server.

The TGT_OPS Structure

The second parameter passed to the back-end initialization routine, pTgtOps, is a pointer to the TGT_OPS structure (defined in installDir/host/include/tgtlib.h), which declares the target server back-end interface. The initialization routine must fill this structure with the appropriate information and function pointers so that the target server understands what the back end can do. When the target server needs to perform a service, it calls the appropriate back-end function.

typedef struct tgt_ops /* target server back end operations */ 
    { 
    BOOL            tgtConnected;     /* TRUE if connected to Target */ 
    TGT_LINK_DESC   tgtLink;          /* link descriptor */ 
    u_int           tgtSupInfo;       /* additional information */ 
    int             tgtEventFd;       /* not used. Use BKEND_INFO structure*/ 
 
    /* back end routines pointers */ 
 
    UINT32         (*tgtPingRtn)          (void); 
    UINT32         (*tgtConnectRtn)       (WDB_TGT_INFO *); 
    UINT32         (*tgtDisconnectRtn)    (void); 
    UINT32         (*tgtModeSetRtn)       (UINT32 *); 
    ... 
    } TGT_OPS;

The tgtConnected field is a boolean variable that is TRUE if the target board is connected to the target server by the back end. This boolean must be set to FALSE by the initialization routine because the board is not yet connected. It is set to TRUE by the target server if the target-board connection succeeds.

The tgtLink field is a TGT_LINK_DESC structure also defined in installDir/host/include/tgtlib.h. This structure describes the communication link with the board and is composed of three fields:

typedef struct 
    { 
    char *     name;              /* target/host link name */ 
    u_int      type;              /* target/host link type */ 
    u_int      speed;             /* target/host link speed in bps */ 
    } TGT_LINK_DESC;

The tgtSupInfo field is a target server spare field.

The tgtEventFd field obsolete. The file descriptor that the back end uses to wake up the target server when an asynchronous event arrives from the target system is now stored in the BKEND_INFO structure. The back end should set tgtEventFd to NONE.


*      
CAUTION: Tornado expects to receive event information in target byte order. Some emulators may automatically convert all data sent to the host to host byte order. The byte order conversion macros FIX_16 and FIX_32 manage this. They are located in installDir/host/src/tgtsvr/backend/bedk/backend.h.

The tgtPingRtn field is the first of many back-end function pointers. Each function is a service provided by the back end. The complete description of each function is given in the online reference material under Tornado API Reference>Target Server Back End Interface. (Note that the function names are similar to the pointer names, but are prefixed with bkend. All function pointers of this structure must be filled in. If a back-end service is not provided, set the pointer to NULL.

The BKEND_INFO Structure

The last parameter passed to the back-end initialization routine, pbkendInfo, is a pointer to a BKEND_INFO structure (defined in installDir/host/include/bkendlib.h). This structure describes information related to the currently attached back end. The key item is the asynchronous notification method provided by the back end.

typedef struct bkEndInfo 
    { 
    int tgtBkInfoSize;  /* size of this structure */ 
    int tgtBkVersion;   /* Version of BackEnd Hiword = Major;  
                            Loword = Minor*/ 
    union 
        { 
        struct _pollingMethod 
            { 
            int bkEndPollingMethod, /* polling method used : 
                                      *      POLL_NONE_MODE 
                                      *      POLL_SELECT_MODE 
                                      *      POLL_BKROUTINE_MODE 
                                      */ 
            union _bkEndNotifMethod 
                { 
                int pollingFd;  /* polling file desc for POLL_SELECT_MODE */ 
                int (*bkEndSelectRtn)(int timeout) /* bkEnd select routine */ 
                } BKEND_NOTIF_METHOD; 
            } POLLING_METHOD; 
        } INFO; 
    } BKEND_INFO;

The target server initializes BKEND_INFO with the following values:

bkendinfo.tgtBkInfoSize = sizeof (bkend_info); 
bkendinfo.tgtBkVersion = BKEND_VERSION_2

The tgtBkInfoSize field is set by the target server to the size of its BKEND_INFO structure. The tgtBkVersion field is set to BKEND_VERSION_2 (defined in installDir/host/include/ bkendlib.h) for the current version. The back end should check these values and reset tgtBkVersion if necessary.

During the initialization phase, the back end fills in the structure with its method of notifying the target server of asynchronous events. An excerpt from the WDB serial back-end initialization routine is as follows:

... 
 
pbkInfo->tgtBkVersion = BKEND_VERSION_2;     /* Backend version 2 */ 
 
/* we'll provide our polling method ( on NT ) */ 
 
#ifdef WIN32 
pbkInfo->INFO.POLLING_METHOD.bkEndPollingMethod = POLL_BKROUTINE_MODE; 
pbkInfo->INFO.POLLING_METHOD.BKEND_NOTIF_METHOD.bkEndSelectRtn = 
win32SerialSelect; 
#else 
 
/* on UNIX we give the tty file desc */ 
 
pbkInfo->INFO.POLLING_METHOD.bkEndPollingMethod = POLL_SELECT_MODE; 
pbkInfo->INFO.POLLING_METHOD.BKEND_NOTIF_METHOD.pollingFd = EventFd; 
#endif 
return (OK);

2.2.2   Back-End Functions

After attaching and initializing the back end, the target server is able to perform actions on the connected board by calling back-end functions. The syntax of all back-end functions is similar. Functions usually take two pointers as arguments: the first points to a structure that contains parameters specifying the service input data; the second points to a structure that is filled in with the data returned by the service. One or both structure pointers may be omitted when a service requires no input or output data. Each function must return a WDB status code: either WDB_OK on success, or an appropriate WDB error code describing the error encountered during the service call. The complete error code list is located in the file installDir/share/src/agents/wdb/wdb.h.

Because the connection to the back-end interface is indirect, through a table of function pointers initialized when the back end is attached, there are no naming restrictions on back-end functions. Their scope is local to the back end, as all other modules access the back end through the function table. A complete description of the function interface is provided in the online reference material under Tornado API Reference>Target Server Back End Interface.

2.2.3   Event Notification

The target server not only sends requests and information to the target, it also needs feedback from the target about what is occurring there. When a target event (such as hitting a breakpoint or an exception) occurs, the target server must be notified. The back-end interface provides two notification methods, synchronous and asynchronous. The notification method is provided to the target server by the BKEND_INFO structure pointer.

Three methods can be provided:

POLL_NONE_MODE:

POLL_SELECT_MODE:

POLL_BKROUTINE_MODE:

With the two asynchronous methods, the target server calls the back end bkendEventGet( ) function only to get the event information. Those methods are more efficient because no polling for events is required.

After issuing a bkendEventGet( ) command, the target server calls bkendEvtPending( ) to see if other events are pending, and calls bkendEventGet( ) again if bkendEvtPending( ) indicates that other events are available to be read. This method allows the target server to upload multiple target events without having to enter the select loop each time.

2.2.4   Designing a select-like routine

The target server is a multi-threaded application. This allows a back end to export its own select-like routine instead of being limited to a selectable file descriptor. This is particularly useful on WIN32 hosts where select( ) addresses only sockets and not other handles such as serial lines or files. The select-like routine can be expanded to address such devices. It allows the target server back-end thread to sleep (thus not consuming CPU cycles) until events arrive. This routine must match the following prototype:

int bkendSelect (int timeout);

The timeout given by the target server is always WAIT_FOREVER (-1).

The return value should follow the following convention:

Positive value

0

Negative value

It is possible that two threads may use the back ends routines at the same time: a synchronous thread (the one servicing a WTX request), which can call all the routines given in the TGT_OPS structure, and the asynchronous thread (the one which is pending on the given select-like routine). You will have to handle the race condition here. The simplest way is to make the back-end thread return from the select-like routine with a 0 status. This causes the target server to wait until the other thread completes its transaction before asking if there are pending events.

For a discussion of how to inform the target server that a select-like routine is used, see 2.2.1 Attachment and Initialization.

2.2.5   Handling Target-Board Reboots

To keep the target server synchronized with the target agent during a target reboot, the back end must call the routine targetServerRestart( ). A reboot detection mechanism is not optional, and the target server does not function properly if the back end fails to call this routine.

2.2.6   Cleaning Up the Tornado Registry

If the target server does not exit gracefully, which usually occurs because there is an error in the back end, it leaves its name in the registry. This prevents you from reusing the same name when you restart the target server. On UNIX, clean up the registry using the "unregister" button of the Tornado launcher. On Windows, select Target Server>Manage on the Tools menu and select Unregister. On either host you can create a wtxtcl script, unreg, like the following:

#!/bin/sh 
# 
# unreg - remove a name from the registery 
# 
# Syntax: unreg <targetServerName> 
# 
echo -n Unregistering $1 ... 
 
wtxtcl << EOF 
 
set seekName $1 
set allNames [wtxInfo] 
 
set pos [lsearch -glob \$allNames \*\$seekName\* ] 
if { \$pos < 0} { 
    puts "invalid name" 
    exit 
    } 
 
set fullName [lindex [lindex \$allNames \$pos ] 0 ] 
wtxUnregister \$fullName 
 
EOF 
 
echo ` done.'

This is an example of the helpful utilities you can create with the wtxtcl shell. For more information on WTX Tcl, see 4. The WTX Protocol, the Tornado User's Guide: Tcl Appendix, and the online reference material under Tornado API Reference>WTX TCL Library.

2.2.7   Message Logging

A message logging facility exists for all WDB back ends. This facility logs all transactions between the back end and the target agent to a file or terminal. Message logging simplifies diagnosing connection problems between the host and target because one can look at the sequence of transactions that led to the problem. This facility can also be used to help new back-end designers understand requests exchanged between existing back ends and the target.

All back ends that connect to the WDB agent send requests to the target using the library installDir/ host/src/tgtsvr/backend/share/rpccore.c, which contains the logging calls. This logging mechanism is back-end-specific, and is only provided for WDB back ends.

Message logging is enabled when the target server is started with the -Bd fileName option. For example, to save log information in the file named /tmp/WDB.log, invoke the target server with a command like the following:

% tgtsvr ... -Bd /tmp/tgtDebug.log ...

The file name must be specified; otherwise message logging is not enabled. If the file already exists, log information is added at the end of the file. Each time a request is logged to the file, output is flushed to assure that the last request written in the log file is actually the last request sent even if the target server hangs.

The length of the debug file can be controlled by the -Bm logMaxSize option. With this flag, a file is created, or reset if it already exists, and is written as a circular file: when the file length reaches logMaxSize, the file is rewritten from the beginning, overriding the existing data. If this flag is not set or set with a 0 value, a file is created, or opened in append mode if it already exists, and is truncated.


*      
CAUTION: Log files can become huge. An average of 100 bytes is used for each request logged; it is not uncommon to have a log file larger than a megabyte. Be sure to allocate enough disk space before starting the target server without the -Bm option. Also be aware that logging affects the performance of the back end.

Each log file starts with a header, followed by records of transactions with the back end. The header provides:

  • the target server user name
  • the time and date the target server was launched
  • the names of the target server and the target
  • the request timeout value
  • the number of times the server retransmits a request when no response is received.

The following is an example of this header:

User Name              : wrs 
Started                : Wed Jun 17 16:38:19 1998 
Target Server Name     : target@couesnon 
Target Name            : target 
Target Server Options  : tgtsvr -V target -Bd /tmp/WDB.log 
Timeout value          : 1 second(s) 
Request re-send Max   : 3

Each request log is made of two parts: the service requested and the reply. The service-requested information includes the request number, the service name, and the input-structure name with the name and value of each field. The request number is a 16-bit integer assigned to distinguish each request. When the upper limit is reached, the request number restarts from zero. The input-structure name and values are omitted when the service does not require input arguments. A service input structure is signaled by the word In placed before its name.

The reply log consists of three parts: the number of times the request was resent, the service status, and the reply-structure name with the name and value of each field. The service status value is one of the WDB error codes. The file wdb.h located in the installDir/share/src/agents/wdb directory provides the complete error code list. As in the input case, the output structure is signaled by the word Out before its name and it is omitted when the reply has no return value.

An example of a request log is given below. In this example the target server performs a checksum on a block of 49788 bytes of target memory starting at address 0x2000. The return status is OK and the checksum value is 0xffff34de.

2 2       WDB_MEM_CHECKSUM          Wed Jun 17 16:38:19 1998 
        In      WDB_MEM_REGION 
                baseAddr        0x20000 
                numBytes        49788 
                param           0 
3       Out    status:         0k 
                UINT32         0xffff34de

2.2.8   Informing Windows About a New Back End

To make a new back end accessible to the Configure pop-up window under the target server option of the Tools menu, add your backend DLL to installDir/host/x86-win32/lib/backend.

Back-end-specific flags must be supplied in the target server launch command edit box, since Tornado cannot automatically know how many and what kind of arguments the back end supports. To do so, add necessary code to handle backend specific flags to:

installDir/host/resource/tcl/app-config/Tornado/01TgtSvrConfigure.win32.tcl and installDir/host/resource/tcl/app-config/Tornado/01TgtSvrManage.win32.tcl.



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:

  • Write a back end which utilizes RPC core functions, if necessary. For a new machine, you must write clnt_xxx.c.

  • Write a device driver on the target, if necessary.

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:

  • The UDP port that the agent accepts requests from, WDBPORT, is 0x4321.

  • The agent's RPC program number, WDBPROG, is 0x55555555.

  • The agent's RPC version number, WDBVERS, is 1.

Requests Sent to the Agent

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

Figure 2-3:   WDB Request Packet

  

  • A 20-byte IP header.

  • An 8-byte UDP header. It consists of four 16-bit words. WDBPORT is word 2.

  • A 40-byte RPC request header. It consists of ten 32-bit words. WDBPROG is word 3. WDBVERS is word 4. The RPC procedure number is word 5.

  • An XDR stream. The first 12 bytes of this stream is WDB-parameter-wrapper information. The rest of the stream is the XDR-encoded parameters for the procedure. (See installDir/share/src/agents/wdb for more information on the WDB_XDR functions.)

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:

  • A 20-byte IP header.

  • An 8-byte UDP header.

  • A 24-byte RPC reply header.

  • An XDR stream. The first 12 bytes of this stream is WDB-reply-wrapper information. The rest of the stream is the XDR-encoded reply from the procedure.

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.  

Figure 2-4:   WDB Reply Packet

    

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 RPC4 code to transport WDB protocol messages; thus you can reuse Wind River's rpccore library5 . 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.)  

Figure 2-5:   Host-Side Code for WDB Back End

   

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:

  • A clntXXX_create( ) routine (where XXX describes the device type you are using): this routine must open the device for communication and return a pointer to a CLIENT data structure. The CLIENT structure should contain the information needed to perform RPC, including pointers to the clnt_ops functions for managing the transmission of data and data such as file descriptors.

  • The clnt_ops functions: these functions perform the basic RPC operations over a specific network medium.


*      
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. 

Figure 2-6:   Driver Type for Different Protocol Stacks

  

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.



2.4    Writing a Non-WDB Back End

This section addresses the task of writing a back end that communicates with an agent (such as an emulator) other than the WDB agent. In this case, your back end methods must service back-end API requests directly, in other words, by way of a proprietary emulator API. For a general approach highlighting some of the issues involved in this process, see 2.4.1 Overview of Writing a Non-WDB Back End. The remainder of this chapter presents an example implementation. The complete source code for the acecpu32 example back end plus a back-end development class library, which implements the back-end framework as a C++ abstract base class, are provided in the Back-End Developer's Kit.

2.4.1   Overview of Writing a Non-WDB Back End

The following tasks are involved in writing a non-WDB back end:

  • writing the initialization routine

  • writing the back-end methods to provide basic run control

  • adding advanced features (such as target function invocations)

  • testing the back end

The initialization routine is the entry point of the back end. It performs the back-end initialization and attachment. Even before the rest of the back-end functions are implemented, this routine can be called to test correct attachment between the back end and the target server.

Once the attachment and initialization procedures have been carried out successfully, you are ready to begin the major task of writing, documenting, and testing the back-end functions. The remainder of this section outlines the key development issues and suggests one approach to implementing a back end.

Design

The first step in your implementation is to determine what functions your back end must provide. The easiest way to establish this is by using the Back-End Developer's Kit as a basis. It contains a sample back end, the acecpu32, which is an implementation for the fictitious ACE C API. It operates an emulator supporting Motorola CPU32 microprocessors through BDM. It provides a structure and an implementation of common back-end services, including Gopher and checksum, which are emulator independent. You will need to support memory and register accesses, event handling on the target, communication with the emulator or other agent, state handling, and other emulator-dependent services.

Figure 2-7 shows the structure of the acecpu32 implementation.   

Figure 2-7:   acecpu32 Implementation

Figure 2-8 shows how the Event_T class, which manages target event information, is implemented to queue WDB_EVT_DATA.   

Figure 2-8:   Event Queue Structure

Implementation

The recommended plan for implementing the back end is to add support incrementally for the desired services, exercising them with the wtxtcl shell at each stage. (For more information on wtxtcl, see 4.4 WTX Tcl API.) Initially, you should write enough of the framework so that the target server can load your skeleton back end. Then you can add the key functions, testing them to make sure that you are on the right track. If you plan to support both UNIX and Windows hosts, we recommend that you build and test on both hosts in parallel.

  1. First, create a directory in installDir/host/src/tgtsvr/backend for the back end. By convention, the name of the back end and its directory should be a short, descriptive phrase which is all lower-case, such as acecpu32, for the ACE CPU32 emulator back end.
  1. Next, implement a skeleton back-end module which initializes the back end. The module must contain the back-end initialization routine and any other necessary routines. The target server will call the initialization routine when it loads the back end. You may find it helpful to provide stubs for other functions which log an error message to the console. Then you can start the target server with your back end and use the wtxtcl shell to test that it invokes functions in response to WTX requests.
  1. Implement the functions to read and write memory and registers.
  1. Implement the functions to set breakpoints, stop execution, resume execution, and step a context.
  1. Add event handling. At this point you should be able to get CrossWind, the source-level debugger, to operate successfully.
  1. Implement desired optional functions, such as the ability to invoke functions on the target.
  1. Tune performance. If you can optimize memory and register reads, you can improve performance significantly.

By implementing the back-end functions in this order, you will be able to verify quickly that different layers of the Tornado development environment interface correctly with your back end and the emulator you are supporting.

Wind River Conventions

The interface between the target server and the back end is documented in 2.2 Back-End Implementation. In addition, 2.2.1 Attachment and Initialization describes the WDB data structures used to pass information into and out of the back end. Your back end methods must also use the WDB error codes. Both the error codes and the data structures are declared in installDir/share/src/agents/wdb/wdb.h.

The Wind River C coding conventions are documented in the Tornado User's Guide: C and C++ Coding Conventions. The following additional conventions have been adopted for C++ and the Back-End Developer's Kit:

  • Class names end with _T.

  • Member function names end with _m.

  • Member data names end with _.

  • Static member function names end with _s.

  • Wind River C conventions for naming and indentation.

Finally, we recommend that you do not use C++ static objects in the back end because you will have no control over when their constructors and destructors are called. Instead, manage the objects with new and delete.

Testing

Back-end testing relies on the wtxtcl shell provided with Tornado. This shell allows you to send WTX protocol requests to the target server, the same kinds of requests the tools send. Using wtxtcl, you can interactively invoke the back-end methods to verify that they work correctly. Program wtxtcl in Tcl, which Wind River has extended to support the WTX protocol.

2.4.2   Setting Up the Back-End Developer's Kit

This section and the two sections that follow it use the libraries and methods of the Back-End Developer's Kit as an example implementation for the CPU32 emulator. You may be able to use significant portions of this framework for your back end.

Design Considerations

The Back-End Developer's Kit consists of a class library that provides the basic back-end framework, an example back end for acecpu32, sample test scripts, and this document. The library also implements common back-end methods, including Gopher and checksum, which are emulator-independent. By integrating this back-end framework into a new back end, you will not have to spend time developing and debugging these back-end services. To take advantage of this framework, derive a vendor-specific back-end class from the abstract base class Backend_T, declared in backend.h as shown in the following example:

... 
#include "backend.h" 
... 
// Declaration of Vendor-specific back end class 
 
class Ace_T : public Backend_T 
    { 
    // Backend_T methods which you are overriding go here 
    ... 
    // Your vendor-specific methods and data go here 
    };

Once you have derived your vendor-specific class, determine what other classes and objects you need in your system. If you are integrating an existing C API for operating the emulator, you may find that the acecpu32 example back end can be modified.

Some of the classes and objects you may need include the following:

  • an event class for handling events on the target
  • a communication object to communicate with the emulator
  • an object for handling state
  • a parsing object to operate an emulator's ASCII interface if there is no binary interface

Figure 2-9 shows the architecture of the acecpu32 back end.     

Figure 2-9:   acecpu32 Architecture

     

Implementation

After installing the libraries supplied with the Back-End Developer's Kit, follow the implementation procedure laid out in 2.4.1 Overview of Writing a Non-WDB Back End. When you begin tuning performance, if your emulator can perform an operation more efficiently than the default implementation (for example, a memory scan) override Backend_T's methods.

For an example of how to use WDB data structures and error codes, look at the acecpu32 example code in installDir/host/src/tgtsvr/backend/acecpu32.

Porting

The Back-End Developer's Kit's class library is supported only on Solaris 2.5.1 and 2.6, Windows NT, and Windows 95 hosts. The source code for this library is provided and can be readily ported to other hosts because of the limited use of operating system calls. Furthermore, the Tornado development environment provides generic makefile stubs which abstract most of the host-dependent build issues.

Testing

The Back-End Developer's Kit provides example test scripts which are located in installDir/host/src/tgtsvr/backend/bedk/tests. You can use these scripts as a basis for your own test scripts.

2.4.3   Getting Started

The first task is to get the basic skeleton of the back end operating. This means the target server should be able to start up, load the back-end DLL, and initialize, connect, and disconnect the back end.

Creating a Framework

In order to create the skeleton back end, carry out the following steps:

  • Write acecpu32.cpp, which contains the routine acecpu32Init( ). This file is the "main" of the back-end DLL.

  • Declare your vendor-specific back-end class, Ace_T, writing stubs for the mandatory methods. This code belongs in the modules that implement the back end, acecpu32Backend.h and acecpu32Backend.cpp. The methods are not yet supported, but it is convenient to have them log a message to the console and return a value indicating that they were successfully invoked.

  • Modify the example makefile to compile your back end.

  • Use wtxtcl to test that the skeleton back end loads, and that you can execute back-end methods.

Implementation

To implement the skeleton back end, write the "main" back-end module, acecpu32.cpp, which initializes the back end by creating a vendor-specific back-end object, an instance of Ace_T.

Writing acecpu32Initialize( )

First, create acecpu32.cpp and define the routine acecpu32Initialize( ), which initializes the back end. Excerpts from acecpu32.cpp are annotated throughout this section.

Tornado provides header files for the development of host tools in installDir/host/include. The first Tornado header file included must be host.h. Declare the interface for the vendor-specific back end, Ace_T, by including acecpu32Backend.h as shown:

/* acecpu32.cpp - back end for ACE's CPU32 BDM emulator */ 
... 
/* includes */ 
 
#ifdef WIN32 
/* fix clash between Wind River's ERROR and Microsoft's ERROR macros */ 
#ifdef ERROR 
#undef ERROR 
#endif 
#include <windows.h> 
#endif /* #ifdef WIN32 */ 
 
#include "host.h" 
#include "tgtlib.h" 
#include "wpwrutil.h" 
#include "windll.h" 
 
#include "acecpu32Backend.h"


*      
NOTE: Always include windows.h first to avoid clashes with Wind River definitions. Do not use ERROR, which Microsoft defines to be (0); always undefine ERROR, include system header files next, and include Wind River header files last.

Next, define the back-end initialization routine, acecpu32Initialize( ). For a complete discussion, see 2.2.1 Attachment and Initialization.

The back end must initialize the TGT_OPS and BKEND_INFO structures with the information needed by the target server to operate the back end; in particular, the addresses of the back-end methods must be stored in TGT_OPS and the asynchronous notification method must be stored in BKEND_INFO. In order for the target server, which is a C application, to call the back-end methods, the Backend_T class uses static member functions to provide an interface that supports C calling conventions; these static functions then invoke normal member functions on the actual back end. For more information on the static wrapper and Backend_T architecture, see the documentation in provided in installDir/host/src/tgtsvr/backend/bedk/backend.h.

STATUS acecpu32Initialize 
    ( 
    char *       tgtName,    /* network unique target name to connect */ 
    TGT_OPS *    pTgtOps     /* vector of back end functions to fill */ 
    BKEND_INFO * pBkendInfo  /* Backend notification method */ 
    )


*      
NOTE: For Windows hosts, it is important to use default calling conventions and not WINAPI.

The final step is to allocate the actual back end by calling new and to perform error logging. The constructor of Backend_T, the back-end abstract base class, will initialize the back-end function pointers in the TGT_OPS structure. Ace_T's constructor initializes the back-end-specific part of the TGT_OPS structure.

The initialization routine must return OK on success or ERROR on failure, as defined in host.h. The Tornado' wpwrutil library provides several functions for error logging, including the function WPWR_LOG_MSG used in our example. Message logging is enabled by starting the target server with the option -V. For more information on the wpwrutil library, see the online reference material under Tornado API Reference>Target Server Back End Interface.

// Create Backend 
pTheBkEnd = new Ace_T (tgtName, timeout, recallNum, pTtyDevName, 
 
                     baudRate, pTgtOps); 
 
if (pTheBkEnd == NULL) 
    { 
    WPWR_LOG_MSG ("acecpu32Init(): new() failed.\n"); 
    return (ERROR); 
    } 
 
if (! pTheBkEnd->isValid_m()) 
    { 
    WPWR_LOG_MSG ("acecpu32Init(): back end initialization failed.\n") 
    return (ERROR); 
    } 
 
return (OK); 
}


*      
NOTE: Constructors can fail, but most compilers lack exception support. For this reason, the Backend_T class provides a pair of protected members. Backend_T::isNotValid_m( ) should be called if vendor-specific constructors fail. Call Backend_T::isValid_m( ) to see if the back end has been successfully initialized.

Add any other routines to acecpu32.cpp that are needed to support the initialization of the back end.

Declaring the Vendor-Specific Back-End Class Skeleton

Next, declare the skeleton of the vendor-specific back-end class, Ace_T. Derive Ace_T from Backend_T and declare all the mandatory methods in acecpu32Backend.h. The examples in this section show the declaration of the Ace_T class in acecpu32Backend.h.

After the usual preamble of comments and modification history, give the header file the standard macro #ifndef/#define construction to prevent multiple inclusions of the header file. Next, provide the #include statements for the other necessary header files.

/* acecpu32Backend.h - header file for ACE SuperBDM BDM back end */ 
... 
#ifndef __INCacecpu32Backendh 
#define __INCacecpu32Backendh 
 
/* includes */

First, include the Rogue Wave header file.

#include "rw/queuecol.h" 
 
#ifdef WIN32 
#ifdef ERROR 
#undef ERROR 
#endif 
#endif

Next, include the header file for the ACE C API, ace/api.h. Include it at this point rather than later, with the other BEDK header files, because it includes Windows header files, and they must be included before the system headers. The undefining and redefining of ERROR is required when Windows header files are included because Windows uses a non-standard ERROR definition.

#include "ace/api.h" // ACE's API header file 
 
#ifdef WIN32 
#ifdef ERROR 
#undef ERROR 
#endif 
#define ERROR (-1) 
#endif

Next, include Tornado header files; wdb.h defines the WDB data structures used to pass information between the back end and the target server.

#include "host.h" 
#include "tgtlib.h" 
#include "wpwrutil.h" 
#include "wdb.h"

Finally, include backend.h, which pulls in the declaration of Backend_T (the back-end abstract base class) and other back-end-specific header files.

#include "backend.h" 
#include "event.h" 
#include "bdmExcLib.h"

Next, create a new link ID to identify your back end as a unique target-link type. This should be done by defining a macro TGT_LINK_BDM_ACE in tgtlib.h. You should choose an unused number in the 0x200 range.

#ifndef TGT_LINK_BDM_ACE                // Should be defined in tgtlib.h 
#define TGT_LINK_BDM_ACE      (0x201)    /* ACE BDM support */ 
#endif

Now, declare the Ace_T back-end class as shown below. To reuse the back-end framework and services implemented in Backend_T, you must derive your vendor-specific back end from it. It is declared in