/**************************************************************************** * CPP Source File: ANCOM.CPP * * Description: Master analyzer communication. * This is a universal ring 3 interface to link-specific ring 0 drivers, such * as CDXECP.VXD and CDXHSL.VXD. Some of the functions here throw C++ * execeptions, which requires that they be exported as mangled names. * Consequently, the ancom.dll must be rebuilt if the application compiler * changes, as there is no standard decorating syntax even between revisions of * the same compiler. * .......................... Functions .................................... * DllMain * DvrApi::DvrApi * openErrFile * createRing30Event * createRing33Event * getOsVersion * DvrApi::command * simulateRx * loadDriver * closeDriver * TxServer::init * TxServer::reinit * TxServer::sendMsg * TxServer::isReceived * RxClient::RxClient * RxClient::unregister * RxClient::setRxRange * RxClient::getMsg * RxClient::testMultiple * ComThreadApi::kill * ......................... notes ....................................... * * Class: DvrApi * Authors: David McCracken * Description: Provides a structured wrapper around the standard Windows * interface openFile and deviceIoControl. * * - DvrApi provides an interface to the ring 0 driver. Each instance uses * object data for passing data to and receiving data back from the ring 0 * driver, and class functions do not use critical sections. Consequently, the * DvrApi is not thread-safe although the ring 0 driver itself is for most * functions. The simplest way to avoid thread conflict is to give each thread * its own DvrApi by cloning the main thread's. However, threads may share a * DvrApi if their operations are synchronized to not simultaneously need to * call into the ring0 driver. For example, if a thread uses the DvrApi only to * initialize objects or in response to an error which automatically prevents * other threads from using the DvrApi. The main thread's DvrApi is like this * and can safely be shared with one thread. * The main thread creates a local DvrApi and passes a reference to this to * each thread that it spawns. This is done instead of a global because threads * may spawn threads using their own local DvrApi as the source. * ......................................................................... * Class: RxClient * Authors: David McCracken * Description: This creates a registered receiver for incoming messages. * * - The constructor calls into the ring 0 driver to register the RxClient to * receive messages, by default all msgTypes. The destructor calls the driver * to unregister the client, at which time any msgTypes that it filtered away * from previous registrants are given back to them. This only restores their * accept filters; any individual messages that it received while registered * are gone. RxClient objects should be created within try blocks to ensure * that their destructors are called in case of exceptions. * * - The range of msgTypes to be directed to the owner of this RxClient is * initially set by the optional begin and end constructor arguments. If not * stated, they are 0 and 255, registering to receive all types. At any time * msgTypes can be added or deleted from the filter by calling acceptRange and * rejectRange. Each call supports only one pair of range arguments so multiple * calls are needed to register or de-register uncontiguous msgTypes. When a * message type is removed from this RxClient's filter, it reverts to the * previously registered RxClient. With some restrictions, as explained in the * ring 0 driver module, this process can peel back the filters to the first * registrant. * * - The unregister function unregisters the RxClient with the ring 0 driver * without invoking the destructor. This is used only for testing the driver; * an unregisterd RxClient is of no value. Note that the destructor just * invokes the unregister function. * *....................................................................... ***************************************************************************/ #define ANCOM_CPP #define _EXPORTING /* ....................... Windows includes ..................*/ #include /* ....................... Run Time Library includes ......... */ #include #include #include /* ....................... Local includes .....................*/ #include "sysdefs.h" #include "cdxdvr.h" #include "ancom.h" /***************************************************************************** * DATA AND DECLARATIONS *****************************************************************************/ #pragma data_seg ("shared") /* Every item in here needs to be initialized or the linker will put it in * the wrong place, even with -SECTION:shared,rws link option. Note that * "shared" is an arbitrary name. All app programs share every initialized item * in this section. */ /*.................. DLL-based Shared TxServer Resources ...................*/ #define TxRdyEventName "CdxTxRdy" HANDLE txRdyEvent = 0; /* Since this is shared, the first process to connect * to the driver should be the only one required to create it. The DLL should * be able to simply assign this value to each subsequent process' * txServer.events[EVENT_TX]. However, when this is done none of the subsequent * processes is connected to the original handle (more importantly to its ring0 * mate). To make the connection, the event must be named and each process must * "create" it and store it own unique handle. Consequently, this global handle * serves only as a flag telling the first process to perform some one-time * initialization procedures. */ ULONG txInsCnt = 0; /* Number of messages inserted into the transmit queue. * This provides an ID for each message. When pTxMsgCnt >= ID, the analyzer has * received the message or else it was discarded. */ int txbIdx = 0; /* Tx buffer insertion byte index. */ DvrApi txDvr = 0; /* OpenVxDHandle points to the eponymous KERNEL32 function called to create * a ring 0 mirror of a ring 3 event. By setting the ring 0 event, a ring 0 * driver can signal a waiting (on the ring 3 event) ring 3 program. This is * valid only in Win9X. In WinNT/2K, OpenVxDHandle will be NULL. This is the * easiest and most reliable way to tell which OS is running. _winver is chaos. */ DWORD ( WINAPI *openVxdHandle )( HANDLE ) = (DWORD ( WINAPI * )( HANDLE )) GetProcAddress( GetModuleHandle( "KERNEL32" ), "OpenVxDHandle" ); #define os9x ( openVxdHandle != 0 ) char fullDriverName[ 20 ] = ""; // e.g. \\.\CDXHSL.VXD or SYS char deviceNames[][12] = { "\\\\.\\CDXHSL", "\\\\.\\CDXECP", "\\\\.\\CDXUSB" }; #define DRIVER_NAME_OFFSET 4 #define DEFAULT_LINK LINK_ECP DllExport short linkSelection = -1; /* Sometimes, the linker warns that these shared strings are relocatable and * may not work at run-time; sometimes it doesn't even with no code change. * This warning might be related to the issue of pointers not being shareable * but it wasn't issued for explicityly declared pointers that did fail at * run-time when different apps attached the driver. Anyway, this table doesn't * seem to have a run-time problem. */ DllExport char *quitMessages[] = { "Self terminated", // QM_SELF "Terminated on master request", // QM_MASTER "Wait Failure at getMsg", // QM_GETFAIL "Aborted by request at getMsg", // QM_GET "Wait Failure at sendMsg", // QM_SENDFAIL "Aborted by request at sendMsg", // QM_SEND "Attempted transmit using uninitialized driver", // QM_TXUNINIT "Attempt to construct RxClient from uninitialized driver", // QM_RXUNINIT }; DllExport char ancomDriverName[ 40 ] = ""; DllExport short ancomTimeoutTypes = 0; char driverLongName[] = "CDNext Analyzer Comm Driver"; HANDLE openDriverMutex = CreateMutex( NULL, FALSE, NULL ); bool isOpen = false; int appCount = 0; #pragma data_seg () // End of items shared by apps. /* Items defined outside of the shared section are duplicated for each app. * This is also the case for uninitialized items defined in the "shared" * section but to avoid confusion only truly shared (i.e. initialized) items * are defined in the "shared" section. This was originally developed under 9x, * which allowed the driverHandle and communication buffer pointers to be * shared by all apps. Under NT+ the handle can't be shared under any * circumstances and the pointers can only be shared by multiple instances of * one app. MS warns not to share pointers in a DLL but doesn't address this * special case of pointers to memory that the driver allocates and explicitly * makes shared. However, considering that the driver has to map the shared * memory for each application (core function is ZwMapViewOfSection) it is not * surprising that the apps can share these pointers. */ DllExport DvrStat *dvrStat = 0; short *pTxQcount = 0; // Pointer to member of dvrStat needed for inline asm. UCHAR *rxBegin = 0; UCHAR *txBegin = 0; UCHAR *beyondTxBuffer = 0; HANDLE driverHandle = 0; // One per app. /***************************************************************************** * FUNCTIONS *****************************************************************************/ BOOL WINAPI DllMain( HINSTANCE hDllInst, DWORD fdwReason, LPVOID lpvReserved ) { return TRUE; } DvrApi::DvrApi( int toForceDllSegment ) { driverHandle = 0; } /**************************************************************************** * Function: openErrFile * Description: Open next error file for recording status and data related to * a run-time error under investigation. Only 10 files are allowed, ranging in * name from runtime0.err to runtime9.err. This function will not overwrite * existing ones but instead try to find the next one in sequence that doesn't * already exist. If it can't find one, then it returns NULL. The best policy * is to delete *.err before starting a test run. * Returns: FILE pointer for file opened in "wb" mode. * Arguments: None * Authors: David McCracken *..........................................................................*/ FILE *openErrFile( void ) { static char buf[] = "runtime0.err"; FILE *fp; int cnt; for( cnt = 1 ; cnt < 10 ; cnt++ ) if(( fp = fopen( buf, "r" )) == 0 ) return fopen( buf, "wb" ); else buf[ 7 ] = cnt + '0'; return 0; } /**************************************************************************** * Function: createRing30Event * Description: Create a ring 3 and a corresponding ring 0 event. This is used * to create a means for an application to wait for a signal from a ring 0 driver. * Returns: The ring 0 event (DWORD) or 0 if failure. * Indirectly returns the ring 3 event handle. * Arguments: * - HANDLE *ring3Event points to ring 3 event handle for indirect return. * - char *name provides a name for events that will be shared by processes. * This name is assigned to the ring3 event. If name is NULL, processes will * not be able to share the event (note that empty string is not a NULL pointer * and would cause the event to be named ""). * Globals: openVxdHandle evaluated to KERNEL32-OpenVxDHandle * Authors: David McCracken * ............. notes ................................................... * - This is valid only for WIN9X. Under NT, the ring0 counterpart to the * ring3 event is created in ring 0 given the ring3 event handle. ...........................................................................*/ DWORD createRing30Event( HANDLE *ring3Event, char *name ) { if( !os9x ) // Guard against calling through NULL openVxdHandle. return 0; // Create ring 3 event with: default security; auto-reset; initially unsignalled; no name. if(( *ring3Event = CreateEvent( NULL, FALSE, FALSE, name )) != 0 ) return openVxdHandle( *ring3Event ); // If ring 3 event OK then make corresponding ring 0 event. else return 0; } /**************************************************************************** * Function: createRing33Event * Description: Create one ring3 event and assign to given event and return * for assignment to another. This is the WINNT counterpart to * createRing30Event although it can execute under WIN9X. * Returns: Ring3 event handle. The same value is also indirectly returned * via the ring3 event pointer argument. * Arguments: * - HANDLE *ring3Event points to another object to receive the same value. * This serves primarily to roughly match createRing30Event. * - char *name provides a name for events that will be shared by processes. * This name is assigned to the ring3 event. If name is NULL, processes will * not be able to share the event. * Globals: None * Authors: David McCracken *..........................................................................*/ HANDLE createRing33Event( HANDLE *ring3Event, char *name ) { return *ring3Event = CreateEvent( NULL, FALSE, FALSE, name ); } /**************************************************************************** * Function: getOsVersion * Description: Tell whether running under WIN9X or WINNT. * Returns: 0 if WIN9X, 1 if WINNT/2K. * Arguments: None * Globals: os9x macro wrapper for openVxdHandle. * Authors: David McCracken *..........................................................................*/ int getOsVersion( void ) { if( os9x ) return 0; // WIN9X else return 1; // WINNT } /**************************************************************************** * Function: DvrApi::command * Description: DeviceIoControl wrapper. * Returns: Nothing. Returns from the driver are located in DvrApi.fromDvr, * which the caller can read. * Arguments: UINT cmd identifies the command passed to the driver. The * caller copies any additional arguments into DvrApi.toDvr before calling this * function. * Globals: None * Authors: David McCracken *..........................................................................*/ DllExport void DvrApi::command( UINT cmd ) { DWORD dummy; // Only NT actually needs this. DeviceIoControl( driverHandle, cmd, 0, 0, &toDvr, sizeof( toDvr ), &dummy, 0 ); } /**************************************************************************** * Function: simulateRx * Description: Insert a message into the driver's input buffer for processing * as if it were from the analyzer, including routing and waking up the * receiver registered for this message type. The message can be passed between * applications. * Returns: Nothing direct. The first byte of the msg argument is changed * to 0 if the message was inserted or 1 if it couldn't be because the buffer * is full. * Arguments: UCHAR *msg is a standard DM message with an extra leading byte * used by the driver to return the insertion status. See onRxMsg.c * simulateRxMsg notes for the rationale for this return method. * Globals: driverHandle * Authors: David McCracken *..........................................................................*/ void simulateRx( UCHAR *msg ) { DWORD dummy; // Only NT actually needs this. DeviceIoControl( driverHandle, CDXDVR_SIMRX, 0, 0, msg, msg[1] + 2, &dummy, 0 ); } /**************************************************************************** * Function: loadDriver * Description: Load or remove kernel-mode driver. Used only if WINNT/2K. * Returns: 0 if success. < 0 if process failure. > 0 if program error. * Arguments: char *cmd is the full command line including insys program * name. * Globals: None * Authors: David McCracken * ........................... notes ....................................... * - The approach of spawning another exe to perform this function allows this * module to be OS-neutral. A driver cannot be loaded/unloaded without calling * NT-specific functions. It is theoretically possible to extract the addresses * of these functions from the libraries as we do with OpenVxDHandle, but this * is difficult and unreliable. It is a lot simpler to just spawn a separate * exe. *..........................................................................*/ int loadDriver( char *cmd ) { static char appName[] = "Ancom"; DWORD err; PROCESS_INFORMATION pif; STARTUPINFO startInfo = { sizeof( STARTUPINFO ), 0 }; startInfo.dwFlags = STARTF_USESHOWWINDOW; startInfo.wShowWindow = SW_SHOWMINIMIZED; //Sleep( 1000 ); if( CreateProcess( 0, // name of executable module. Do not use this. It screws up everything. cmd, // e.g. "insys cdxhsl d:\\nt40\\master\\cdxhsl.sys" 0, // SD 0, // SD FALSE, // handle inheritance option. CREATE_NEW_CONSOLE, // creation flags 0, // new environment block 0, // current directory name &startInfo, // startup information &pif ) == 0 ) // 0 = failure { err = GetLastError(); MessageBox( 0, "Unable to execute insys.exe", appName, MB_OK ); return -1; } else if( WaitForSingleObject( pif.hProcess, 10000 ) != WAIT_OBJECT_0 ) { TerminateProcess( pif.hProcess, 100 ); CloseHandle( pif.hProcess ); MessageBox( 0, "Insys appears hung", appName, MB_OK ); return -2; } else { //Sleep( 10 ); GetExitCodeProcess( pif.hProcess, &err ); if( err != 0 ) MessageBox( 0, cmd, "Driver load/unload failure", MB_OK ); //else // MessageBox( 0, "Driver load/unload success", appName, MB_OK ); return err; } } /**************************************************************************** * Function: closeDriver * Description: Remove driver from the system when the last connected * application closes. Only NT/2K needs this, as 9X automatically removes * drivers that can be dynamically installed and removed. * Returns: Nothing * Arguments: None * Globals: driverHandle, linkSelection, appCount * Authors: David McCracken * ........................... notes ....................................... *..........................................................................*/ void closeDriver( void ) { char removeCmd[ 100 ]; if( driverHandle != 0 && linkSelection != -1 && --appCount == 0 ) { CloseHandle( driverHandle ); if( ! os9x ) { sprintf( removeCmd, "insys %s remove", deviceNames[ linkSelection ] + DRIVER_NAME_OFFSET ); loadDriver( removeCmd ); // e.g. "insys cdxecp remove". } } } /**************************************************************************** * Function: openDriver * Description: Open a driver on behalf of an application. All applications * must call this in order to attach to the driver. A call to the ring 0 driver * init function is made on behalf of each application but the driver is actually * initialized (e.g. buffer memory allocated) only at the first call (i.e. the * first app to attach the driver). * * Returns: Selected driver index, which is the same as requested by * linkSel argument unless the driver is already open or -1 if the driver can't * be opened. * * Arguments: * - DvrApi *pDvr is pointer to uninitialized driver, which will be initialized here. * - short linkSel is requested link selection, which will be opened if a link * hasn't already been opened (presumably by another application). If -1 then * open default link. * - HWND hwnd is window to display status messages. Use 0 for batch mode, e.g. * under script or hidden operation. * - char *pathexe is the path plus application executable name. The path may * be used to find the driver's sys file. * - USHORT show comprises flags telling what, if any, events to show to the * user. The flag names are SHOW_LINK, SHOW_LINKMATCH, SHOW_LINKFAIL, and * SHOW_LINKALL. * * Authors: David McCracken * .............. notes .................................................... *- openDriverMutex. OpenDriver uses a mutex to prevent two applications from * colliding. The cheap WaitForSingleObject function is used instead of the * more expensive WaitForMultipleObjects, which would afford the master thread * a means to abort, because this is likely to be called from the master thread * anyway, so there would be no means to abort other than timeout, which is * included in the wait call. ...........................................................................*/ short openDriver( DvrApi *pDvr, short linkSel, HWND hwnd, char *pathexe, USHORT show, FindPorts *fp ) { char loadCmd[ 200 ]; char *cp; if( WaitForSingleObject( openDriverMutex, 500 ) != WAIT_OBJECT_0 ) return -1; if( linkSelection != -1 && linkSel != linkSelection ) { if( show & SHOW_LINKMATCH ) MessageBox( hwnd, "The selected link doesn't match the link already running, which will be used instead.", driverLongName, MB_OK | MB_ICONERROR | MB_SYSTEMMODAL ); linkSel = linkSelection; } if( linkSel == -1 ) linkSel = DEFAULT_LINK; strcpy( fullDriverName, deviceNames[ linkSel ]); strcat( fullDriverName, os9x ? ".VXD" : ".SYS" ); if( os9x ) driverHandle = CreateFile( fullDriverName, 0, 0, NULL, 0, FILE_FLAG_DELETE_ON_CLOSE, NULL ); else // WINNT { if( linkSelection == -1 ) { sprintf( loadCmd, "insys %s %s", deviceNames[ linkSel ] + DRIVER_NAME_OFFSET, pathexe ); for( cp = loadCmd + strlen( loadCmd ) ; *cp != '\\' && *cp != ':' ; --cp ) ; strcpy( cp + 1, fullDriverName + DRIVER_NAME_OFFSET ); // e.g. "insys cdxhsl d:\nt40\master\cdxhsl.sys" loadDriver( loadCmd ); // OK even if already loaded. } driverHandle = CreateFile( deviceNames[ linkSel ], GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); // Incompatible with Win95 arguments. } if( driverHandle == INVALID_HANDLE_VALUE ) { if( show & SHOW_LINKFAIL ) MessageBox( hwnd, fullDriverName + DRIVER_NAME_OFFSET, "Unable to open driver", MB_RETRYCANCEL | MB_ICONERROR | MB_SYSTEMMODAL ); ReleaseMutex( openDriverMutex ); return -1; } else linkSelection = linkSel; if( fp == NULL ) { pDvr->toDvr.fp.irq = ~0; pDvr->toDvr.fp.dma = ~0; pDvr->toDvr.fp.io1 = ~0; pDvr->toDvr.fp.io2 = ~0; } else { pDvr->toDvr.fp.irq = fp->irq; pDvr->toDvr.fp.dma = fp->dma; pDvr->toDvr.fp.io1 = fp->io1; pDvr->toDvr.fp.io2 = fp->io2; } pDvr->command( CDXDVR_FINDPORTS ); pDvr->command( CDXDVR_INIT ); dvrStat = pDvr->fromDvr.init.pDvrStat; pTxQcount = &dvrStat->txQcount; // Direct pointer needed for inline asm in sendMsg. ancomTimeoutTypes = pDvr->fromDvr.init.timeoutTypes; rxBegin = pDvr->fromDvr.init.rxBegin; txBegin = pDvr->fromDvr.init.txBegin; beyondTxBuffer = pDvr->fromDvr.init.beyondTx; /* To verify sharing of DMA buffer, at CDXDVR_INIT, the string "RxBegin" was * inserted at rxBegin + 1 (rxBegin[0] is used for message indicator code). * Verify that we see that here in the application. */ cp = (char *)( rxBegin + 1 ); pDvr->command( CDXDVR_GETVERSION ); wsprintf( ancomDriverName, "%s L%d.%d-C%d.%d", fullDriverName + DRIVER_NAME_OFFSET, pDvr->fromDvr.ver.linkMajor, pDvr->fromDvr.ver.linkMinor, pDvr->fromDvr.ver.coreMajor, pDvr->fromDvr.ver.coreMinor); if( show & SHOW_LINK ) MessageBox( hwnd, ancomDriverName, driverLongName, MB_OK | MB_SYSTEMMODAL ); appCount++; ReleaseMutex( openDriverMutex ); return linkSelection; } /**************************************************************************** * Function: TxServer::init * Description: Initialize a TxServer. All applications call this to connect * to the TxServer but the server initialization call into the ring 0 driver * is only done for the first one. * Returns: Nothing. * Arguments: DvrApi *dvr points to an interface that will be copied to a * private instance. The caller can do anything that it wants with DvrApi upon * return. * HANDLE abortEvent provides a process-specific abort signal. * * Globals: txRdyEvent * * Authors: David McCracken * ............... notes .................................................. * - TxServers are not initialized by a constructor because they mostly * comprise a shared core instance. This core includes a driver interface * (DvrApi) and a multi-process MUTEX, both of which must be provided by the * first application to initialize a TxServer. Subsequent apps also provide * these because they don't know that they aren't the first, but the shared * elements are not re-initialized. Each application (process) has its own * TxServer object, but this comprises only the shared MUTEX (each process has * its own process-relative copy of this) and a two-element array of event * handles for waiting on transmit event or app main thread abort. The * events[TX] handle is the same for all TxServer instances but each has its * own array for faster usage in sendMsg. * *- The TxServer constructor only sets txMutex = 0, for use as an unitialized * object indicator. The init function assigns txMutex a non-0 value only after * first confirming that the driver is initialized. By inspecting txMutex, class * functions determine directly that TxServer is initialized and indirectly that * the driver is initialized. ...........................................................................*/ DllExport void TxServer::init( DvrApi *dvr, HANDLE abortEvent ) { if( txMutex != 0 ) return; // This app's TxServer has already been initialized. The app // program should know better but this prevents problems during development. if( driverHandle == 0 ) throw ThrowAbort( QM_TXUNINIT ); txMutex = CreateMutex( NULL, // Default security for 95/98. NT and 2000 require SYNCHRONIZE (?). FALSE, // No initial owner, i.e. immediately signalled for the first grabber. "CdxMasterAnalyzerTxInsert" ); events[ EVENT_ABORT ] = abortEvent; if( txRdyEvent == 0 ) { // Only the first app to attach the driver does this. txDvr = *dvr; if( os9x ) txDvr.toDvr.regTx.event.ring0 = createRing30Event( &txRdyEvent, TxRdyEventName ); else txDvr.toDvr.regTx.event.ring3 = createRing33Event( &txRdyEvent, TxRdyEventName ); if( txRdyEvent != 0 ) { txDvr.command( CDXDVR_REGISTERTX ); txbIdx = 0; events[ EVENT_TX ] = txRdyEvent; } } else { events[ EVENT_TX ] = CreateEvent( NULL, FALSE, FALSE, TxRdyEventName ); } } /**************************************************************************** * Function: TxServer::reinit * Description: Discard all messages currently in the transmit queue. * Returns: Nothing * Arguments: None * Globals: txInsCnt, txIdx * Authors: David McCracken *..........................................................................*/ DllExport void TxServer::reinit( void ) { txInsCnt = 0; txbIdx = 0; } /**************************************************************************** * Function: TxServer::sendMsg * Description: Send the given message to the analyzer by putting it into the * transmit queue and waking up the transmit ring 0 driver if necessary. If the * queue is full, the thread will be put to sleep until the message can be put. * * Returns: ULONG insert message number, which is the message's ID. This * can subsequently be compared to the sent message (transmitted and ACK'd or * discarded after three tries) count to determine when the analyzer has * received the message. TxServer.isReceived provides this capability. * This may also throw a ThrowAbort if the app aborts while the thread is * waiting to put its message into the transmit queue. * * Arguments: * - UCHAR *msg is a Pascal-type string, with the first byte indicating the * length of the remainder of the string, i.e. it doesn't count itself. This * does not need to provide space for the checksum/CRC. * * - DWORD wait is an optional argument specifying the number of msec to wait * for the transmit queue to have room for this message. The default is * INFINITE (defined in winbase.h as 0xFFFFFFFF). * * Globals: txMutex, beyondTxBuffer, txBegin, pTxQcount, * txDvr, txInsCnt * Authors: David McCracken * .................. notes ................................................ * - Wait for space. To avoid overrunning the extractor, we will go to sleep, * waiting for the ISR (onRxAckNak) to remove enough messages to provide the * amount of space needed. With the exception of atomic increment of txQcount, * this function executes with no coherency guarding relative to the ISR. It is * possible for the ISR to remove the blocking message between the time that * sendMsg detects the overrun and the time that the wait is registered. If * more than one message remains and the buffer is at least as big as two MAX * messages (this requirement is related to wrap, not timing) then there will * be another message to trigger onRxAckNak. However, if only one message * remains, onRxAckNak may finish as the wait is being registered, hanging the * system without an independent timeout. To reduce unnecessary delays yet * avoid turning this into essentially a polled process, the txQcount is * checked at several points as follows: * 1. If 0 at the beginning then no overrun checking is performed. * 2. Just before calling the wait, if the count is 0 then skip wait. This * could happen even before the first wait call because the ISR may have * executed. This check is in the wait loop (in fact it is the official condition) * so that in the very remote chance of wakeup failure in the ISR (this should only be * possible if the entire buffer is smaller than three MAX messages) sendMsg will * not hang. * 3. The hard timeout is a short 10 msec. if only one message remains to be * extracted because it may be extracted as the wait is registered. Otherwise, * the hard wait is INFINITE, which simplifies communicating with an analyzer * that may pause for a long time under debugging control. This could be changed * to e.g. 500 msec. for a realistic recovery means, but the hangup should not * occur under the txQcount-based decision regimen. Even a very fast link can't * cause a problem because the PC must still process the messages. * * - Overrun and end of buffer detection are independent but interact. If the * insertion point would cause the message to go over the end of the buffer and * the extraction point is above the insertion then there is also an overrun, * obviously. What isn't so obvious (at least in coding) is that wakeup point * in this case is just the beginning of the buffer plus the length of the * message, regardless of where the extraction point is relative to either the * unwrapped insertion point or the end of the buffer. To avoid excessive * duplication of tests (i.e. overrun within buffer end and vice versa) an * overrun flag is used between the buffer end detector and the overrun * detector. Without this, we couldn't wrap txIp before the overrun detector * but then its calculation of wakeup point as txIp + len would be wrong (in * fact it would lie beyond the buffer). * * - When this was originally developed under 9x, txIp was shared by all * applications. This also worked under NT+ for multiple instances of the same * app but failed for multiple apps. Unlike the other pointers to share memory, * such as rxBegin and txBegin, txIp is written as well as read at this level. * Consequently, unlike them, txIp cannot simply be made an application instance * variable. Instead, a shareable index, txbIdx, is defined. The automatic * pointer, txIp, is initialized here by adding txbIdx to the app-specific * txBegin. txIp is safe to use for the remainder of this function because * txMutex protects txIp and txbIdx from changes by other threads (and apps) * and the low-level driver doesn't even see either of them. We just have to * be sure to change txbIdx and txIp together here (or update txbIdx when * leaving). ...........................................................................*/ DllExport ULONG TxServer::sendMsg( UCHAR *msg, DWORD wait ) { #define TXQOFF offsetof(DvrStat,txQcount) bool overrun; UCHAR *xp; int len; DWORD useWait; UCHAR *txIp; if( !isInitialized()) throw ThrowAbort( QM_TXUNINIT ); if( WaitForSingleObject( txMutex, wait ) == WAIT_TIMEOUT ) { ReleaseMutex( txMutex ); return 0; /* This doesn't need an exception because the timeout is * infinite for any asynchronous caller. A synchronous caller passes a smaller * timeout and will report error if the return is 0. */ } len = *msg + 3; overrun = false; txIp = txBegin + txbIdx; if( txIp + len >= beyondTxBuffer ) { /* The message is too long for the space remaining between txIp and the end * of the buffer. Zero the byte at txIp (if it isn't beyond the buffer) to * tell extractor that this is the end of messages in the buffer, and wrap * around to the beginning of the buffer. */ if( txIp < beyondTxBuffer ) *txIp = 0; if( txIp < txBegin + dvrStat->txExtract ) overrun = true; txIp = txBegin; txbIdx = 0; } if( overrun || // txIp has wrapped but would have overrun txExtract. *pTxQcount > 0 && txIp <= ( xp = txBegin + dvrStat->txExtract ) && txIp + len >= xp ) { dvrStat->txWake = ( txIp + len ) - txBegin; // Note txWake is not a pointer. while( *pTxQcount > 0 ) { switch( WaitForMultipleObjects( 2, events, FALSE, useWait = *pTxQcount > 1 ? wait : 10 )) // See "Wait for space" note. { case WAIT_FAILED : ReleaseMutex( txMutex ); throw ThrowAbort( QM_SENDFAIL ); case WAIT_OBJECT_0 + EVENT_ABORT : ReleaseMutex( txMutex ); throw ThrowAbort( QM_SEND ); // Equivalent to return a value. case WAIT_TIMEOUT : if( useWait == 10 ) continue; ReleaseMutex( txMutex ); return 0; // Try the caller's timeout only once. default : // WAIT_OBJECT_0 + EVENT_TX or TIMEOUT and buffer empty. break; } break; } } memcpy( txIp, msg, len - 2 ); txbIdx += len; /* No need to also advance txIp because it isn't used again in this invocation. */ AtomicInc16At( pTxQcount ); if( dvrStat->txIdle ) txDvr.command( CDXDVR_WAKETX ); // Tx selector/driver needs wake up. ReleaseMutex( txMutex ); return ++txInsCnt; } /**************************************************************************** * Function: isReceived * Description: Determine whether the given message, identified by sequence * number, has been transmitted and acknowledged. This is used primarily at * shutdown to be sure that target debug points are removed. * * Returns: true if the message has been transmitted and ack'd or if the * communication link has never been initialized. The rationale for the latter * is that this function is called to determined whether to take remedial * action, such as warning or retry, which can't serve any purpose if the link * has not been established. Such action is necessary and useful only if the * link has been established and the message has not be received. * * Arguments: ULONG msgId is the transmitted message number, which was * returned by TxServer::sendMsg (the application must save this number in * order to know what to ask for when it calls isReceived). * Globals: dvrStat->txMsgCnt * Authors: David McCracken * ........................... notes ....................................... * - dvrStat->txMsgCnt is the transmit message count exported by the driver. * This counts all transmitted messages that are no longer pending, whether * due to ACK or abandonment (after 3 retries). Therefore, even if there is * no response from the receiver, we may perceive the message has having been * received, which is the ideal behavior in the context of isReceived for the * same reason that uninitialized is equivalent to the message having been * received, i.e. nothing can or should be done about this or any previous * messages. *..........................................................................*/ DllExport bool TxServer::isReceived( ULONG msgId ) { if( !isInitialized()) return true; return msgId <= dvrStat->txMsgCnt; } /**************************************************************************** * Function: RxClient::RxClient * Authors: David McCracken * Description: RxClient constructor * Returns: Throws ThrowAbort if uninitialized driver reference. * Arguments: *- DvrApi *dvr is an initialized (opened) driver. *- HANDLE abortEvent is the application's global event through which the main * (normally user interface) thread is able to abort sleeping/waiting threads. * - UCHAR begin and end tell the range of msgTypes to be routed to this * client. These arguments will actually limit the message types only if this * is not the first RxClient to register, which automatically receives all * messages. This allows an application to register for the messages that it * really must receive even if (or because) this means stealing them from a * previously registered application but if there are no previous registrants * then it automatically gets all of the messages. * * Globals: * ................. notes ................................................ *- By verifying here that the driver has been initialized, we avoid having to * do it repeatedly in class functions, particularly getMsg. DvrApi's constructor * doesn't open/initialize the driver, so we depend on the application to do * this. The check here prevents system crash. ...........................................................................*/ RxClient::RxClient( DvrApi *dvr, HANDLE abortEvent, UCHAR begin, UCHAR end ) { if( driverHandle == 0 ) throw ThrowAbort( QM_RXUNINIT ); if( os9x ) dvr->toDvr.regRx.event.ring0 = createRing30Event( &events[ EVENT_RX ], 0 ); else dvr->toDvr.regRx.event.ring3 = createRing33Event( &events[ EVENT_RX ], 0 ); if( events[ EVENT_RX ] != 0 ) { events[ EVENT_ABORT ] = abortEvent; dvr->toDvr.regRx.range.begin = begin; dvr->toDvr.regRx.range.end = end; dvr->command( CDXDVR_REGISTERRX ); qp = dvr->fromDvr.rx.qp; rxBegin = dvr->fromDvr.rx.rxBegin; // Repeat OK-- just be sure it's done once. } RxClient::dvr = dvr; } /**************************************************************************** * Function: RxClient::unregister * Description: Remove a registered receiver from the driver's receiver list. * Returns: Nothing * Arguments: None * Globals: None * Authors: David McCracken ...........................................................................*/ DllExport void RxClient::unregister( void ) { if( qp == 0 ) return; // Uninitialized RxClient. if( events[ EVENT_RX ] != 0 ) { dvr->toDvr.id = qp->id; dvr->command( CDXDVR_UNREGISTERRX ); events[ EVENT_RX ] = 0; } } /**************************************************************************** * Function: setRxRange * Description: Set a given range of rx message types to be accepted for the * client, i.e. routed to it, or rejected. Rejecting a range that this client * is registered to receive causes the previous registered receiver to regain * the range. * Returns: Nothing * Arguments: * - UCHAR begin * - UCHAR end * - UINT acceptReject is CDXDVR_RXACCEPT or CDXDVR_RXREJECT. * Globals: None * Authors: David McCracken * ........................... notes ....................................... * - Safe to call even if "this" is NULL. *..........................................................................*/ DllExport void RxClient::setRxRange( UCHAR begin, UCHAR end, UINT acceptReject ) { if( this != 0 ) { dvr->toDvr.msgFilter.id = qp->id; dvr->toDvr.msgFilter.range.begin = begin; dvr->toDvr.msgFilter.range.end = end; dvr->command( acceptReject ); } } /**************************************************************************** * Function: RxClient::getMsg * Description: Discard the previously retrieved message (if any) and get the * next one. If one isn't available then go to sleep until one is available or * the communication interface is shut down (normally by the user interface * thread). * Returns: Pointer to the retrieved message. This is NULL only if the * caller has requested only to discard the previous message. * Arguments: * - UCHAR *prev is the previous message. If there is none, e.g. the first time * a client calls, prev should be a value known to not be in the DMA buffer. * The most reliable and convenient value for this is 0. * - short mode is: * -- RCGM_NORMAL = Discard previous (if arg matches) and waits for next. * -- RCGM_ONLY_DISCARD = Discard previous and return 0. * -- RCGM_NO_WAIT = Discard previous and immediately return next or 0 * Globals: dvrStat * Authors: David McCracken *..................... notes .............................................. * * - To discard the previously released message, getMsg decrements mcount and * advances xp, which still points to the message because the queue inserter * (ring 0 driver: onRxMsg) doesn't change a registered receiver's xp when its * mcount is not 0. Note that mcount lags one cycle behind the message reported * out to the caller. One the first call, mcount and xp are not changed. On the * second call, the previous message is discarded by decrementing mcount and * incrementing xp. This ensures that mcount != 0 effectively acts as a mutex * to prevent driver/application conflict over xp even when the caller * retrieves the last available message. Originally, xp was shared by the * driver and app and was not cached. However, to support NT, it was necessary * to change to an index, xi. The local xp generated from xi can become invalid * at any time that mcount is 0. Consequently, the xp calculated before decrementing * mcount must not be used afterward unless mcount didn't go to 0. However, just * checking for mcount > 0 is not safe because the driver could increment it * after the app has decremented it. It is easier and safer to simply reload * xp in both the subsequent mcount = 0 and not 0 code branches. * * - If a registrant's mcount is 0 when a message arrives for it, onRxMsg * updates xp point to the new message. This particularly speeds up infrequent * message receivers because it allows them to skip over intervening messages. * Modifying xp in the interrupt as well as here could require a ring3/0 mutex * but this is avoided by changing xp here only when mcount is not 0. This is * inherent in the message skipper toward the end and is made to happen in the * discarder by decrementing mcount only after modifying xp. * * - If ackMargin > 0 it means that the raw input buffer is nearly full and, to * prevent a possible overrun on the next input, the driver has paused (either * sending ACKPAUSE or nothing). The driver assigns to ackMargin the amount of * space that it wants freed up before allowing additional input from the * analyzer. Since ackMargin (the actual counter-- not the pointer to it) is * shared by all users of the driver, it is possible for two threads/apps to * intersect at the ackMargin check and response in getMsg, causing two calls * to the driver to send the delayed ACK. This could be prevented with a * multi-process MUTEX but it is cheaper to use no guarding and let the driver * discard all but the first call. This would be a more expensive solution if * it happened often, but it will occur very infrequently. Using a guard in * the driver doesn't prevent the possibility of ackMargin being decreased by * more than one thread but this is irrelevant, as it is a long and all * decisions regarding it consider only two states, > 0 and <= 0. * ...........................................................................*/ DllExport UCHAR *RxClient::getMsg( UCHAR *prev, short mode ) { USHORT len; USHORT *pmcount; USHORT *flags; UCHAR *xp; xp = rxBegin + qp->xi; if( prev == xp + 1 ) { // Discard the previous message by advancing the extraction point. xp += ( len = xp[1] + 2 ); qp->xi = xp - rxBegin; pmcount = &qp->mcount; AtomicDec16At( pmcount ); if( dvrStat->ackMargin > 0 && ( dvrStat->ackMargin -= len ) <= 0 ) dvr->command( CDXDVR_ENDRXPAUSE ); } if( mode == RCGM_ONLY_DISCARD ) return 0; if( qp->mcount == 0 ) { if( mode == RCGM_NO_WAIT ) return 0; /* If message count is 0 and mode is to wait for the next input, we need to * tell the low level driver (procRxMsg in onrxmsg.c) that we are waiting so * that it knows to signal us. To avoid excessive signalling we don't want to * set QCTL_RXWAITING before determining that there are no messages, but * setting the flag after testing mcount leaves a time period during which a * message might be received and procRxMsg doesn't know that we have already * determined that we need to wait (because mcount is 0). Our setting of * QCTL_RXWAITING won't do any good in this case, because procRxMsg will have * already received the first message. To avoid this, we set the flag and then * retest mcount. If it is still 0 then we know that we will be signalled at * the first message. Otherwise, the first message arrived between the time of * our first and second tests. In this case, the event may or may not be * signalled. Either way, we can safely reset the event without waiting for it. * We also have to reset QCTL_RXWAITING in case procRxMsg executed only between * the first test of mcount and the setting of the flag, in which case, * procRxMsg doesn't signal the event or clear the flag. */ flags = &qp->flags; AtomicOr16At( flags, QCTL_RXWAITING ); if( qp->mcount == 0 ) { switch( WaitForMultipleObjects( 2, events, FALSE, INFINITE )) { case WAIT_FAILED: throw ThrowAbort( QM_GETFAIL ); case WAIT_OBJECT_0 + EVENT_ABORT : throw ThrowAbort( QM_GET ); } } else { AtomicAnd16At( flags, ~QCTL_RXWAITING ); ResetEvent( events[ EVENT_RX ]); } xp = rxBegin + qp->xi; } /* Skip over messages not for this receiver. This isn't needed if we had to * wait for a message, because the inserter (ring 0 driver-onRxMsg) updates the * registered receiver's xp if its mcount is 0. Note reload of xp from xi. */ else { for( xp = rxBegin + qp->xi ; *xp != qp->id ; ) { if( *xp == QCTL_WRAP ) xp = rxBegin; else xp += xp[1] + 2; } qp->xi = xp - rxBegin; } return xp + 1; } /**************************************************************************** * Function: RxClient::testMultiple * Authors: David McCracken * Description: Wait for msgCount to reach a requested level. This is used * only for testing the access functions' ability to handle multiple messages * and buffer wrap-around. * Returns: Nothing * Arguments: int msgCount = the absolute message count, i.e. total. * Globals: None. ...........................................................................*/ void RxClient::testMultiple( int msgCount ) { while( qp->mcount < msgCount ) ; } /**************************************************************************** * Function: ComThreadApi::kill * Description: Abort this communication thread. * Returns: Nothing * Arguments: HANDLE abort is the thread's abort event. * Globals: None * Authors: David McCracken * ............. notes .................................................... * - If a thread contains both receive and transmit operations then it can only * be reliably aborted by signalling both rxAbort and txAbort. Unlike normal * communication threads, it may also not always sleep when it has nothing to * do, in which case it would also require MB_KILL to be sure of aborting. ...........................................................................*/ void ComThreadApi::kill( HANDLE abort ) { int cnt; if( isActive()) { mailbox = MB_KILL; SetEvent( abort ); for( cnt = 0 ; ; cnt++ ) { if( cnt >= 10 ) break; if( mailbox != MB_DEAD ) Sleep( 100 ); else break; } } ResetEvent( abort ); /* This event is created as auto-reset in * createRing30Event. However, the thread may not be waiting and instead abort * on the mailbox MB_KILL message, in which case the event is never reset * because the thread is not released. The fact that the event is not reset if * the thread is already going may be an anomoly of Windows but, in any case, * simply resetting here solves the problem and doesn't hurt anything. */ }