Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3973 lines
116 KiB

/*++
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 <foo>' could be altered
--*/
#include <nt.h>
#include <ntrtl.h> // ASSERT, DbgPrint
#include <nturtl.h>
#include <windows.h>
#include <softpc.h> // x86 virtual machine definitions
#include <vrdlctab.h>
#include <vdmredir.h>
#include "vrdebug.h"
#include <dlcapi.h> // Official DLC API definition
#include <ntdddlc.h> // IOCTL commands
#include <dlcio.h> // Internal IOCTL API interface structures
#include "vrdlc.h"
#include "vrdlcdbg.h"
#define BOOL
#include <insignia.h> // Insignia defines
#include <xt.h> // half_word
#include <ica.h> // ica_hw_interrupt
#include <vrica.h> // 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
}