/*++ Copyright (c) 1991 Microsoft Corporation Copyright (c) 1991 Nokia Data Systems Module Name: vrdlcpst.c Abstract: This module implements the call-back functions for DOS DLC CCBs. The call- backs are also referred to (in IBM terminology) as appendages or exits. These are executed asynchronously when a CCB request completes. Other asynchronous events can be generated such as adapter status change or network error. These latter types (& other similar events) are dependent upon the adapter hardware we are using - Token Ring or EtherNet (IBM Ether Link or PC Network), and are not expected to occur frequently (usually in error situations or where something bad happens to the net). We maintain a READ CCB for each supported adapter (2 per VDM). The READ will capture ALL events - command completions, data reception, status change, etc. When a READ CCB completes, we put it in a queue of completed CCBs and interrupt the VDM. The VDM must asynchronously call back to VrDlcHwInterrupt where it dequeues and processes the completed command at the head of the queue. The READ CCB has a pointer to the completed CCB or received data. If the READ is for a completed NT CCB then the CCB will have a pointer to the original DOS CCB. We have to get the original DOS CCB address from the NT CCB and complete the command with the relevant information contained in the READ/completed NT CCBs. We never expect the queue of pending completions/data receptions to grow very large, since DOS is single-tasking and unless it has interrupts disabled for a large part of the time, then the completion queue should be processed in a timely fashion Contents: VrDlcInitialize VrDlcHwInterrupt (FindCompletedRead) (ProcessReceiveFrame) (QueryEmulatedLocalBusyState) (SetEmulatedLocalBusyState) ResetEmulatedLocalBusyState (ResetEmulatedLocalBusyStateSap) (ResetEmulatedLocalBusyStateLink) (DeferReceive) (DeferAllIFrames) (RemoveDeferredReceive) (VrDlcEventHandlerThread) InitializeEventHandler InitiateRead (PutEvent) (PeekEvent) (GetEvent) (FlushEventQueue) (RemoveDeadReceives) (ReleaseReceiveResources) (IssueHardwareInterrupt) (AcknowledgeHardwareInterrupt) (CancelHardwareInterrupts) Author: Antti Saarenheimo (o-anttis) 26-DEC-1991 Revision History: 16-Jul-1992 rfirth Added queue and interrupt serialization; debugging 19-Nov-1992 rfirth substantially modified event processing - use a queue per adapter; consolidated per-adapter data into DOS_ADAPTER structure; re-wrote local-busy processing Note: It turns out that a thread can recursively enter a critical section. The functions marked 'MUST [NOT] BE ENTERED WHILE HOLDING CRITICAL SECTION ' could be altered --*/ #include #include // ASSERT, DbgPrint #include #include #include // x86 virtual machine definitions #include #include #include "vrdebug.h" #include // Official DLC API definition #include // IOCTL commands #include // Internal IOCTL API interface structures #include "vrdlc.h" #include "vrdlcdbg.h" #define BOOL #include // Insignia defines #include // half_word #include // ica_hw_interrupt #include // call_ica_hw_interrupt // // defines // #define EVENT_THREAD_STACK 0 // 4096 // // macros // // // IS_LOCAL_BUSY - determines whether we have a LOCAL BUSY state active for a // particular link station // #define IS_LOCAL_BUSY(adapter, stationId) (QueryEmulatedLocalBusyState((BYTE)adapter, (WORD)stationId) == BUSY) // // private types // typedef enum { INDICATE_RECEIVE_FRAME, INDICATE_LOCAL_BUSY, INDICATE_COMPLETE_RECEIVE } INDICATION; typedef enum { CURRENT, DEFERRED } READ_FRAME_TYPE; // // private prototypes // PLLC_CCB FindCompletedRead( OUT READ_FRAME_TYPE* pFrameType ); INDICATION ProcessReceiveFrame( IN OUT PLLC_CCB* ppCcb, IN LLC_DOS_CCB UNALIGNED * pDosCcb, IN LLC_DOS_PARMS UNALIGNED * pDosParms, OUT LLC_STATUS* Status ); LOCAL_BUSY_STATE QueryEmulatedLocalBusyState( IN BYTE AdapterNumber, IN WORD StationId ); VOID SetEmulatedLocalBusyState( IN BYTE AdapterNumber, IN WORD StationId ); BOOLEAN ResetEmulatedLocalBusyStateSap( IN BYTE AdapterNumber, IN WORD StationId, IN BYTE DlcCommand ); BOOLEAN ResetEmulatedLocalBusyStateLink( IN BYTE AdapterNumber, IN WORD StationId, IN BYTE DlcCommand ); VOID DeferReceive( IN BYTE AdapterNumber, IN WORD StationId, IN PLLC_CCB pReadCcb ); VOID DeferAllIFrames( IN BYTE AdapterNumber, IN WORD StationId ); PLLC_CCB RemoveDeferredReceive( IN BYTE AdapterNumber, IN WORD StationId, OUT BOOLEAN* pDeferredFramesLeft ); DWORD VrDlcEventHandlerThread( IN PVOID pParameter ); VOID PutEvent( IN BYTE AdapterNumber, IN PLLC_CCB pCcb ); PLLC_CCB PeekEvent( IN BYTE AdapterNumber ); PLLC_CCB GetEvent( IN BYTE AdapterNumber ); VOID FlushEventQueue( IN BYTE AdapterNumber ); VOID RemoveDeadReceives( IN PLLC_CCB pCcb ); VOID ReleaseReceiveResources( IN PLLC_CCB pCcb ); VOID IssueHardwareInterrupt( VOID ); VOID AcknowledgeHardwareInterrupt( VOID ); VOID CancelHardwareInterrupts( IN LONG Count ); // // external functions // extern ACSLAN_STATUS (*lpAcsLan)(IN OUT PLLC_CCB, OUT PLLC_CCB*); extern VOID VrQueueCompletionHandler( IN VOID (*AsyncDispositionRoutine)(VOID) ); extern VOID VrRaiseInterrupt( VOID ); // // external data // extern DOS_ADAPTER Adapters[DOS_DLC_MAX_ADAPTERS]; // // private data // // // aReadCcbs - for each adapter (max. 2) we have an NT READ CCB which is used // to get received data which is returned to the DOS program via its RECEIVE // CCB. Note that these are pointers to CCBs, not the actual CCBs. We also get // status changes & command completions through the same mechanism. These are // reflected to the VDM through the various 'appendages' // // NB: once initialized, this array is ONLY WRITTEN BY InitiateRead // PLLC_CCB aReadCcbs[DOS_DLC_MAX_ADAPTERS]; // // aReadEvents - for each adapter (max. 2) we have a handle to an EVENT object. // This is in the unsignalled state until an event completes the READ CCB. // These are kept in an array because this is how WaitForMultipleObjects // expects them // HANDLE aReadEvents[DOS_DLC_MAX_ADAPTERS]; // // HardwareIntCritSec is used to protect updates to HardwareIntsQueued. // HardwareIntsQueued is the number of outstanding hardware interrupt requests // to the VDM. -1 means none outstanding, 0 is 1, etc. // #define NO_INTERRUPTS_PENDING (-1) static CRITICAL_SECTION HardwareIntCritSec; static LONG HardwareIntsQueued = NO_INTERRUPTS_PENDING; // // hEventThread - handle of asynchronous event thread // static HANDLE hEventThread; // // routines // VOID VrDlcInitialize( VOID ) /*++ Routine Description: Initializes critical sections that must always be available Arguments: None. Return Value: None. --*/ { IF_DEBUG(DLC) { DPUT("VrDlcInitialize\n"); } IF_DEBUG(CRITICAL) { CRITDUMP(("VrDlcInitialize\n")); } // // initialize the critical section for the event list // InitializeCriticalSection(&HardwareIntCritSec); } BOOLEAN VrDlcHwInterrupt( VOID ) /*++ Routine Description: Procedure reads one DOS appendage call from the event queue and sets the initializes MS-DOS registers for the appendage call. A NT DLC event may generate several DOS appendage calls. It is also possible, that no appendage call is needed. The event queue is incremented only after the last DOS appendage has been generated Return Value: FALSE - the event queue was empty, poll the next interrupt handler TRUE - an event was handled successfully. --*/ { BOOLEAN GetNewEvent = FALSE; // default no extra events generated PLLC_CCB pReadCcb; // READ CCB at head of EventQueue PLLC_READ_PARMS pReadParms; // READ parameter table LLC_DOS_CCB UNALIGNED * pDosCcb;// flat 32-bit pointer to DOS CCB WORD cLeft; PLLC_CCB pCcb; LLC_CCB UNALIGNED * pFlatCcbAddr; LLC_DOS_PARMS UNALIGNED * pParms; DWORD iStation; static DWORD iCurrentTempStatus = 0; PLLC_BUFFER pNtFrame; // pointer to received NT frame PLLC_DOS_RECEIVE_PARMS_EX pRcvParms; // special NT rcv params for DOS LLC_STATUS Status; INDICATION indication; WORD buffersLeft; DOS_ADDRESS dpDosCcb; DOS_ADDRESS newAddress; READ_FRAME_TYPE frameType; BYTE adapterNumber; BYTE sapNumber; WORD stationId; PLLC_CCB cancelledReceive = NULL; #if DBG CHAR reasonCode; DWORD reasonCount; #endif IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcHwInterrupt entered\n"); } // // preset the VDM flags to do nothing by default when control is returned // to the hardware interrupt service routine // SET_CALLBACK_NOTHING(); // // This is called from the hardware interrupt handler in vrnetb.c. If there // are no events in the queue, then let the NetBIOS h/w interrupt handler // check if it has any completed NCBs. If there is something in the DLC // event queue then we will return info to the DOS box telling it that it // has a completed CCB; if there was something to complete for NetBIOS, // then it will have to wait until all pending DLC events are completed // pReadCcb = FindCompletedRead(&frameType); if (pReadCcb == NULL) { IF_DEBUG(DLC_ASYNC) { DPUT("*** VrDlcHwInterrupt: Error: no completed READs ***\n"); } IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrDlcHwInterrupt: Error: no completed READs ***\n")); } return FALSE; } IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrDlcHwInterrupt: READ CCB Peeked @ %x ***\n", pReadCcb)); } // // get the completion event and dispatch it // pReadParms = &pReadCcb->u.pParameterTable->Read; adapterNumber = pReadCcb->uchAdapterNumber; switch (pReadParms->uchEvent) { case LLC_EVENT_COMMAND_COMPLETION: // // Event 0x01 // IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcHwInterrupt: LLC_EVENT_COMMAND_COMPLETION\n"); } // // Complete only the first CCB command in the list. // The completed receive CCBs are in the 32-bit flat address // space (we cannot pass them through to DOS because of // different buffer format between DOS and Nt (or OS/2)). // // Every completed receive command (the ones with receive data flag // are never completed here) uses LLC_DOS_SPECIAL_COMMAND as its // input flag, the command actual post routine address and the // original CCB address in DOS have been save to its parameter table. // The completion flag is zero, if the receive command is not // completed by a command completion routine. // pCcb = pReadParms->Type.Event.pCcbCompletionList; if (pReadParms->ulNotificationFlag == LLC_DOS_SPECIAL_COMMAND) { // // The DOS receive is yet another exception: // We cannot directly use the DOS receive CCB, because // the driver completes the command with the received data in // NT buffers => we have allocated a special receive CCB and // parameter table from 32-bit address space to execute // the same receive command. We must now copy the received data // back to DOS buffer pools and then complete the original DOS // receive CCB // pRcvParms = (PVOID)READ_DWORD(&pCcb->u.pParameterTable); pDosCcb = DOS_PTR_TO_FLAT((PVOID)READ_DWORD(&pRcvParms->dpOriginalCcbAddress)); // // Copy the received data only if the receive was successful // if (pCcb->uchDlcStatus == STATUS_SUCCESS || pCcb->uchDlcStatus == LLC_STATUS_LOST_DATA_INADEQUATE_SPACE) { // // We must complete here all receive data commands regardless // if they have a DOS appendage. Zero value in appendage // means, that we don't call appendage routine. // (interrupt vectors don't usually include executable code). // if (pRcvParms->dpCompletionFlag != 0) { setPostRoutine(pRcvParms->dpCompletionFlag); setES(HIWORD(pRcvParms->dpOriginalCcbAddress)); setBX(LOWORD(pRcvParms->dpOriginalCcbAddress)); setAX((WORD)pCcb->uchDlcStatus); } } else { setPostRoutine(0); } pDosCcb->uchDlcStatus = pCcb->uchDlcStatus; cancelledReceive = pCcb; } else { // // On entry to the command completion appendage, the following // are set: // // ES:BX = CCB address of the completed command. // CX = adapter number // AL = return code from CCB_RETCODE // AH = 0x00 // pFlatCcbAddr = DOS_PTR_TO_FLAT(pCcb); setES(HIWORD(pCcb)); setBX(LOWORD(pCcb)); setCX((WORD)pFlatCcbAddr->uchAdapterNumber); setAX((WORD)pFlatCcbAddr->uchDlcStatus); setPostRoutine(pReadParms->ulNotificationFlag); IF_DEBUG(CRITICAL) { CRITDUMP(("COMMAND_COMPLETION: ANR=%04x:%04x CCB=%04x:%04x Type=%02x Status=%02x Adapter=%04x\n", HIWORD(pReadParms->ulNotificationFlag), LOWORD(pReadParms->ulNotificationFlag), getES(), getBX(), pFlatCcbAddr->uchDlcCommand, getAL(), getCX() )); } } break; case LLC_EVENT_TRANSMIT_COMPLETION: // // Event 0x02 // IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcHwInterrupt: LLC_EVENT_TRANSMIT_COMPLETION\n"); } // // Map first CCB pointer and its parameter table to 32-bit addr space // pCcb = pReadParms->Type.Event.pCcbCompletionList; pFlatCcbAddr = (PLLC_CCB)DOS_PTR_TO_FLAT(pCcb); pParms = (PLLC_DOS_PARMS)DOS_PTR_TO_FLAT(READ_DWORD(&pFlatCcbAddr->u.pParameterTable)); IF_DEBUG(TX_COMPLETE) { DPUT3("VrDlcHwInterrupt: pCcb=%x pFlatCcbAddr=%x pParms=%x\n", pCcb, pFlatCcbAddr, pParms ); } // // there may be several completed transmit CCBs chained together // if (--pReadParms->Type.Event.usCcbCount) { // // RLF 09/24/92 // // If there are multiple completions linked together then we leave // this READ CCB at the head of the queue. Decrement the number of // CCBs left and update the pointer to the next one // pReadParms->Type.Event.pCcbCompletionList = (PLLC_CCB)READ_DWORD(&pFlatCcbAddr->pNext); WRITE_DWORD(&pFlatCcbAddr->pNext, 0); pReadCcb = NULL; GetNewEvent = TRUE; IF_DEBUG(DLC_ASYNC) { DPUT2("VrDlcHwInterrupt: next Tx completion: %04x:%04x\n", HIWORD(pReadParms->Type.Event.pCcbCompletionList), LOWORD(pReadParms->Type.Event.pCcbCompletionList) ); } #if DBG reasonCode = 'T'; reasonCount = pReadParms->Type.Event.usCcbCount; #endif } // // The second transmit queue must be returned to the buffer pool IF the // transmit was successful // if (pFlatCcbAddr->uchDlcStatus == LLC_STATUS_SUCCESS && READ_DWORD(&pParms->Transmit.pXmitQueue2)) { IF_DEBUG(DLC_ASYNC) { DPUT2("VrDlcHwInterrupt: freeing XmitQueue2 @ %04x:%04x\n", GET_SEGMENT(&pParms->Transmit.pXmitQueue2), GET_OFFSET(&pParms->Transmit.pXmitQueue2) ); } // // p2-47 IBM LAN Tech Ref: // // "Buffers in XMIT_QUEUE_TWO are freed by the adapter support // software if the transmission is successful (return code is // zero)." // FreeBuffers(GET_POOL_INDEX(pFlatCcbAddr->uchAdapterNumber, READ_WORD(&pParms->Transmit.usStationId) ), (DPLLC_DOS_BUFFER)READ_DWORD(&pParms->Transmit.pXmitQueue2), &cLeft ); IF_DEBUG(DLC_ASYNC) { DPUT1("VrDlcHwInterrupt: after FreeBuffers: %d buffers left\n", cLeft); // DUMPCCB(pFlatCcbAddr, // TRUE, // DumpAll // FALSE, // CcbIsInput // TRUE, // IsDos // HIWORD(pCcb), // Segment // LOWORD(pCcb) // Offset // ); } // // p3-105 IBM LAN Tech Ref: // // "Before taking an appendage exit or posting completion, the // buffers in this queue are returned to the SAP buffer pool // and this field set to zero upon command completion if the // return code equals zero (X'00)." // WRITE_DWORD(&pParms->Transmit.pXmitQueue2, 0); } // // this is a real asynchronous notification - we must asynchronously // call a DOS appendage routine - set up registers: // // ES:BX = completed (transmit) CCB // CX = adapter number // AL = return status // AH = 0x00 // setPostRoutine(pReadParms->ulNotificationFlag); setES(HIWORD(pCcb)); setBX(LOWORD(pCcb)); setCX((WORD)pFlatCcbAddr->uchAdapterNumber); setAX((WORD)pFlatCcbAddr->uchDlcStatus); IF_DEBUG(CRITICAL) { CRITDUMP(("TRANSMIT_COMPLETION: ANR=%04x:%04x CCB=%04x:%04x Type=%02x Status=%02x Adapter=%04x\n", HIWORD(pReadParms->ulNotificationFlag), LOWORD(pReadParms->ulNotificationFlag), getES(), getBX(), pFlatCcbAddr->uchDlcCommand, getAL(), getCX() )); } break; case LLC_EVENT_RECEIVE_DATA: // // Event 0x04 // IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcHwInterrupt: LLC_EVENT_RECEIVE_DATA\n"); // // dump the NT extended RECEIVE + parms // DUMPCCB((PLLC_CCB)(pReadParms->ulNotificationFlag), TRUE, // DumpAll FALSE, // CcbIsInput FALSE, // IsDos 0, // Segment 0 // Offset ); } /*********************************************************************** picture this, if you will NT READ CCB +---------+ | | | | NT RECEIVE CCB | | | | +---------+ | | | | | | | | | | | | | u.pParameterTable-+ | | +---------+ | | | | | | | | | | | u.pParameterTable-+ | +---------+ | v ^ | +---------+ | | NT READ parms | | | | | | | v | | | +---------+ EXTENDED NT |ulNotificationFlag-+ | | RECEIVE PARMS | | | | |pFirstBuffer--+ | | | | | | | +---------+ | | | | | | | | | | |dpOrigininalCcbAddress | +---------+ | v | +---------+ | | | | first NT receive frame | | v | | +---------+ | | DOS RECEIVE | | | | CCB | | | | | | | | | | | | | | +---------+ | | | | +---------- | | +---------+ | | | v +---------+ DOS RECEIVE parms | | | | | | | | | | | | | - - - - -> DOS | | receive | | data +---------+ buffers to avoid the DLC device driver writing to the DOS RECEIVE parameter table (because the NT RECEIVE parameter table is larger, and the driver will corrupt DOS memory if we give it a DOS RECEIVE CCB), the RECEIVE CCB is actually an NT RECEIVE CCB. The RECEIVE parameters are in an extended table which contains the DOS address of the original DOS RECEIVE CCB. When completing a DOS RECEIVE, we have to copy the data from the NT frame to DOS buffers and put the address of the DOS buffers in the DOS RECEIVE parameter table then call the DOS receive data appendage ***********************************************************************/ pDosCcb = (PLLC_DOS_CCB)pReadParms->ulNotificationFlag; dpDosCcb = ((PLLC_DOS_RECEIVE_PARMS_EX)pDosCcb->u.pParms)->dpOriginalCcbAddress; pDosCcb = (PLLC_DOS_CCB)DOS_PTR_TO_FLAT(dpDosCcb); pParms = (PLLC_DOS_PARMS)READ_FAR_POINTER(&pDosCcb->u.pParms); pNtFrame = pReadParms->Type.Event.pReceivedFrame; stationId = pNtFrame->Contiguous.usStationId; sapNumber = SAP_ID(stationId); IF_DEBUG(RX_DATA) { DPUT3("VrDlcHwInterrupt: pNtFrame=%x pDosCcb=%x pParms=%x\n", pNtFrame, pDosCcb, pParms); } // // call ProcessReceiveFrame to find out what to do with this frame. If // we accepted it then we can free the READ CCB and received NT buffer // else we have to indicate local busy. N.B. The READ CCB pointed to // by pReadCcb may well be a different READ CCB when this function // returns // indication = ProcessReceiveFrame(&pReadCcb, pDosCcb, pParms, &Status); switch (indication) { case INDICATE_RECEIVE_FRAME: // // we have an I-Frame to indicate to the VDM. If we got the I-Frame // from the deferred queue then generate another h/w interrupt // IF_DEBUG(RX_DATA) { DPUT("INDICATE_RECEIVE_FRAME\n"); } // // set up the registers for the data received appendage: // // DS:SI = RECEIVE CCB // ES:BX = received data buffer (Buffer 1) // CX = adapter number // AL = return code - STATUS_SUCCESS // AH = 0x00 // setDS(HIWORD(dpDosCcb)); setSI(LOWORD(dpDosCcb)); setES(GET_SEGMENT(&pParms->Receive.pFirstBuffer)); setBX(GET_OFFSET(&pParms->Receive.pFirstBuffer)); setAX((WORD)LLC_STATUS_SUCCESS); setCX((WORD)pReadCcb->uchAdapterNumber); setPostRoutine(READ_DWORD(&pParms->Receive.ulReceiveExit)); IF_DEBUG(CRITICAL) { CRITDUMP(("DATA_COMPLETION: ANR=%04x:%04x CCB=%04x:%04x Type=%02x Status=%02x Adapter=%04x\n", HIWORD(READ_DWORD(&pParms->Receive.ulReceiveExit)), LOWORD(READ_DWORD(&pParms->Receive.ulReceiveExit)), getDS(), getSI(), pDosCcb->uchDlcCommand, getAL(), getCX() )); } IF_DEBUG(ASYNC_EVENT) { DUMPCCB(POINTER_FROM_WORDS(getDS(), getSI()), TRUE, // DumpAll FALSE, // CcbIsInput TRUE, // IsDos getDS(),// Segment getSI() // Offset ); } IF_DEBUG(RX_DATA) { DPUT5("VrDlcHwInterrupt: received data @ %04x:%04x, CCB @ %04x:%04x. status=%02x\n", getES(), getBX(), getDS(), getSI(), getAL() ); } break; case INDICATE_LOCAL_BUSY: // // we have an I-Frame to receive, but no buffers. We have put NT // DLC into LOCAL BUSY (Buffer) state for this link, we must tell // the VDM that we are in LOCAL BUSY state // IF_DEBUG(RX_DATA) { DPUT("INDICATE_LOCAL_BUSY\n"); } // // generate a DLC Status Change event & status table // iStation = iCurrentTempStatus++ & 7; RtlZeroMemory((LPBYTE)&lpVdmWindow->aStatusTables[iStation], sizeof(struct _DOS_DLC_STATUS)); WRITE_WORD(&((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usStationId, stationId); WRITE_WORD(&((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usDlcStatusCode, LLC_INDICATE_LOCAL_STATION_BUSY); // // Set the registers for DLC status change event: ES:BX point to the // DLC status table containing the additional information; AX contains // the status code; CX contains the adapter number; SI contains the // value specified for USER_STAT_VALUE in the DLC.OPEN.SAP // newAddress = NEW_DOS_ADDRESS(dpVdmWindow, &lpVdmWindow->aStatusTables[iStation]); setES(HIWORD(newAddress)); setBX(LOWORD(newAddress)); setAX(LLC_INDICATE_LOCAL_STATION_BUSY); setCX((WORD)adapterNumber); setSI(Adapters[adapterNumber].UserStatusValue[sapNumber]); setPostRoutine(Adapters[adapterNumber].DlcStatusChangeAppendage[sapNumber]); IF_DEBUG(STATUS_CHANGE) { DPUT5("VrDlcHwInterrupt: Status Change info: ES:BX=%04x:%04x, AX=%04x, CX=%04x, SI=%04x\n", getES(), getBX(), getAX(), getCX(), getSI() ); DPUT4("VrDlcHwInterrupt: Status Change Exit = %04x:%04x, StationId=%04x, StatusCode=%04x\n", HIWORD(Adapters[adapterNumber].DlcStatusChangeAppendage[sapNumber]), LOWORD(Adapters[adapterNumber].DlcStatusChangeAppendage[sapNumber]), ((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usStationId, ((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usDlcStatusCode ); } IF_DEBUG(CRITICAL) { CRITDUMP(("LOCAL_BUSY: ES:BX=%04x:%04x, AX=%04x, CX=%04x, SI=%04x\n" "Status Change Exit = %04x:%04x, StationId=%04x, StatusCode=%04x\n", getES(), getBX(), getAX(), getCX(), getSI(), HIWORD(Adapters[adapterNumber].DlcStatusChangeAppendage[sapNumber]), LOWORD(Adapters[adapterNumber].DlcStatusChangeAppendage[sapNumber]), ((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usStationId, ((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usDlcStatusCode )); } break; case INDICATE_COMPLETE_RECEIVE: // // a non I-Frame cannot be completed due to insufficient buffers. // We will discard the frame, but we have to complete the // outstanding RECEIVE, which means CANCELLING the outstanding // NT RECEIVE (assuming that it didn't complete due to real LOCAL // BUSY state) // IF_DEBUG(RX_DATA) { DPUT("INDICATE_COMPLETE_RECEIVE\n"); } // // cancel the NT RECEIVE // ReceiveCancel(pReadCcb->uchAdapterNumber, pReadParms->ulNotificationFlag ); // // we must delete any pending receives which reference this RECEIVE CCB // cancelledReceive = (PLLC_CCB)pReadParms->ulNotificationFlag; // // set up the registers to call the command completion appendage // for the failed RECEIVE command // setES(HIWORD(dpDosCcb)); setBX(LOWORD(dpDosCcb)); setAL((UCHAR)Status); setPostRoutine(READ_DWORD(&pDosCcb->ulCompletionFlag)); WRITE_BYTE(&pDosCcb->uchDlcStatus, Status); IF_DEBUG(CRITICAL) { CRITDUMP(("COMPLETE_RECEIVE: ANR=%04x:%04x CCB=%04x:%04x Type=%02x Status=%02x\n", HIWORD(READ_DWORD(&pDosCcb->ulCompletionFlag)), LOWORD(READ_DWORD(&pDosCcb->ulCompletionFlag)), getES(), getBX(), pDosCcb->uchDlcCommand, getAL() )); } break; } // // if we have a non-NULL READ CCB pointer then we have to free the // NT buffer and free the CCB // if (pReadCcb) { BufferFree(pReadCcb->uchAdapterNumber, pReadCcb->u.pParameterTable->Read.Type.Event.pReceivedFrame, &buffersLeft ); } break; case LLC_EVENT_STATUS_CHANGE: // // Event 0x08, 0x10, 0x20 & 0x40? // IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcHwInterrupt: LLC_EVENT_STATUS_CHANGE\n"); } // // This heuristics tries to minimize the risk of overwritten // DLC status indications and minimize the needed DOS memory. // The first 5 link stations on each adapter has permanent // status tables => apps requiring permanent status tables // works fine up to 5 link connections. The first opened // adapter may use also slots 5 - 10 for permanent status tables, // if there is only one opened adapter. The last 5 slots // are always reserved to routing of the status indications. // This implementation takes 300 bytes DOS memory. The full // support of all possible 510 link stations would take 10 kB memory. // It's better save 10 kB DOS memory and have no configuration // parameters, and accept a very low error probability // // // decrement the station ID by 1 since we always start at station 1 // (0 is the SAP) // iStation = (pReadParms->Type.Status.usStationId & 0x00ff) - 1; if (OpenedAdapters == 1 && iStation < DOS_DLC_STATUS_PERM_SLOTS) { // // We have only one adapter, use slots 1 - 10 for permanent tables // ; // NOP } else if (OpenedAdapters == 2 && iStation < (DOS_DLC_STATUS_PERM_SLOTS / 2)) { // // We have two adapters, use 5 slots for each adapter // to have permanent status tables at least for the // first station ids // iStation += (DOS_DLC_STATUS_PERM_SLOTS / 2) * adapterNumber; } else { // // Use the slots reserve for temporary DLC status indications // iStation = iCurrentTempStatus; iCurrentTempStatus = (iCurrentTempStatus + 1) % DOS_DLC_STATUS_TEMP_SLOTS; } // // We must copy the DLC status block to the VDM address space. Note we // actually use 3 bytes more than the CCB1 status tables, but since we // are using pointers, it shouldn't matter // RtlCopyMemory((LPBYTE)&lpVdmWindow->aStatusTables[iStation], &pReadParms->Type.Status, sizeof(struct _DOS_DLC_STATUS) ); // // Set the registers for DLC status change event: ES:BX point to the // DLC status table containing the additional information; AX contains // the status code; CX contains the adapter number; SI contains the // value specified for USER_STAT_VALUE in the DLC.OPEN.SAP // newAddress = NEW_DOS_ADDRESS(dpVdmWindow, &lpVdmWindow->aStatusTables[iStation]); setES(HIWORD(newAddress)); setBX(LOWORD(newAddress)); setAX(pReadParms->Type.Status.usDlcStatusCode); setCX((WORD)pReadCcb->uchAdapterNumber); setSI(pReadParms->Type.Status.usUserStatusValue); setPostRoutine(pReadParms->ulNotificationFlag); IF_DEBUG(CRITICAL) { CRITDUMP(("STATUS_CHANGE: ANR=%04x:%04x Status Table=%04x:%04x Status=%04x Adapter=%04x\n", HIWORD(pReadParms->ulNotificationFlag), LOWORD(pReadParms->ulNotificationFlag), getES(), getBX(), getAX(), getCX() )); } IF_DEBUG(STATUS_CHANGE) { DPUT5("VrDlcHwInterrupt: Status Change info: ES:BX=%04x:%04x, AX=%04x, CX=%04x, SI=%04x\n", getES(), getBX(), getAX(), getCX(), getSI() ); DPUT4("VrDlcHwInterrupt: Status Change Exit = %04x:%04x, StationId=%04x, StatusCode=%04x\n", HIWORD(pReadParms->ulNotificationFlag), LOWORD(pReadParms->ulNotificationFlag), ((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usStationId, ((PDOS_DLC_STATUS)&lpVdmWindow->aStatusTables[iStation])->usDlcStatusCode ); } break; default: // // This should be impossible! // DPUT("VrDlcHwInterrupt: this is an impossible situation!\n"); IF_DEBUG(CRITICAL) { CRITDUMP(("VrDlcHwInterrupt: this is an impossible situation!\n")); } break; } // // if pReadCcb is not NULL, free the READ CCB & parameter table. If it is // NULL then this means we did not complete processing of the READ CCB - // it has more than 1 transmit CCB chained to it or it is a received frame // which is on the deferred queue // if (pReadCcb) { if (frameType == CURRENT) { // // RLF 09/24/92 // // Remove the READ CCB from the head of the queue using GetEvent // before freeing it. We got the pointer to the completed READ CCB // from PeekEvent which left the READ at the head of the queue in // case we needed to access the same completed READ for multiple // transmit completions // #if DBG // // ensure that the CCB that we are removing from the head of the queue // is the same one returned by PeekEvent // { PLLC_CCB CcbAtQueueHead; CcbAtQueueHead = GetEvent(adapterNumber); if (pReadCcb != CcbAtQueueHead) { DbgPrint("VrDlcHwInterrupt: " "*** ERROR: GetEvent CCB (%x) != PeekEvent CCB (%x) ***\n", CcbAtQueueHead, pReadCcb ); DbgBreakPoint(); } } #else GetEvent(adapterNumber); #endif } else { // // READ CCB is at head of deferred queue. Remove it. If there are // any more READ CCBs queued on this deferred queue then we have // to generate an extra h/w interrupt // #if DBG // // ensure that the CCB that we are removing from the head of the queue // is the same one returned by FindCompletedRead // { PLLC_CCB CcbAtQueueHead; CcbAtQueueHead = RemoveDeferredReceive(adapterNumber, stationId, &GetNewEvent); if (pReadCcb != CcbAtQueueHead) { DbgPrint("VrDlcHwInterrupt: " "*** ERROR: GetEvent CCB (%x) != PeekEvent CCB (%x) ***\n", CcbAtQueueHead, pReadCcb ); DbgBreakPoint(); } if (GetNewEvent) { reasonCode = 'R'; reasonCount = Adapters[adapterNumber].LocalBusyInfo[LINK_ID(stationId)].Depth; } } #else RemoveDeferredReceive(adapterNumber, stationId, &GetNewEvent); #endif } LocalFree((HLOCAL)pReadCcb); IF_DEBUG(DLC_ALLOC) { DPUT1("FREE: freed block @ %x\n", pReadCcb); } } // // RLF 09/24/92 // // Re-ordered issuing h/w int & removing freeing READ CCB since now that // we peek first & remove later, we must be sure that a fully completed // READ CCB is removed from the queue before we request a new h/w // interrupt. I am not expecting this to be re-entered, but who knows // what might happen on a MP machine? // if (GetNewEvent) { // // The event generates more than one appendage call. We need a new // h/w interrupt for each extra appendage call // #if DBG IF_DEBUG(DLC_ASYNC) { DPUT2("*** Calling ica_hw_interrupt from within ISR. Cause = %d %s ***\n", reasonCount, reasonCode == 'T' ? "multiple transmits" : "deferred I-Frames" ); } IF_DEBUG(CRITICAL) { CRITDUMP(("*** Calling ica_hw_interrupt from within ISR. Cause = %d %s ***\n", reasonCount, reasonCode == 'T' ? "multiple transmits" : "deferred I-Frames" )); } #endif IssueHardwareInterrupt(); } // // if an NT RECEIVE CCB was completed or terminated, remove any pending // READ CCBs completed by received data events which reference the terminated // RECEIVE // if (cancelledReceive) { RemoveDeadReceives(cancelledReceive); LocalFree(cancelledReceive); IF_DEBUG(DLC_ALLOC) { DPUT1("FREE: freed block @ %x\n", cancelledReceive); } } // // acknowledge this hardware interrupt, meaning decrement the interrupt // counter and issue a new request if we've got interrupts outstanding // AcknowledgeHardwareInterrupt(); // // return TRUE to indicate that we have accepted the hw interrupt // return TRUE; } PLLC_CCB FindCompletedRead( OUT READ_FRAME_TYPE* pFrameType ) /*++ Routine Description: Finds the next READ CCB to process - first looks at the event queue of current completed CCBs, then at the deferred I-Frame queue. This routine attempts to bounce between both adapters if they are both active, in order not to only service the most active adapter and ignore for a long period of time pending completed CCBs on the less active adapter NOTE: we only return a deferred I-Frame when the app has issued DLC.FLOW.CONTROL against the station Arguments: pFrameType - returns the type of event found - CURRENT (event READ is at head of EventQueue) or DEFERRED (event READ is at head of a LOCAL_BUSY_INFO queue) Return Value: PLLC_CCB pointer to completed READ CCB. The CCB is NOT DEQUEUED - the caller must do that when the event has been completely disposed of to the VDM --*/ { DWORD i; PLLC_CCB pCcb = NULL; static BYTE ThisAdapter = 0; for (i = 0; !Adapters[ThisAdapter].IsOpen && i < DOS_DLC_MAX_ADAPTERS; ++i) { ThisAdapter = (ThisAdapter + 1) & (DOS_DLC_MAX_ADAPTERS - 1); } if (i == DOS_DLC_MAX_ADAPTERS) { // // no adapters alive? // IF_DEBUG(DLC_ASYNC) { DPUT("*** FindCompletedRead: no open adapters??? ***\n"); } return NULL; } // // RLF 09/24/92 // // Changed GetEvent to PeekEvent: if there are multiple chained completed // CCBs (transmit+?) then we have to leave the READ CCB @ the head of the // queue until we have generated call-backs for all chained completions. // Only when there are no more completions can we remove the READ CCB from // the queue (using GetEvent) // if (pCcb = PeekEvent(ThisAdapter)) { *pFrameType = CURRENT; } else { // // see if there are any deferred I-Frames // IF_DEBUG(CRITSEC) { DPUT1("FindCompletedRead: ENTERING Adapters[%d].LocalBusyCritSec\n", ThisAdapter ); } EnterCriticalSection(&Adapters[ThisAdapter].LocalBusyCritSec); // // DeferredReceives is a reference count of link stations in emulated // local-busy(buffer) state. They can be BUSY or CLEARING. We are only // interested in CLEARING: this means that the app has issued // DLC.FLOW.CONTROL to us & presumably has returned some buffers via // BUFFER.FREE (if not, we just go back to BUSY state) // if (Adapters[ThisAdapter].DeferredReceives) { for (i = Adapters[ThisAdapter].FirstIndex; i <= Adapters[ThisAdapter].LastIndex; ++i) { if (Adapters[ThisAdapter].LocalBusyInfo[i].State == CLEARING) { if (pCcb = Adapters[ThisAdapter].LocalBusyInfo[i].Queue) { *pFrameType = DEFERRED; break; } } } } IF_DEBUG(CRITSEC) { DPUT1("FindCompletedRead: LEAVING Adapters[%d].LocalBusyCritSec\n", ThisAdapter ); } LeaveCriticalSection(&Adapters[ThisAdapter].LocalBusyCritSec); } IF_DEBUG(DLC_ASYNC) { DPUT2("FindCompletedRead: returning READ CCB @ %08x type is %s\n", pCcb, *pFrameType == DEFERRED ? "DEFERRED" : "CURRENT" ); } // // next time in, try the other adapter first // ThisAdapter = (ThisAdapter + 1) & (DOS_DLC_MAX_ADAPTERS - 1); return pCcb; } INDICATION ProcessReceiveFrame( IN OUT PLLC_CCB* ppCcb, IN LLC_DOS_CCB UNALIGNED * pDosCcb, IN LLC_DOS_PARMS UNALIGNED * pDosParms, OUT LLC_STATUS* Status ) /*++ Routine Description: Determines what to do with a received frame. We try to copy the received frame to DOS buffers, but if we have insufficient buffers then we must queue or discard the frame. We queue the frame only if it is an I-Frame. We must first check the queue of deferred received frames or else risk getting out-of-sequence received data in the DOS client. When this routine is called, *ppCcb points at one of: 1. A deferred I-Frame READ CCB 2. A current received data READ CCB, I-Frame or other MAC/data NOTE: Deferred I-Frames that are for stations whose local-busy(buffer) state has been cleared by the app take precedence over the event at the head of the EventQueue Arguments: ppCcb - pointer to new READ CCB. On output, this will be a non-NULL value if we are to free the CCB and the received NT frame buffer. If we placed the read on the deferred receive queue then we return a NULL pDosCcb - pointer to DOS CCB pDosParms - pointer to DOS RECEIVE parameter table. If we copy - entirely or partially - a frame, the DOS buffer chain is linked to the DOS RECEIVE parameter table at FIRST_BUFFER Status - if the frame was copied, contains the status to return to the DOS appendage: LLC_STATUS_SUCCESS The frame was completely copied to the DOS buffer(s) LLC_STATUS_LOST_DATA_NO_BUFFERS The frame could not be copied to the DOS buffers and has been discarded. There were NO buffers available. The frame was NOT an I-Frame LLC_STATUS_LOST_DATA_INADEQUATE_SPACE The frame has been partially copied to the DOS buffer(s), but the rest of it had to be discarded, because not enough DOS buffers were available. The frame was NOT an I-Frame Status is only meaningful when INDICATE_COMPLETE_RECEIVE is returned Return Value: INDICATION INDICATE_RECEIVE_FRAME We copied the frame to DOS buffers. Free the CCB and NT buffer and return the frame to DOS INDICATE_LOCAL_BUSY We couldn't copy the received I-Frame to DOS buffers because we don't have enough of them. We have put the NT link station into LOCAL BUSY (Buffer) state. Indicate local busy to the VDM INDICATE_COMPLETE_RECEIVE We couldn't copy the received non I-Frame to DOS buffers. Complete the DOS RECEIVE command with an error --*/ { PLLC_CCB pCcb = *ppCcb; PLLC_PARMS pParms = pCcb->u.pParameterTable; PLLC_BUFFER pFrame = pParms->Read.Type.Event.pReceivedFrame; UCHAR adapter = pCcb->uchAdapterNumber; WORD stationId = pFrame->Contiguous.usStationId; WORD numBufs; WORD bufferSize; WORD buffersAvailable; WORD nLeft; DPLLC_DOS_BUFFER dosBuffers; INDICATION indication; LLC_STATUS status; DWORD flags; IF_DEBUG(DLC_ASYNC) { DPUT2("ProcessReceiveFrame: adapter=%d, stationId=%x\n", adapter, stationId); } // // make sure we don't read or write bogus Adapter structure // ASSERT(adapter < DOS_DLC_MAX_ADAPTERS); // // we are NOT chaining receive frames. DOS receive frame processing assumes // that we get one frame at a time. Assert that it is so // ASSERT(pFrame->Contiguous.pNextFrame == NULL); // // get the contiguous and break flags for CopyFrame based on the RECEIVE options // flags = ((pFrame->Contiguous.uchOptions & (LLC_CONTIGUOUS_MAC | LLC_CONTIGUOUS_DATA)) ? CF_CONTIGUOUS : 0) | ((pFrame->Contiguous.uchOptions & LLC_BREAK) ? CF_BREAK : 0); // // calculate the number of buffers required to receive this frame // numBufs = CalculateBufferRequirement(adapter, stationId, pFrame, pDosParms, &bufferSize ); // // if the frame is an I-Frame then we have to perform these checks: // // 1. if there is already a deferred packet for this station ID/adapter // then we must try to receive this before the frame in hand // // 2. if there aren't sufficient buffers for the request then we must // queue this frame on the deferred queue (if it isn't there already) // and indicate that we have a local busy (buffers) state to the DOS // client // if (pFrame->Contiguous.uchMsgType == LLC_I_FRAME) { IF_DEBUG(DLC_ASYNC) { DPUT("ProcessReceiveFrame: I-Frame\n"); } // // try to allocate the required number of buffers as a chain - returns // DOS pointers, ie unusable in flat data space. Note that if we have // deferred receives then we may be able to satisfy the request now. // We must allocate all the required buffers for an I-Frame, or none // status = GetBuffers(GET_POOL_INDEX(adapter, stationId), numBufs, &dosBuffers, &nLeft, FALSE, NULL ); if (status == LLC_STATUS_SUCCESS) { // // we managed to allocate the required number of DOS buffers. Copy // the NT frame to the DOS buffers and set the indication to return // the I-Frame and free the READ CCB and NT frame buffer // status = CopyFrame(pFrame, dosBuffers, READ_WORD(&pDosParms->Receive.usUserLength), bufferSize, flags ); ASSERT(status == LLC_STATUS_SUCCESS); indication = INDICATE_RECEIVE_FRAME; } else { // // we couldn't get the required number of DOS buffers. We must // queue this I-Frame (& any subsequent I-Frames received for this // link station) on the deferred queue and indicate the local // busy (buffers) state to the DOS client. Set pCcb to NULL to // indicate that it mustn't be deallocated by the caller (it has // already been deallocated in SetEmulatedLocalBusyState) // // We set the LOCAL BUSY (Buffer) state as quickly as possible: we // don't want more than 1 I-Frame queued per link station if we // can help it // SetEmulatedLocalBusyState(adapter, stationId); pCcb = NULL; indication = INDICATE_LOCAL_BUSY; } } else { IF_DEBUG(DLC_ASYNC) { DPUT("ProcessReceiveFrame: other MAC/DATA Frame\n"); } // // the frame is not an I-Frame. If we don't have sufficient buffers to // receive it then we can discard it // status = GetBuffers(GET_POOL_INDEX(adapter, stationId), numBufs, &dosBuffers, &nLeft, TRUE, &buffersAvailable ); if (status == LLC_STATUS_SUCCESS) { // // we got some DOS buffers, but maybe not all requirement // if (buffersAvailable < numBufs) { // // set the status required to be returned to DOS in the completed // RECEIVE CCB and set the CF_PARTIAL flag so that CopyFrame // will know to terminate early // *Status = LLC_STATUS_LOST_DATA_INADEQUATE_SPACE; flags |= CF_PARTIAL; indication = INDICATE_COMPLETE_RECEIVE; } else { // // we allocated all required DOS buffers for this frame // indication = INDICATE_RECEIVE_FRAME; } // // copy the whole or partial frame // status = CopyFrame(pFrame, dosBuffers, READ_WORD(&pDosParms->Receive.usUserLength), bufferSize, flags ); IF_DEBUG(DLC_ASYNC) { DPUT1("ProcessReceiveFrame: CopyFrame (non-I-Frame) returns %x\n", status); } } else { // // no DOS buffers at all // *Status = LLC_STATUS_LOST_DATA_NO_BUFFERS; indication = INDICATE_COMPLETE_RECEIVE; } } // // set the FIRST_BUFFER field in the DOS RECEIVE parameter table. This // is only meaningful if we're going to complete the RECEIVE, with either // success or failure status. Use WRITE_DWORD in case the parameter table // is unaligned // WRITE_DWORD(&pDosParms->Receive.pFirstBuffer, dosBuffers); // // if we are returning DOS buffers, then return the BUFFERS_LEFT field // if (dosBuffers) { PLLC_DOS_BUFFER pDosBuffer = (PLLC_DOS_BUFFER)DOS_PTR_TO_FLAT(dosBuffers); WRITE_WORD(&pDosBuffer->Contiguous.cBuffersLeft, nLeft); } // // set *ppCcb. If this contains non-NULL on return then the caller will // deallocate the CCB and free the NT buffer(s) // *ppCcb = pCcb; // // return indication of action to take // return indication; } LOCAL_BUSY_STATE QueryEmulatedLocalBusyState( IN BYTE AdapterNumber, IN WORD StationId ) /*++ Routine Description: Gets the current local-busy(buffer) state of the requested link station on a particular adapter Arguments: AdapterNumber - which adapter StationId - which link station Return Value: LOCAL_BUSY_STATE NOT_BUSY AdapterNumber/StationId has no deferred I-Frames BUSY AdapterNumber/StationId has deferred I-Frames and has not yet received DLC.FLOW.CONTROL(local-busy(buffer), reset) from the DOS DLC app CLEARING AdapterNumber/StationId has deferred I-Frames AND has received DLC.FLOW.CONTROL(local-busy(buffer), reset) from the DOS DLC app and is currently trying to unload I-Frames to DOS DLC app --*/ { LOCAL_BUSY_STATE state; ASSERT(HIBYTE(StationId) != 0); ASSERT(LOBYTE(StationId) != 0); IF_DEBUG(CRITSEC) { DPUT1("QueryEmulatedLocalBusyState: ENTERING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); state = Adapters[AdapterNumber].LocalBusyInfo[LINK_ID(StationId)].State; if (state == BUSY_BUFFER || state == BUSY_FLOW) { state = BUSY; } IF_DEBUG(CRITSEC) { DPUT1("QueryEmulatedLocalBusyState: LEAVING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); ASSERT(state == NOT_BUSY || state == CLEARING || state == BUSY || state == BUSY_BUFFER || state == BUSY_FLOW ); IF_DEBUG(DLC_ASYNC) { DPUT1("QueryEmulatedLocalBusyState: returning %s\n", state == NOT_BUSY ? "NOT_BUSY" : state == CLEARING ? "CLEARING" : state == BUSY ? "BUSY" : state == BUSY_BUFFER ? "BUSY_BUFFER" : "BUSY_FLOW" ); } return state; } VOID SetEmulatedLocalBusyState( IN BYTE AdapterNumber, IN WORD StationId ) /*++ Routine Description: Sets the emulated local-busy state to local-busy(buffer). If the state is currently NOT_BUSY, sends a DLC.FLOW.CONTROL(local-busy(buffer), set) to the DLC driver. In all cases sets the current state to BUSY Called during processing of an I-Frame when we run out of DOS buffers into which we receive the I-Frame. We can be processing a current I-Frame or a deferred I-Frame at the time we run out of buffers: in the first instance this routine sets local-busy(buffer) state for the first time; in the second instance we regress into local-busy(buffer) state (BUSY) from the CLEARING state. This will happen so long as we continue to run out of DOS buffers Arguments: AdapterNumber - which adapter to set emulated local busy state for StationId - which link station on AdapterNumber Return Value: None. --*/ { LOCAL_BUSY_STATE state; DWORD link = LINK_ID(StationId); ASSERT(AdapterNumber < DOS_DLC_MAX_ADAPTERS); ASSERT(HIBYTE(StationId) != 0); // SAP can't be 0 ASSERT(LOBYTE(StationId) != 0); // Link Station can't be 0 IF_DEBUG(CRITSEC) { DPUT1("SetEmulatedLocalBusyState: ENTERING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); state = Adapters[AdapterNumber].LocalBusyInfo[link].State; ASSERT(state == NOT_BUSY || state == CLEARING || state == BUSY || state == BUSY_BUFFER || state == BUSY_FLOW ); // // if the state of this link station is currently NOT_BUSY then we have // to stop the DLC driver receiving I-Frames for this station. In all // cases, put the READ CCB on the end of the deferred queue for this // adapter/link station // Adapters[AdapterNumber].LocalBusyInfo[link].State = BUSY; // // if the previous state was NOT_BUSY then this is the first time this link // station has gone into local-busy(buffer) state. Increment the deferred // receive count (number of links in local-busy(buffer) state on this // adapter) and send a flow control command to the DLC driver, disabling // further I-Frame receives until we clear the backlog // if (state == NOT_BUSY) { IF_DEBUG(DLC_ASYNC) { DPUT2("SetEmulatedLocalBusyState: setting %d:%04x to BUSY from NOT_BUSY\n", AdapterNumber, StationId ); } ++Adapters[AdapterNumber].DeferredReceives; // // update the indexes to reduce search effort when looking for deferred // receives // ASSERT(Adapters[AdapterNumber].FirstIndex <= Adapters[AdapterNumber].LastIndex); if (Adapters[AdapterNumber].FirstIndex > link) { Adapters[AdapterNumber].FirstIndex = link; } if (Adapters[AdapterNumber].LastIndex < link || Adapters[AdapterNumber].LastIndex == NO_LINKS_BUSY) { Adapters[AdapterNumber].LastIndex = link; } #if DBG //ASSERT(DosDlcFlowControl(AdapterNumber, StationId, LLC_SET_LOCAL_BUSY_BUFFER) == LLC_STATUS_SUCCESS); ASSERT(DlcFlowControl(AdapterNumber, StationId, LLC_SET_LOCAL_BUSY_USER) == LLC_STATUS_SUCCESS); #else //DosDlcFlowControl(AdapterNumber, StationId, LLC_SET_LOCAL_BUSY_BUFFER); DlcFlowControl(AdapterNumber, StationId, LLC_SET_LOCAL_BUSY_USER); #endif } else { IF_DEBUG(DLC_ASYNC) { DPUT3("SetEmulatedLocalBusyState: setting %d:%04x to BUSY from %s\n", AdapterNumber, StationId, state == CLEARING ? "CLEARING" : state == BUSY_BUFFER ? "BUSY_BUFFER" : state == BUSY_FLOW ? "BUSY_FLOW" : "???" ); } } ASSERT(state != BUSY); // // add this READ CCB to the end of the deferred receive queue for this // adapter/link station and any subsequent READ CCBs which completed with // received I-Frames // DeferAllIFrames(AdapterNumber, StationId); IF_DEBUG(DLC_ASYNC) { DPUT5("SetEmulatedLocalBusyState(%d, %04x): Ref#=%d, First=%d, Last=%d\n", AdapterNumber, StationId, Adapters[AdapterNumber].DeferredReceives, Adapters[AdapterNumber].FirstIndex, Adapters[AdapterNumber].LastIndex ); } // // now reduce the priority of the event handler thread to give the CCB // handler thread some time to free buffers & issue DLC.FLOW.CONTROL // (mainly for DOS apps which do it in the wrong order) // SetThreadPriority(hEventThread, THREAD_PRIORITY_LOWEST); IF_DEBUG(CRITSEC) { DPUT1("SetEmulatedLocalBusyState: LEAVING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); } BOOLEAN ResetEmulatedLocalBusyState( IN BYTE AdapterNumber, IN WORD StationId, IN BYTE DlcCommand ) /*++ Routine Description: Clears the local-busy(buffer) state for this adapter/link station. If the transition is from BUSY to CLEARING then just changes the state and issues a hardware interrupt to the VDM: the reason for this is that the original interrupt from the READ which caused us to go into local-busy(buffer) state was used to generate a DLC status change event Called for a single link station or an entire SAP Called in response to receiving DLC.FLOW.CONTROL(local-busy(buffer), reset) from DOS app for a SAP or link station Arguments: AdapterNumber - which adapter to clear the local-busy(buffer) state for StationId - which link station on this adapter DlcCommand - which DLC command is causing this reset Return Value: BOOLEAN TRUE - state was reset from BUSY to CLEARING FALSE - state is NOT_BUSY: invalid request --*/ { BOOLEAN reset; ASSERT(AdapterNumber < DOS_DLC_MAX_ADAPTERS); ASSERT(HIBYTE(StationId) != 0); // SAP can't be 0 // // grab the LocalBusyCritSec for the adapter and reset the local-busy(buffer) // state for the link station or the entire SAP. If we are resetting for the // entire SAP, holding the critical section ensures that new I-Frames won't // cause another station to go into emulated local-busy(buffer) state while // we are resetting the rest // IF_DEBUG(CRITSEC) { DPUT1("ResetEmulatedLocalBusyState: ENTERING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); if (LOBYTE(StationId) == 0) { reset = ResetEmulatedLocalBusyStateSap(AdapterNumber, StationId, DlcCommand); } else { reset = ResetEmulatedLocalBusyStateLink(AdapterNumber, StationId, DlcCommand); } IF_DEBUG(CRITSEC) { DPUT1("ResetEmulatedLocalBusyState: LEAVING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); return reset; } BOOLEAN ResetEmulatedLocalBusyStateSap( IN BYTE AdapterNumber, IN WORD StationId, IN BYTE DlcCommand ) /*++ Routine Description: This function is called when the app resets the local-busy(buffer) state for an entire SAP NB: This function MUST BE CALLED WHILE HOLDING THE LocalBusyCritSec for this adapter Arguments: AdapterNumber - which adapter to StationId - SAP:00 - which SAP to reset local-busy(buffer) states for DlcCommand - which DLC command is causing this reset Return Value: BOOLEAN TRUE - links reset for this SAP FALSE - no links reset for this SAP --*/ { DWORD link = Adapters[AdapterNumber].FirstIndex; DWORD last = Adapters[AdapterNumber].LastIndex; DWORD count = 0; LOCAL_BUSY_STATE state; ASSERT(AdapterNumber < DOS_DLC_MAX_ADAPTERS); ASSERT(HIBYTE(StationId) != 0); ASSERT(link <= last); ASSERT(DlcCommand == LLC_BUFFER_FREE || DlcCommand == LLC_DLC_FLOW_CONTROL); IF_DEBUG(DLC_ASYNC) { DPUT3("ResetEmulatedLocalBusyStateSap(%d, %04x, %s)\n", AdapterNumber, StationId, DlcCommand == LLC_BUFFER_FREE ? "BUFFER.FREE" : DlcCommand == LLC_DLC_FLOW_CONTROL ? "DLC.FLOW.CONTROL" : "???" ); } // // we may have a DLC.FLOW.CONTROL for a SAP which has already been reset // by a previous DLC.FLOW.CONTROL // if (link == NO_LINKS_BUSY) { ASSERT(last == NO_LINKS_BUSY); IF_DEBUG(DLC_ASYNC) { DPUT("ResetEmulatedLocalBusyStateSap: SAP already reset\n"); } return FALSE; } // // since we are holding the LocalBusyCritSec for this adapter, we can use // FirstLink and LastLink to try reduce the number of searches for busy // stations. No new stations can go busy and change FirstLink or LastLink // while we are in this loop // for (++StationId; link <= last; ++StationId) { state = Adapters[AdapterNumber].LocalBusyInfo[link].State; ++link; if (state == BUSY || (state == BUSY_BUFFER && DlcCommand == LLC_DLC_FLOW_CONTROL) || (state == BUSY_FLOW && DlcCommand == LLC_BUFFER_FREE)) { if (ResetEmulatedLocalBusyStateLink(AdapterNumber, StationId, DlcCommand)) { ++count; } } } return count != 0; } BOOLEAN ResetEmulatedLocalBusyStateLink( IN BYTE AdapterNumber, IN WORD StationId, IN BYTE DlcCommand ) /*++ Routine Description: This function is called when the app resets the local-busy(buffer) state for a single link station Clears the local-busy(buffer) state for this adapter/link station. If the transition is from BUSY to CLEARING then just changes the state and issues a hardware interrupt to the VDM: the reason for this is that the original interrupt from the READ which caused us to go into local-busy(buffer) state was used to generate a DLC status change event NB: This function MUST BE CALLED WHILE HOLDING THE LocalBusyCritSec for this adapter Arguments: AdapterNumber - which adapter to clear the local-busy(buffer) state for StationId - which link station on this adapter DlcCommand - which DLC command is causing this reset Return Value: BOOLEAN TRUE - state was reset from BUSY to CLEARING FALSE - state is NOT_BUSY: invalid request --*/ { DWORD link = LINK_ID(StationId); LOCAL_BUSY_STATE state; ASSERT(AdapterNumber < DOS_DLC_MAX_ADAPTERS); ASSERT(HIBYTE(StationId) != 0); // SAP can't be 0 ASSERT(LOBYTE(StationId) != 0); // Link Station can't be 0 ASSERT(DlcCommand == LLC_BUFFER_FREE || DlcCommand == LLC_DLC_FLOW_CONTROL); IF_DEBUG(DLC_ASYNC) { DPUT3("ResetEmulatedLocalBusyStateLink(%d, %04x, %s)\n", AdapterNumber, StationId, DlcCommand == LLC_BUFFER_FREE ? "BUFFER.FREE" : DlcCommand == LLC_DLC_FLOW_CONTROL ? "DLC.FLOW.CONTROL" : "???" ); } state = Adapters[AdapterNumber].LocalBusyInfo[link].State; ASSERT(state == NOT_BUSY || state == CLEARING || state == BUSY || state == BUSY_BUFFER || state == BUSY_FLOW ); if (state == BUSY) { // // if the state is BUSY then this is the first DLC.FLOW.CONTROL or // BUFFER.FREE since we went into local-busy(buffer) state. State // transition is to BUSY_FLOW if this is a DLC.FLOW.CONTROL else // BUSY_BUFFER // IF_DEBUG(DLC_ASYNC) { DPUT1("ResetEmulatedLocalBusyStateLink: state: BUSY -> %s\n", DlcCommand == LLC_BUFFER_FREE ? "BUSY_BUFFER" : "BUSY_FLOW" ); } Adapters[AdapterNumber].LocalBusyInfo[link].State = (DlcCommand == LLC_BUFFER_FREE) ? BUSY_BUFFER : BUSY_FLOW; } else if ((state == BUSY_FLOW && DlcCommand == LLC_BUFFER_FREE) || (state == BUSY_BUFFER && DlcCommand == LLC_DLC_FLOW_CONTROL)) { // // state is BUSY_FLOW or BUSY_BUFFER. If this reset is caused by the // other half of the state transition requirement then change the state // IF_DEBUG(DLC_ASYNC) { DPUT3("ResetEmulatedLocalBusyStateLink: link %d.%04x changing from %s to CLEARING\n", AdapterNumber, StationId, state == BUSY_FLOW ? "BUSY_FLOW" : "BUSY_BUFFER" ); } Adapters[AdapterNumber].LocalBusyInfo[link].State = CLEARING; IF_DEBUG(DLC_ASYNC) { DPUT("ResetEmulatedLocalBusyStateLink: Interrupting VDM\n"); } IssueHardwareInterrupt(); // // for the benefit of the caller, the state was essentially BUSY // state = BUSY; } else { IF_DEBUG(DLC_ASYNC) { DPUT3("ResetEmulatedLocalBusyStateLink: NOT resetting state of %d.%04x. state is %s\n", AdapterNumber, StationId, state == CLEARING ? "CLEARING" : "NOT_BUSY" ); } } return state == BUSY; } VOID DeferReceive( IN BYTE AdapterNumber, IN WORD StationId, IN PLLC_CCB pReadCcb ) /*++ Routine Description: Adds a READ CCB to the end of the deferred receive queue for an adapter/ station id NB: This function MUST BE CALLED WHILE HOLDING THE LocalBusyCritSec for this adapter Arguments: AdapterNumber - which adapter to set emulated local busy state for StationId - which link station on AdapterNumber pReadCcb - pointer to completed received I-Frame CCB (NT READ CCB) Return Value: None. --*/ { PLLC_CCB* pQueue; PLLC_CCB pLlcCcb; // // if there is nothing in the queue for this adapter number/station ID // then place this CCB at the head of the queue, else put the CCB at // the end. The CCBs are chained together using their CCB_POINTER fields. // Normally, this field is not used for received frame READ CCBs // ASSERT(pReadCcb->pNext == NULL); ASSERT(HIBYTE(StationId) != 0); ASSERT(LOBYTE(StationId) != 0); #if DBG IF_DEBUG(DLC_ASYNC) { DPUT4("DeferReceive: deferring I-Frame for %d.%04x. CCB = %08x. Current depth is %d\n", AdapterNumber, StationId, pReadCcb, Adapters[AdapterNumber].LocalBusyInfo[LINK_ID(StationId)].Depth ); } #endif pQueue = &Adapters[AdapterNumber].LocalBusyInfo[LINK_ID(StationId)].Queue; pLlcCcb = *pQueue; if (!pLlcCcb) { *pQueue = pReadCcb; } else { for (; pLlcCcb->pNext; pLlcCcb = pLlcCcb->pNext) { ; } pLlcCcb->pNext = pReadCcb; } #if DBG ++Adapters[AdapterNumber].LocalBusyInfo[LINK_ID(StationId)].Depth; ASSERT(Adapters[AdapterNumber].LocalBusyInfo[LINK_ID(StationId)].Depth <= MAX_I_FRAME_DEPTH); IF_DEBUG(CRITICAL) { CRITDUMP(("DeferReceive: %d.%04x CCB=%08x Depth=%d\n", AdapterNumber, StationId, pReadCcb, Adapters[AdapterNumber].LocalBusyInfo[LINK_ID(StationId)].Depth )); } #endif } VOID DeferAllIFrames( IN BYTE AdapterNumber, IN WORD StationId ) /*++ Routine Description: Removes all pending I-Frames for StationId from the EventQueue for this adapter and places them on the deferred queue for this StationId. This is done when the StationId goes into local-busy(buffer) state. We do this so that any event packets behind the I-Frames can be completed, other link stations which aren't blocked can receive their I-Frames and ensures that once in local-busy state, all I-Frames are deferred for a link station NB: This function MUST BE CALLED WHILE HOLDING THE LocalBusyCritSec for this adapter NB: we have to gain access to 2 critical sections - the LocalBusyCritSec for this station ID & the EventQueueCritSec for this adapter Assumption: This function is called in the context of the VDM h/w ISR and before AcknowledgeHardwareInterrupt is called for this ISR BOP Arguments: AdapterNumber - which adapter structure to use StationId - which link station to remove I-Frames for Return Value: None. --*/ { PLLC_CCB pCcb; PLLC_CCB next; PLLC_CCB* last; PLLC_READ_PARMS pReadParms; PLLC_BUFFER pFrame; BOOLEAN remove; // // deferredFrameCount starts at -1 because it is a count of pending h/w // interrupts to cancel. We have to acknowledge the current one which will // reduce the count by 1 // LONG deferredFrameCount = -1; ASSERT(AdapterNumber < DOS_DLC_MAX_ADAPTERS); ASSERT(HIBYTE(StationId) != 0); ASSERT(LOBYTE(StationId) != 0); IF_DEBUG(CRITSEC) { DPUT1("DeferAllIFrames: ENTERING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); pCcb = Adapters[AdapterNumber].EventQueueHead; last = &Adapters[AdapterNumber].EventQueueHead; while (pCcb) { pReadParms = &pCcb->u.pParameterTable->Read; // // only remove I-Frames from the EventQueue that are destined for this // link station // remove = FALSE; if (pReadParms->uchEvent == LLC_EVENT_RECEIVE_DATA) { pFrame = pReadParms->Type.Event.pReceivedFrame; if (pFrame->Contiguous.uchMsgType == LLC_I_FRAME && pFrame->Contiguous.usStationId == StationId) { remove = TRUE; } } if (remove) { next = pCcb->pNext; *last = next; --Adapters[AdapterNumber].QueueElements; if (Adapters[AdapterNumber].EventQueueTail == pCcb) { if (last == &Adapters[AdapterNumber].EventQueueHead) { Adapters[AdapterNumber].EventQueueTail = NULL; } else { Adapters[AdapterNumber].EventQueueTail = CONTAINING_RECORD(last, LLC_CCB, pNext); } } IF_DEBUG(DLC_ASYNC) { DPUT3("DeferAllIFrames: moving CCB %08x for %d.%04x\n", pCcb, AdapterNumber, StationId ); } pCcb->pNext = NULL; DeferReceive(AdapterNumber, StationId, pCcb); ++deferredFrameCount; pCcb = next; } else { IF_DEBUG(DLC_ASYNC) { DPUT1("DeferAllIFrames: not removing CCB %08x from EventQueue\n", pCcb ); } last = (PLLC_CCB*)&pCcb->pNext; pCcb = pCcb->pNext; } } if (deferredFrameCount > 0) { CancelHardwareInterrupts(deferredFrameCount); } IF_DEBUG(CRITSEC) { DPUT1("DeferAllIFrames: LEAVING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); } PLLC_CCB RemoveDeferredReceive( IN BYTE AdapterNumber, IN WORD StationId, OUT BOOLEAN* pDeferredFramesLeft ) /*++ Routine Description: Removes a READ CCB from the head of the deferred receive queue for an adapter/station id and sets the head to point to the next deferred receive in the queue NB: This function MUST NOT BE CALLED WHILE HOLDING THE LocalBusyCritSec for this adapter (opposite of DeferReceive) Arguments: AdapterNumber - which adapter to set emulated local busy state for StationId - which link station on AdapterNumber pDeferredFramesLeft - TRUE if there are more frames on this deferred queue Return Value: PLLC_CCB pointer to CCB from head of queue --*/ { PLLC_CCB* pQueue; PLLC_CCB pLlcCcb; DWORD link = LINK_ID(StationId); // // if there is nothing in the queue for this adapter number/station ID // then place this CCB at the head of the queue, else put the CCB at // the end. The CCBs are chained together using their CCB_POINTER fields. // Normally, this field is not used for received frame READ CCBs // ASSERT(HIBYTE(StationId) != 0); ASSERT(LOBYTE(StationId) != 0); IF_DEBUG(CRITSEC) { DPUT1("RemoveDeferredReceive: ENTERING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); pQueue = &Adapters[AdapterNumber].LocalBusyInfo[link].Queue; pLlcCcb = *pQueue; *pQueue = pLlcCcb->pNext; ASSERT(pLlcCcb != NULL); IF_DEBUG(DLC_ASYNC) { DPUT4("RemovedDeferredReceive: removing I-Frame for %d.%04x. CCB = %08x. Current depth is %d\n", AdapterNumber, StationId, pLlcCcb, Adapters[AdapterNumber].LocalBusyInfo[LINK_ID(StationId)].Depth ); } #if DBG --Adapters[AdapterNumber].LocalBusyInfo[link].Depth; #endif // // if the deferred queue is now empty, reset the state and issue the real // DLC.FLOW.CONTROL(local-busy(buffer), reset) to the DLC driver. Also // reduce the reference count of link stations on this adapter in the // local-busy(buffer) state and update the first and last link indicies // if (*pQueue == NULL) { IF_DEBUG(DLC_ASYNC) { DPUT2("RemoveDeferredReceive: %d.%04x: change state to NOT_BUSY\n", AdapterNumber, StationId ); } Adapters[AdapterNumber].LocalBusyInfo[link].State = NOT_BUSY; --Adapters[AdapterNumber].DeferredReceives; if (Adapters[AdapterNumber].DeferredReceives) { if (link == Adapters[AdapterNumber].FirstIndex) { for (link = Adapters[AdapterNumber].FirstIndex + 1; link <= Adapters[AdapterNumber].LastIndex; ++link) { if (Adapters[AdapterNumber].LocalBusyInfo[link].State != NOT_BUSY) { Adapters[AdapterNumber].FirstIndex = link; break; } } } else if (link == Adapters[AdapterNumber].LastIndex) { for (link = Adapters[AdapterNumber].LastIndex - 1; link >= Adapters[AdapterNumber].FirstIndex; --link ) { if (Adapters[AdapterNumber].LocalBusyInfo[link].State != NOT_BUSY) { Adapters[AdapterNumber].LastIndex = link; break; } } } } else { Adapters[AdapterNumber].FirstIndex = NO_LINKS_BUSY; Adapters[AdapterNumber].LastIndex = NO_LINKS_BUSY; } #if DBG //ASSERT(DosDlcFlowControl(AdapterNumber, StationId, LLC_RESET_LOCAL_BUSY_BUFFER) == LLC_STATUS_SUCCESS); ASSERT(DlcFlowControl(AdapterNumber, StationId, LLC_RESET_LOCAL_BUSY_USER) == LLC_STATUS_SUCCESS); #else //DosDlcFlowControl(AdapterNumber, StationId, LLC_RESET_LOCAL_BUSY_BUFFER); DlcFlowControl(AdapterNumber, StationId, LLC_RESET_LOCAL_BUSY_USER); #endif *pDeferredFramesLeft = FALSE; // // restore async event thread's priority // SetThreadPriority(hEventThread, THREAD_PRIORITY_ABOVE_NORMAL); } else { *pDeferredFramesLeft = TRUE; } IF_DEBUG(DLC_ASYNC) { DPUT5("RemoveDeferredReceive(%d, %04x): Ref#=%d, First=%d, Last=%d\n", AdapterNumber, StationId, Adapters[AdapterNumber].DeferredReceives, Adapters[AdapterNumber].FirstIndex, Adapters[AdapterNumber].LastIndex ); } IF_DEBUG(CRITSEC) { DPUT1("RemoveDeferredReceive: LEAVING Adapters[%d].LocalBusyCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].LocalBusyCritSec); return pLlcCcb; } DWORD VrDlcEventHandlerThread( IN PVOID pParameter ) /*++ Routine Description: This is the VDM DLC event handler thread. The thread reads all DLC events from both DOS DLC adapters, queues them to the event queue and requests a DOS hardware interrupt (the post routine mechanism uses hardware interrupts to make an external event in the VDM) To make this stuff as fast as possible, we don't allocate or free any memory in this loop, but we reuse the old read CCBs and their parameter tables in the event queue We filter out any completion which will NOT result in an asynchronous event in the VDM. This means CCB completions for this emulator (DIR.CLOSE.ADAPTER and DIR.CLOSE.DIRECT for example) and received I-Frames for link stations which are currently in the BUSY (local-busy(buffer)) state. This avoids us making unnecessary interrupts in the VDM and costly (on x86 machines) BOPs which achieve no action for the VDM Arguments: pParameter - not used Return Value: None, this should loop forever, until the VDM process dies --*/ { DWORD status = LLC_STATUS_PENDING; DWORD waitIndex; PLLC_CCB pReadCcb; PLLC_READ_PARMS pReadParms; WORD stationId; UNREFERENCED_PARAMETER(pParameter); IF_DEBUG(DLC_ASYNC) { DPUT2("VrDlcEventHandlerThread kicked off: Thread Handle=%x, Id=%d\n", GetCurrentThread(), GetCurrentThreadId() ); } // // wait for the READ CCB event for either adapter to become signalled (by // DLC driver when READ completes) // while (TRUE) { waitIndex = WaitForMultipleObjects( ARRAY_ELEMENTS(aReadEvents), // count of objects aReadEvents, // handle array FALSE, // do not wait all objects INFINITE // wait forever ); // // if we get 0xFFFFFFFF back then an error occurred // if (waitIndex == 0xffffffff) { status = GetLastError(); IF_DEBUG(DLC_ASYNC) { DPUT1("VrDlcEventHandlerThread: FATAL: WaitForMultipleObjects returns %d\n", status); } // // this terminates the thread! // break; } // // if we get a number > number of events-1, then either a timeout // occurred or a mutex was abandoned, both of which are highly // improbable. Just continue with the loop // if (waitIndex > LAST_ELEMENT(aReadEvents)) { IF_DEBUG(DLC_ASYNC) { DPUT1("VrDlcEventHandlerThread: ERROR: WaitForMultipleObjects returns %d?: continuing\n", waitIndex); } continue; } // // one of the READ CCBs has successfully completed (oh joy!) // pReadCcb = aReadCcbs[waitIndex]; // // reset the event // ResetEvent(aReadEvents[waitIndex]); IF_DEBUG(DLC_ASYNC) { DPUT1("VrDlcEventHandlerThread: Event occurred for adapter %d\n", waitIndex); IF_DEBUG(READ_COMPLETE) { DUMPCCB(pReadCcb, TRUE, FALSE, FALSE, 0, 0); } } if (pReadCcb->uchDlcStatus == STATUS_SUCCESS) { // // it gets better! // pReadParms = &pReadCcb->u.pParameterTable->Read; // // if the completion flag is VRDLC_COMMAND_COMPLETION then this // command originated in this emulator: Do Not hand it back to // the VDM. In fact do nothing with it: this is an asynchronous // command that we didn't want to wait for (like DIR.CLOSE.ADAPTER // or DIR.CLOSE.DIRECT) // if (pReadParms->ulNotificationFlag == VRDLC_COMMAND_COMPLETION) { IF_DEBUG(CRITICAL) { CRITDUMP(("*** VrDlcEventHandlerThread: VRDLC_COMMAND_COMPLETION: CCB=%08x COMMAND=%02x ***\n", pReadCcb, pReadCcb->uchDlcCommand)); } } else if (pReadParms->uchEvent == LLC_EVENT_STATUS_CHANGE && pReadParms->Type.Status.usDlcStatusCode == LLC_INDICATE_LOCAL_STATION_BUSY && !IS_LOCAL_BUSY(waitIndex, pReadParms->Type.Status.usStationId)) { // // We must separate the buffer busy states of global NT // buffer pool and the local buffer pools. // This must be a real buffer busy indication, if // the SAP has no overflowed receive buffers. // How can such a situation arise - if we (ie DOS emulation) aren't // holding onto the buffers, then where are they? Sounds like a bug // to me (RLF 07/22/92) // IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcEventHandlerThread: *** REAL LOCAL BUSY??? ***\n"); } // // We are not queueing buffers because of having // no SAP buffers available => this must be a real // buffer busy indication. The READ command should // automatically extend the buffer pool size (up // to the maximum value set in the initialization) // DlcFlowControl((BYTE)waitIndex, pReadParms->Type.Status.usStationId, LLC_RESET_LOCAL_BUSY_BUFFER); } else if (pReadParms->uchEvent == LLC_EVENT_RECEIVE_DATA && pReadParms->Type.Event.pReceivedFrame->Contiguous.uchMsgType == LLC_I_FRAME) { stationId = pReadParms->Type.Event.pReceivedFrame->Contiguous.usStationId; ASSERT(HIBYTE(stationId) != 0); ASSERT(LOBYTE(stationId) != 0); IF_DEBUG(CRITSEC) { DPUT1("VrDlcEventHandlerThread: ENTERING Adapters[%d].LocalBusyCritSec\n", waitIndex ); } EnterCriticalSection(&Adapters[waitIndex].LocalBusyCritSec); // // if the link station is in emulated local-busy(buffer) state // BUSY or CLEARING then queue the I-Frame. This action does NOT // generate a h/w interrupt to the VDM. VrDlcHwInterrupt generates // additional h/w interrupts when it processes deferred I-Frames // ASSERT( Adapters[waitIndex].LocalBusyInfo[LINK_ID(stationId)].State == NOT_BUSY || Adapters[waitIndex].LocalBusyInfo[LINK_ID(stationId)].State == CLEARING || Adapters[waitIndex].LocalBusyInfo[LINK_ID(stationId)].State == BUSY || Adapters[waitIndex].LocalBusyInfo[LINK_ID(stationId)].State == BUSY_BUFFER || Adapters[waitIndex].LocalBusyInfo[LINK_ID(stationId)].State == BUSY_FLOW ); if (Adapters[waitIndex].LocalBusyInfo[LINK_ID(stationId)].State != NOT_BUSY) { DeferReceive((BYTE)waitIndex, stationId, pReadCcb); // // set the READ CCB pointer to NULL: we have to allocate a // new READ CCB // pReadCcb = NULL; } IF_DEBUG(CRITSEC) { DPUT1("VrDlcEventHandlerThread: LEAVING Adapters[%d].LocalBusyCritSec\n", waitIndex ); } LeaveCriticalSection(&Adapters[waitIndex].LocalBusyCritSec); } // // pReadCcb is NULL if the READ CCB is to be added to the event // queue, NULL if we deferred an I-Frame for a link station in // local-busy(buffer) state // if (pReadCcb) { // // queue the completed READ CCB in the event queue. If it is // full (!) then wait. We will already have issued a call to // call_ica_hw_interrupt for each of the events in the queue // so we wait on the VDM removing events from the queue. This // is an irregular situation, that I (RLF) don't expect to // arise. If it does, then it probably means there is some // horrendous inefficiency somewhere // PutEvent((BYTE)waitIndex, pReadCcb); IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcEventHandlerThread: Interrupting VDM\n"); } // // poke the VDM so that it knows there is some asynchronous // processing to do // IssueHardwareInterrupt(); // // set pReadCcb to NULL. We have to allocate and submit a new // READ CCB // pReadCcb = NULL; } } else { // // The READ function failed, the adapter must be closed. We now // wait until the adapter is opened again and the event is set // back to the signaled state // IF_DEBUG(DLC_ASYNC) { DPUT1("VrDlcEventHandlerThread: READ failed. Status=%x\n", pReadCcb->uchDlcStatus); } LocalFree(pReadCcb); IF_DEBUG(DLC_ALLOC) { DPUT1("FREE: freed block @ %x\n", pReadCcb); } // // wait for a new READ CCB to be created the next time this adapter // is opened // //continue; pReadCcb = NULL; } // // if we had a successful completion then get a new CCB. If the CCB was // not queued, re-use it // if (!pReadCcb) { pReadCcb = InitiateRead(waitIndex, (LLC_STATUS*)&status); if (pReadCcb) { status = pReadCcb->uchDlcStatus; } else { IF_DEBUG(DLC_ASYNC) { DPUT("VrDlcEventHandlerThread: Error: InitiateRead returns NULL\n"); } break; } } else { status = lpAcsLan(pReadCcb, NULL); if (status != LLC_STATUS_SUCCESS) { IF_DEBUG(DLC_ASYNC) { DPUT1("VrDlcEventHandlerThread: Error: AcsLan returns %d\n", status); } break; } } } // // !!! WE SHOULD NEVER BE HERE !!! // IF_DEBUG(DLC_ASYNC) { DPUT1("VrDlcEventHandlerThread: Fatal: terminating. Status = %x\n", status); } return 0; } BOOLEAN InitializeEventHandler( VOID ) /*++ Routine Description: Initializes static data structures used in event handling Arguments: None Return Value: BOOLEAN Success - TRUE Failure - FALSE --*/ { DWORD i; DWORD Tid; IF_DEBUG(DLC_ASYNC) { DPUT("Vr: InitializeEventHandler\n"); } // // make sure the read CCBs and event queues are in a known state // RtlZeroMemory(aReadCcbs, sizeof(aReadCcbs)); // // preset the handle array with invalid handles so we know which ones // have been allocated in clean up // for (i = 0; i < ARRAY_ELEMENTS(aReadEvents); ++i) { aReadEvents[i] = INVALID_HANDLE_VALUE; } // // create event handles for all (both) supported adapters. DIR.OPEN.ADAPTER // sets the event to signalled which enables the event handler thread to // receive events for that adapter. If we get an error creating the handles // then clean up before exiting so that we may try this again later // for (i = 0; i < ARRAY_ELEMENTS(aReadEvents); i++) { aReadEvents[i] = CreateEvent(NULL, // security attributes: no inherit TRUE, // manual-reset event FALSE, // initial state = not signaled NULL // unnamed event ); if (aReadEvents[i] == NULL) { IF_DEBUG(DLC_ASYNC) { DPUT1("Vr: InitializeEventHandler: Error: failed to create read event: %d\n", GetLastError()); } goto cleanUp; } } // // create and start the thread which handles RECEIVE events // hEventThread = CreateThread(NULL, // security attributes EVENT_THREAD_STACK, // initial thread stack size VrDlcEventHandlerThread, NULL, // thread args 0, // creation flags &Tid ); if (hEventThread) { IF_DEBUG(CRITICAL) { CRITDUMP(("InitializeEventHandler: Created thread Handle=%x, Tid=%d\n", hEventThread, Tid)); } SetThreadPriority(hEventThread, THREAD_PRIORITY_ABOVE_NORMAL); return TRUE; } else { IF_DEBUG(DLC_ASYNC) { DPUT1("Vr: InitializeEventHandler: Error: failed to create thread: %d\n", GetLastError()); } } // // we come here if for some reason we couldn't create the event handles or // the event handler thread // cleanUp: for (i = 0; i < ARRAY_ELEMENTS(aReadEvents); ++i) { if (aReadEvents[i] != INVALID_HANDLE_VALUE) { CloseHandle(aReadEvents[i]); } } IF_DEBUG(DLC_ASYNC) { DPUT("InitializeEventHandler: Error: returning FALSE\n"); } return FALSE; } PLLC_CCB InitiateRead( IN DWORD AdapterNumber, OUT LLC_STATUS* ErrorStatus ) /*++ Routine Description: Create a READ CCB, initialize it to get all events for all stations, set the completion event to the event created for this adapter and submit the CCB (via AcsLan). If the submit succeeds, set this CCB as the READ CCB for AdapterNumber NB: The READ CCB - which will be queued on EventQueue - and its parameter table are allocated together, so we only need one call to LocalFree to deallocate both This routine IS THE ONLY PLACE WHERE aReadCcbs IS WRITTEN once the array has been initialized in InitializeEventHandler Arguments: AdapterNumber - which adapter to initiate this READ for ErrorStatus - returned LLC_STATUS describing failure if this function returns NULL Return Value: PLLC_CCB pointer to allocated/submitted CCB or NULL. If this function succeeds then aReadCcbs[AdapterNumber] points to the READ CCB If this function fails then *ErrorStatus will contain an LLC_STATUS describing why we failed to allocate/submit the CCB. The CCB will be deallocated in this case --*/ { PLLC_CCB pCcb; PLLC_READ_PARMS parms; LLC_STATUS status; IF_DEBUG(DLC_ASYNC) { DPUT1("InitiateRead: AdapterNumber=%d\n", AdapterNumber); } // // Allocate, initialize and issue the next DLC command. Allocate contiguous // space for CCB and parameter table // pCcb = (PLLC_CCB)LocalAlloc(LMEM_FIXED | LMEM_ZEROINIT, sizeof(LLC_CCB) + sizeof(LLC_READ_PARMS) ); // // put the READ CCB in the array before we have a chance to complete it // aReadCcbs[AdapterNumber] = pCcb; if (pCcb) { // // initialize required CCB fields // pCcb->uchAdapterNumber = (UCHAR)AdapterNumber; pCcb->uchDlcCommand = LLC_READ; parms = (PLLC_READ_PARMS)&pCcb[1]; pCcb->u.pParameterTable = (PLLC_PARMS)parms; pCcb->hCompletionEvent = aReadEvents[AdapterNumber]; // // set the read options to receive ALL events for ALL stations // parms->uchOptionIndicator = LLC_OPTION_READ_ALL; parms->uchEventSet = LLC_READ_ALL_EVENTS; // // submit the CCB. If it's not ok, free the CCB and NULL the pointer // in the list of per-adapter READ CCBs // status = lpAcsLan(pCcb, NULL); if (status != LLC_STATUS_SUCCESS) { aReadCcbs[AdapterNumber] = NULL; IF_DEBUG(DLC_ASYNC) { DPUT1("InitiateRead: AcsLan failed: %x\n", status); } LocalFree((HLOCAL)pCcb); IF_DEBUG(DLC_ALLOC) { DPUT1("FREE: freed block @ %x\n", pCcb); } *ErrorStatus = status; pCcb = NULL; } } else { *ErrorStatus = LLC_STATUS_NO_MEMORY; } #if DBG IF_DEBUG(DLC_ASYNC) { DPUT2("InitiateRead: returning pCcb=%x%c", pCcb, pCcb ? '\n' : ' '); if (!pCcb) { DPUT1("*ErrorStatus=%x\n", *ErrorStatus); } } #endif return pCcb; } VOID PutEvent( IN BYTE AdapterNumber, IN PLLC_CCB pCcb ) /*++ Routine Description: Adds a completed event (READ CCB) to the Event Queue. If the event queue is full then returns FALSE. Updates the queue tail index. The queue is accessed inside a critical section Arguments: AdapterNumber - which adapter to queue event for pCcb - pointer to completed READ CCB to add to the queue Return Value: None. --*/ { ASSERT(AdapterNumber < DOS_DLC_MAX_ADAPTERS); ASSERT(pCcb->pNext == NULL); IF_DEBUG(CRITSEC) { DPUT1("PutEvent: ENTERING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); if (Adapters[AdapterNumber].EventQueueTail == NULL) { Adapters[AdapterNumber].EventQueueHead = pCcb; } else { Adapters[AdapterNumber].EventQueueTail->pNext = pCcb; } Adapters[AdapterNumber].EventQueueTail = pCcb; ++Adapters[AdapterNumber].QueueElements; IF_DEBUG(EVENT_QUEUE) { DPUT5("PutEvent: Added %x to adapter %d EventQueue. Head=%x Tail=%x Elements=%d\n", pCcb, AdapterNumber, Adapters[AdapterNumber].EventQueueHead, Adapters[AdapterNumber].EventQueueTail, Adapters[AdapterNumber].QueueElements ); } IF_DEBUG(CRITSEC) { DPUT1("PutEvent: LEAVING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); } PLLC_CCB PeekEvent( IN BYTE AdapterNumber ) /*++ Routine Description: Reads the next completed CCB from the head of the Event Queue. If the queue is empty (QueueElements == 0) then returns NULL. The queue is accessed inside a critical section Arguments: None. Return Value: PLLC_CCB Success - pointer to CCB at queue head Failure - NULL --*/ { PLLC_CCB pCcb; ASSERT(AdapterNumber < DOS_DLC_MAX_ADAPTERS); IF_DEBUG(CRITSEC) { DPUT1("PeekEvent: ENTERING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); if (Adapters[AdapterNumber].QueueElements) { pCcb = Adapters[AdapterNumber].EventQueueHead; IF_DEBUG(EVENT_QUEUE) { DPUT5("PeekEvent: CCB %x from adapter %d queue head. Head=%x Tail=%x Elements=%d\n", pCcb, AdapterNumber, Adapters[AdapterNumber].EventQueueHead, Adapters[AdapterNumber].EventQueueTail, Adapters[AdapterNumber].QueueElements ); } } else { pCcb = NULL; IF_DEBUG(EVENT_QUEUE) { DPUT1("PeekEvent: adapter %d queue is EMPTY!\n", AdapterNumber); } } IF_DEBUG(CRITSEC) { DPUT1("PeekEvent: LEAVING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); IF_DEBUG(CRITICAL) { CRITDUMP(("PeekEvent: returning %x\n", pCcb)); } return pCcb; } PLLC_CCB GetEvent( IN BYTE AdapterNumber ) /*++ Routine Description: Gets the next completed CCB from the head of the Event Queue. If the queue is empty (QueueElements == 0) then returns NULL. If there is a completed event in the queue, removes it and advances the queue head to the next element. The queue is accessed inside a critical section Arguments: AdapterNumber - which adapter's event queue to remove event from Return Value: PLLC_CCB Success - pointer to dequeued CCB Failure - NULL --*/ { PLLC_CCB pCcb; IF_DEBUG(CRITSEC) { DPUT1("GetEvent: ENTERING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); if (Adapters[AdapterNumber].QueueElements) { pCcb = Adapters[AdapterNumber].EventQueueHead; Adapters[AdapterNumber].EventQueueHead = pCcb->pNext; --Adapters[AdapterNumber].QueueElements; if (Adapters[AdapterNumber].QueueElements == 0) { Adapters[AdapterNumber].EventQueueTail = NULL; } IF_DEBUG(EVENT_QUEUE) { DPUT5("GetEvent: Removed %x from adapter %d EventQueue. Head=%x Tail=%x Elements=%d\n", pCcb, AdapterNumber, Adapters[AdapterNumber].EventQueueHead, Adapters[AdapterNumber].EventQueueTail, Adapters[AdapterNumber].QueueElements ); } } else { pCcb = NULL; IF_DEBUG(EVENT_QUEUE) { DPUT1("GetEvent: queue for adapter %d is EMPTY!\n", AdapterNumber); } } IF_DEBUG(CRITSEC) { DPUT1("GetEvent: LEAVING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); IF_DEBUG(CRITICAL) { CRITDUMP(("GetEvent: returning %x\n", pCcb)); } return pCcb; } VOID FlushEventQueue( IN BYTE AdapterNumber ) /*++ Routine Description: Removes all READ CCBs from the event queue. Arguments: None. Return Value: None. --*/ { PLLC_CCB pCcb; IF_DEBUG(CRITSEC) { DPUT1("FlushEventQueue: ENTERING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } EnterCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); #if DBG if (!Adapters[AdapterNumber].QueueElements) { DPUT("FlushEventQueue: queue is EMPTY!\n"); } #endif while (Adapters[AdapterNumber].QueueElements) { pCcb = Adapters[AdapterNumber].EventQueueHead; --Adapters[AdapterNumber].QueueElements; Adapters[AdapterNumber].EventQueueHead = pCcb->pNext; IF_DEBUG(EVENT_QUEUE) { DPUT5("FlushEventQueue: Removed %x from adapter %d EventQueue. Head=%x Tail=%x Elements=%d\n", pCcb, AdapterNumber, Adapters[AdapterNumber].EventQueueHead, Adapters[AdapterNumber].EventQueueTail, Adapters[AdapterNumber].QueueElements ); } // // BUGBUG - received frames? // LocalFree((HLOCAL)pCcb); IF_DEBUG(DLC_ALLOC) { DPUT1("FREE: freed block @ %x\n", pCcb); } Adapters[AdapterNumber].EventQueueTail = NULL; } IF_DEBUG(CRITSEC) { DPUT1("FlushEventQueue: ENTERING Adapters[%d].EventQueueCritSec\n", AdapterNumber ); } LeaveCriticalSection(&Adapters[AdapterNumber].EventQueueCritSec); } VOID RemoveDeadReceives( IN PLLC_CCB pCcb ) /*++ Routine Description: The receive command described by pCcb has completed (terminated). We must remove any queued reads completed by data receive which refer to this CCB Arguments: pCcb - pointer to RECEIVE CCB (NT) Return Value: None. --*/ { PLLC_CCB thisCcb; PLLC_CCB nextCcb; PLLC_CCB lastCcb = NULL; PLLC_CCB prevCcb = NULL; DWORD i; PDOS_ADAPTER pAdapter = &Adapters[pCcb->uchAdapterNumber]; PLLC_CCB* pQueue; // // remove any queued receives from the event queue. Note: There should NOT // be any. The reason: there can't be any data received after the associated // RECEIVE command has been cancelled or terminated. This is the theory, // anyway // EnterCriticalSection(&pAdapter->EventQueueCritSec); thisCcb = pAdapter->EventQueueHead; for (i = pAdapter->QueueElements; i; --i) { nextCcb = thisCcb->pNext; if (thisCcb->u.pParameterTable->Read.ulNotificationFlag == (ULONG)pCcb) { IF_DEBUG(EVENT_QUEUE) { DPUT5("RemoveDeadReceives: Removed %x from adapter %d EventQueue. Head=%x Tail=%x Elements=%d\n", thisCcb, pCcb->uchAdapterNumber, pAdapter->EventQueueHead, pAdapter->EventQueueTail, pAdapter->QueueElements ); } ReleaseReceiveResources(thisCcb); if (pAdapter->EventQueueHead == thisCcb) { pAdapter->EventQueueHead = nextCcb; } --pAdapter->QueueElements; lastCcb = thisCcb; } else { prevCcb = thisCcb; } thisCcb = nextCcb; } if (pAdapter->EventQueueTail == lastCcb) { pAdapter->EventQueueTail = prevCcb; } LeaveCriticalSection(&pAdapter->EventQueueCritSec); // // remove any queued deferred receives from the deferred I-Frame queue for // this SAP or link station // EnterCriticalSection(&pAdapter->LocalBusyCritSec); if (pAdapter->DeferredReceives) { ASSERT(pAdapter->FirstIndex != NO_LINKS_BUSY); ASSERT(pAdapter->LastIndex != NO_LINKS_BUSY); for (i = pAdapter->FirstIndex; i <= pAdapter->LastIndex; ++i) { pQueue = &pAdapter->LocalBusyInfo[i].Queue; for (thisCcb = *pQueue; thisCcb; thisCcb = thisCcb->pNext) { if (thisCcb->u.pParameterTable->Read.ulNotificationFlag == (ULONG)pCcb) { IF_DEBUG(EVENT_QUEUE) { DPUT3("RemoveDeadReceives: Removed %x from adapter %d BusyList. Queue=%x\n", thisCcb, pCcb->uchAdapterNumber, pAdapter->LocalBusyInfo[i].Queue ); } *pQueue = thisCcb->pNext; ReleaseReceiveResources(thisCcb); #if DBG --pAdapter->LocalBusyInfo[i].Depth; #endif thisCcb = *pQueue; } else { pQueue = &thisCcb->pNext; } } if (pAdapter->LocalBusyInfo[i].Queue == NULL) { pAdapter->LocalBusyInfo[i].State = NOT_BUSY; --pAdapter->DeferredReceives; } } // // reset the indicies // if (pAdapter->DeferredReceives) { for (i = pAdapter->FirstIndex; i <= pAdapter->LastIndex; ++i) { if (pAdapter->LocalBusyInfo[i].State != NOT_BUSY) { pAdapter->FirstIndex = i; break; } } for (i = pAdapter->LastIndex; i > pAdapter->FirstIndex; --i) { if (pAdapter->LocalBusyInfo[i].State != NOT_BUSY) { pAdapter->LastIndex = i; break; } } } else { pAdapter->FirstIndex = NO_LINKS_BUSY; pAdapter->LastIndex = NO_LINKS_BUSY; } } LeaveCriticalSection(&pAdapter->LocalBusyCritSec); } VOID ReleaseReceiveResources( IN PLLC_CCB pCcb ) /*++ Routine Description: Releases all resources used by a completed data receive READ CCB Arguments: pCcb - pointer to completed READ CCB. We have to return all received frames to buffer pool, and the READ CCB and parameter table to the proceess heap Return Value: None. --*/ { WORD buffersLeft; // // this is a data receive - return the data buffers to the pool // ASSERT(pCcb->u.pParameterTable->Read.uchEvent == LLC_EVENT_RECEIVE_DATA); BufferFree(pCcb->uchAdapterNumber, pCcb->u.pParameterTable->Read.Type.Event.pReceivedFrame, &buffersLeft ); // // free the READ CCB and parameter table // LocalFree((HLOCAL)pCcb); IF_DEBUG(DLC_ALLOC) { DPUT1("FREE: freed block @ %x\n", pCcb); } } VOID IssueHardwareInterrupt( VOID ) /*++ Routine Description: Issue a simulated hardware interrupt to the VDM. This routine exists because we were losing interrupts - seeing more calls to call_ica_hw_interrupt than calls to VrDlcHwInterrupt. Hence presumably simulated interrupts were being lost. So we now only have 1 un-acknowledged simulated interrupt outstanding at any one time. If we already have interrupts outstanding then we just increment a counter of pending interrupts. When we dismiss the current interrupt using the companion routine AcknowledgeHardwareInterrupt, we can generate at that point a queued interrupt Arguments: None. Return Value: None. --*/ { IF_DEBUG(CRITICAL) { CRITDUMP(("*** INT ***\n")); } IF_DEBUG(DLC_ASYNC) { DPUT("*** INT ***\n"); } // // increment the hardware interrupt counter under critical section control. // The counter starts at -1, so 0 means 1 interrupt outstanding. If we go // to >1 then we have interrupts queued and must wait until the current // one is dismissed // IF_DEBUG(CRITSEC) { DPUT("IssueHardwareInterrupt: ENTERING HardwareIntCritSec\n"); } EnterCriticalSection(&HardwareIntCritSec); ++HardwareIntsQueued; if (!HardwareIntsQueued) { VrQueueCompletionHandler(VrDlcHwInterrupt); //call_ica_hw_interrupt(NETWORK_ICA, NETWORK_LINE, 1); VrRaiseInterrupt(); } else { IF_DEBUG(CRITICAL) { CRITDUMP(("*** INT Queued (%d) ***\n", HardwareIntsQueued)); } } IF_DEBUG(CRITSEC) { DPUT("IssueHardwareInterrupt: LEAVING HardwareIntCritSec\n"); } LeaveCriticalSection(&HardwareIntCritSec); IF_DEBUG(DLC_ASYNC) { DPUT("*** EOF INT ***\n"); } } VOID AcknowledgeHardwareInterrupt( VOID ) /*++ Routine Description: The companion routine to IssueHardwareInterrupt. Here we just decrement the interrupt counter. If it is >= 0 then we still have interrupts pending, so we issue a new interrupt request. This seems to work - we don't lose interrupt requests to the VDM Arguments: None. Return Value: None. --*/ { #if DBG LONG deferredInts; #endif IF_DEBUG(CRITICAL) { CRITDUMP(("*** INT ACK ***\n")); } IF_DEBUG(DLC_ASYNC) { DPUT("*** INT ACK ***\n"); } // // decrement the interrupt counter within the critical section. If it goes // to -1 then we have no more outstanding hardware interrupt requests. If // it is > -1 then issue a new interrupt request // IF_DEBUG(CRITSEC) { DPUT("AcknowledgeHardwareInterrupt: ENTERING HardwareIntCritSec\n"); } EnterCriticalSection(&HardwareIntCritSec); --HardwareIntsQueued; #if DBG deferredInts = HardwareIntsQueued; #endif // // sanity check // ASSERT(HardwareIntsQueued >= -1); if (HardwareIntsQueued >= 0) { IF_DEBUG(CRITICAL) { CRITDUMP(("*** INT2 ***\n")); } VrQueueCompletionHandler(VrDlcHwInterrupt); //call_ica_hw_interrupt(NETWORK_ICA, NETWORK_LINE, 1); VrRaiseInterrupt(); } IF_DEBUG(CRITSEC) { DPUT("AcknowledgeHardwareInterrupt: LEAVING HardwareIntCritSec\n"); } LeaveCriticalSection(&HardwareIntCritSec); #if DBG IF_DEBUG(CRITICAL) { CRITDUMP(("*** EOF INT ACK (%d) ***\n", deferredInts)); } IF_DEBUG(DLC_ASYNC) { DPUT1("*** EOF INT ACK (%d) ***\n", deferredInts); } #endif } VOID CancelHardwareInterrupts( IN LONG Count ) /*++ Routine Description: Used to decrement the number of pending h/w interrupts. We need to do this when removing completed READ CCBs from an event queue for which h/w interrupts have been issued Arguments: Count - number of h/w interrupt requests to cancel. Used to aggregate the cancels, saving Count-1 calls to this routine & Enter & Leave critical section calls Return Value: None. --*/ { #if DBG LONG deferredInts; #endif IF_DEBUG(CRITICAL) { CRITDUMP(("*** CancelHardwareInterrupts(%d) ***\n", Count)); } IF_DEBUG(DLC_ASYNC) { DPUT1("*** CancelHardwareInterrupts(%d) ***\n", Count); } // // decrement the interrupt counter within the critical section. If it goes // to -1 then we have no more outstanding hardware interrupt requests. If // it is > -1 then issue a new interrupt request // IF_DEBUG(CRITSEC) { DPUT("CancelHardwareInterrupts: ENTERING HardwareIntCritSec\n"); } EnterCriticalSection(&HardwareIntCritSec); HardwareIntsQueued -= Count; #if DBG deferredInts = HardwareIntsQueued; #endif // // sanity check // ASSERT(HardwareIntsQueued >= -1); IF_DEBUG(CRITSEC) { DPUT("CancelHardwareInterrupts: LEAVING HardwareIntCritSec\n"); } LeaveCriticalSection(&HardwareIntCritSec); #if DBG IF_DEBUG(CRITICAL) { CRITDUMP(("*** EOF CancelHardwareInterrupts (%d) ***\n", deferredInts)); } IF_DEBUG(DLC_ASYNC) { DPUT1("*** EOF CancelHardwareInterrupts (%d) ***\n", deferredInts); } #endif }