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.

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.1 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 ( )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:
No asynchronous mechanism is provided. The back end cannot handle asynchronous events. When this method is specified, no asynchronous events are sent to the target server. The target-server back-end thread uses synchronous requests to get events from the target. After each WTX request, it issues a bkendEventGet( ) call to get any events from the target.

POLL_SELECT_MODE:
The back end provides a file descriptor which can be put into a select( ) routine. When this method is specified, the thread handling the events waits on this descriptor using select( ). When this file descriptor becomes active because the emulator or target agent has written to it, the back-end thread gets the events.

POLL_BKROUTINE_MODE:
The back end provides a routine which behaves like the select( ) routine. When this method is specified, the back-end thread pends on the provided routine until the routine returns something positive. When this occurs, the back-end thread receives the pending events.

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
The target server has something to read. It calls the bkendEventGet( ) and bkendEvtPending( ) routines to purge all the pending events.

0
Timeout, or another thread is taking the back end. The target server back-end thread waits until the actual thread dealing with the back end finishes its transaction, and pick up the resulting events afterward by calling bkendEvtPending( ) and( )bkendEventGet( )if events are pending.

Negative value
An error occurred. The back-end thread terminates.

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

Windows does not automatically recognize that a given back end is available. To make a new back end accessible to the Configure pop-up window under the Target Server option of the Tools menu, it is necessary to edit the file: installDir/host/resource/tcl/app-config/Tornado/01TargetServer.win32.tcl.

Change the line:

set tsCfg_backendList {wdbrpc wdbserial netrom}

to

set tsCfg_backendList {wdbrpc wdbserial netrom mybkend}

Back-end-specific flags must be supplied in the Target Server Launch command edit box, since Tornado can not automatically know how many and what kind of arguments the back end supports.


1:  On UNIX hosts and on Windows host when you use the command line, the environment variable WIND_HOST_TYPE records the host type. (For example, on a Sun-4 running Solaris 2.5.1, WIND_HOST_TYPE is sun4-solaris2; see the Tornado User's Guide: Getting Started.)