/* semfisdd.c */

/* Device Driver for IBM SDLC adapter.                                       */

#ifndef IMADRIVER
#error - driver code requires IMADRIVER be defined on command line
#endif

#include <stddef.h>
#include <ntddk.h>

#include <semfismi.h>
#include <semfistr.h>
#include <semfisnt.h>
#include <semfisdm.h>
#include <semfis73.h>
#include <semfissp.h>
#include <semfisis.h>
#include <semfisrb.h>
#include <semfisfs.h>
#include <seclink.h>
#include <semfisdo.h>
#include <semfispr.h>
#include <semfisda.h>
#include <semfisel.h>


// Temporary Usage : function prototype declaration should be pulled in

extern NTSTATUS
ZwOpenKey(
    OUT PHANDLE KeyHandle,
    IN ACCESS_MASK DesiredAccess,
    IN POBJECT_ATTRIBUTES ObjectAttributes
    );



/* semfiddd.c - NT Device Driver for IBM SDLC adapter                        */


/**PROC+**********************************************************************/
/*                                                                           */
/* Name         AdapterExistenceCheck (                                      */
/*                            PCONFIGDATA pConfigData                        */
/*                            );                                             */
/*                                                                           */
/* Purpose      Hardware-specific of initialising                            */
/*                                                                           */
/* Params    IN pConfigData: the config data record for adapter to be checked*/
/*                                                                           */
/* Return Value BOOLean:    TRUE if the adapter is present and responding    */
/*                                                                           */
/* Side Effect: For MPAA, sets dma channel in config data!  This will then   */
/*              immediately be copied to the Device Object config data ( so  */
/*              no danger of it being used by another configdata record in   */
/*              error.                                                       */
/*                                                                           */
/* Operation:   Tries to read ports confirming the presence of the adapter   */
/*              This is a trivial check of one port, suitable for start of   */
/*              day checks just to tell us if the device should be created.  */
/*              Real hardware initialisation is done on open.                */
/*                                                                           */
/* Notes        We do as little as we can get away with here because a) we   */
/*              don't have a full device extension set up and b) to avoid    */
/*              causing interrupts hopefully.                                */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean AdapterExistenceCheck (
                              PCONFIGDATA pConfigData
                              )
{
  BOOLean FoundMPA;

  if (pConfigData->AdapterType EQ AT_SDLC  ||
      pConfigData->AdapterType EQ AT_MPCA1 ||
      pConfigData->AdapterType EQ AT_MPCA2)
  {
    if (pConfigData->MPCAModePort NE 0)
    {
      /***********************************************************************/
      /* This is an MPCA adapter, we hope.  Disable and reenable it          */
      /***********************************************************************/
      WR_N_DELAY (pConfigData->MPCAModePort, AC_MPCAD);
      WR_N_DELAY (pConfigData->MPCAModePort, pConfigData->MPCAModeValue);

      TRACE_DATABYTE (Mpc, pConfigData->MPCAModeValue);
    }

    /*************************************************************************/
    /* There isn't a reliable way of determining if the MPCA/SDLC cards      */
    /* are present.  So at this stage say they are and let non-existence     */
    /* show up later as an Open error.                                       */
    /*************************************************************************/

    return (TRUE);
  }

  if (pConfigData->AdapterType EQ AT_MPAA1 ||
      pConfigData->AdapterType EQ AT_MPAA2)
  {
    IO_ADDRESS AdapterSelector;

    for (
         FoundMPA = FALSE,
           AdapterSelector = (IO_ADDRESS) 8;
         AdapterSelector < (IO_ADDRESS) 0x10;
         AdapterSelector++
        )
    {
      WR_N_DELAY (POS_Adapter_Select, (UCHAR) AdapterSelector);

      if (IO_IN (POS_AdapterID_HiByte)   EQ HIBYTE(POS_MPAATypeID) &&
          IO_IN (POS_AdapterID_LoByte)   EQ LOBYTE(POS_MPAATypeID) &&
          ((UCHAR)
           (IO_IN(POS_ConfigByte2) & 0x1F)
          )                              EQ pConfigData->MPAAAdapterIdentifier)
      {
        FoundMPA = TRUE;
        pConfigData->DMAChannel = (UCHAR) (IO_IN (POS_ConfigByte3) & 7);
        break;
      }
    }

    WR_N_DELAY (POS_Adapter_Select, 7);

    return (FoundMPA);
  }
}

/*****************************************************************************/
/*                                                                           */
/* Name         AdapterReset                                                     */
/*                                                                           */
/* Purpose      Reset an adapter and prime it                                */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT None                                                         */
/*                                                                           */
/* Modified:    31/03/88 Initial coding                                      */
/*                                                                           */
/*****************************************************************************/

void AdapterReset (PDX pDX)
{
  TRACE_EVENT (<ARs);                   /* every body needs one...           */

  pDX->HardwareError = FALSE;           /* if user screwed up on hardware    */
                                        /* previously, we'll let him try     */
                                        /* again this time                   */

  /***************************************************************************/
  /* If an MPCA has been configured then write out the mode set byte.        */
  /***************************************************************************/

  if (pDX->ConfigData.MPCAModePort NE 0)
  {
    WR_N_DELAY (pDX->ConfigData.MPCAModePort, pDX->ConfigData.MPCAModeValue);
    TRACE_DATABYTE (Mpc, pDX->ConfigData.MPCAModeValue);
  }

  /***************************************************************************/
  /* Now mode set the 8255.                                                  */
  /***************************************************************************/

  WR_N_DELAY (pDX->ADAPTERBASE+AR_8255, A55_ModeSet);

  IO_OUT (pDX->ADAPTERBASE+AR_8255B,A55_Reset8273On);
  KeStallExecutionProcessor (10L);        /* a long delay for reset on         */

  WR_N_DELAY (pDX->ADAPTERBASE+AR_8255B,A55_Reset8273Off);

  IO_OUT (pDX->ADAPTERBASE+AR_8255C, A55_InitPortC);

  TRACE_EVENT (ARs>);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         AllocateDMAMemory (                                          */
/*                                 int         BufferSize                    */
/*                                PVOID      *pBufferPtr                     */
/*                                PMDL       *pMdl                           */
/*                                PHYSICAL_A *pPhysAddr                      */
/*                                ULONG       *ErrorInformation              */
/*                                );                                         */
/*                                                                           */
/* Purpose      Modularise the steps to allocate/deallocate and check DMA mem*/
/*              Also, make freeing up easy (like, free everything on failure)*/
/*                                                                           */
/* Params   IN  BufferSize                                                   */
/*          OUT BufferPtr   pointer to actual memory                         */
/*          OUT pMdl        the allocated mdl                                */
/*          OUT pPhysAddr   physical memory's address                        */
/*          OUT ErrorInformation: suitable for IoStatus.Information          */
/*                                                                           */
/* Return Value BOOLean:    TRUE if everything allocated OK                  */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean AllocateDMAMemory (
                      ULONG        BufferSize,
                      PVOID      *pBufferPtr,
                      PMDL      *ppMdl,
                      PHYSICAL_ADDRESS * pPhysAddr,
                      ULONG       *ErrorInformation
                     )
{
  BOOLean ReturnCode = TRUE;

  TRACE_EVENT (<Dma);

  *pBufferPtr = NULL;
  *ppMdl      = NULL;

  *pBufferPtr = MmAllocateContiguousMemory (BufferSize,
                          RtlConvertUlongToLargeInteger (0xFFFFFFL) );
  if (*pBufferPtr EQ NULL)
  {
    ReturnCode = FALSE;
    *ErrorInformation = IO_ERR_CANT_ALLOCATE_MEMORY;
  }

  if (ReturnCode)
  {
    *ppMdl = IoAllocateMdl(*pBufferPtr,
                           BufferSize,
                           FALSE,       /* this is not a secondary buffer    */
                           FALSE,       /* no charge to quota                */
                           (PIRP)NULL   /* not attached to IRP               */
                        );
    if (*ppMdl EQ NULL)
    {
      ReturnCode = FALSE;
      *ErrorInformation = IO_ERR_CANT_ALLOCATE_MDL;
    }
  }
  if (ReturnCode)
  {
    MmProbeAndLockPages (*ppMdl, KernelMode, IoModifyAccess);

    *pPhysAddr = MmGetPhysicalAddress(*pBufferPtr);
    ASSERT ((pPhysAddr->LowPart | pPhysAddr->HighPart) NE 0L);

    /* check our understanding of MmAllocateContiguousMemory */

    ASSERT (BITSOFF(pPhysAddr->LowPart, 0xFF000000L));
    if (DMACrosses64K(pPhysAddr->LowPart, BufferSize))
    {
      ReturnCode = FALSE;
      *ErrorInformation = IO_ERR_DMA_BUFFER_UNUSABLE;
    }
  }
  if (!ReturnCode)
  {
    if (*ppMdl NE NULL)
    {
      IoFreeMdl (*ppMdl);
      *ppMdl     = NULL;
    }
    if (*pBufferPtr NE NULL)
    {
      MmFreeContiguousMemory (*pBufferPtr);
      *pBufferPtr = NULL;
    }
  }

  TRACE_EVENT (Dma>);
  return (ReturnCode);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        Close8273Sequence (PDX pDX)                                  */
/*                                                                           */
/* Purpose:     To gracefully close down the 8273                            */
/*                                                                           */
/* Params:   IN pDX                                                          */
/*                                                                           */
/* Implicit input: Must not be called at interrupt or Synchronised level!!!  */
/*                                                                           */
/* Return Value:none                                                         */
/*                                                                           */
/**PROC-**********************************************************************/

void Close8273Sequence (PDX pDX)
{
  TRACE_EVENT (<C73);
    /*************************************************************************/
    /* Clear down the transmitter and receiver and release the h/w.          */
    /*************************************************************************/

    IoctlAbortTransmitter (pDX);
    IoctlAbortReceiver    (pDX);

    KeSynchronizeExecution(pDX->Interrupt, SynchReset8273, (PVOID) pDX);

    /*************************************************************************/
    /* Drop back down to non-interrupt level to allow pending interrupts in. */
    /* The SynchReset8273 call above sets the Closing flag                   */
    /*************************************************************************/

    KeSynchronizeExecution(pDX->Interrupt, SynchTerminateAdapter, (PVOID) pDX);

    pDX->AdapterIsClosing      = FALSE;
  TRACE_EVENT (C73>);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        CompleteIoRequest (PIRP pIrp)                                */
/*                                                                           */
/* Purpose:     The Io request must be completed for each entry point.       */
/*                                                                           */
/* Params:   IN pIrp            The Io Request to be completed               */
/*                                                                           */
/* Return Value:none                                                         */
/*                                                                           */
/* Operation:   The Io request must be completed at despatch level           */
/*                                                                           */
/**PROC-**********************************************************************/

void CompleteIoRequest (PIRP pIrp)
{
  /***************************************************************************/
  /* IoCompleteRequest used to need a RaiseIrql ... but no longer does.      */
  /* Keep this as a subroutine for now - could become a macro later.         */
  /***************************************************************************/

//KIRQL PreviousIrql;

//KeRaiseIrql (DISPATCH_LEVEL, &PreviousIrql);

  IoCompleteRequest (pIrp, 0);         /* 0 => no priority boost for APC    */

//KeLowerIrql (PreviousIrql);

  TRACE_EVENT (S+I:);
  TRACE_DWORD (pIrp->IoStatus.Status);
  TRACE_DWORD (pIrp->IoStatus.Information);

}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        DeviceInit (                                                 */
/*                          PDRIVER_OBJECT DriverObject                      */
/*                          CHAR *         DeviceName,                       */
/*                          PCONFIGDATA   pConfigData,                       */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     Initialise the next device (from GetDriverSpec)              */
/*              Called for each configured device from DriverEntry.          */
/*                                                                           */
/* Params:   IN DriverObject:   Us!                                          */
/*           IN DeviceName:     COMDL$0x (from GetDriverSpec())              */
/*           IN pConfigData:    config data to use                           */
/*                                                                           */
/* Return Value:BOOLean:        TRUE if device successfully initialised      */
/*                                                                           */
/* Operation:   The general principle is: do as little as possible to here - */
/*              just establish the device exists and allocate a device       */
/*              object for it.  Any allocation of resources is done on open  */
/*              to avoid using resources unnecessarily.  This applies to     */
/*              interrupt objects,                                           */
/*                                                                           */
/*              0. On input, driver and flavour name read from cfg register  */
/*                                                                           */
/*              1. If                                                        */
/*                 |--> HardwareExistenceCheck                               */
/*                 fails,                                                    */
/*                   return failure                                          */
/*                                                                           */
/*              2. Init for NT                                               */
/*                 - allocate DeviceObject                                   */
/*                   - DO_DIRECT_IO                                          */
/*                 - IoInitializeDpcRequest                                  */
/*                                                                           */
/*              3. Init the device extension fields we need to know now:     */
/*                 - DeviceIsOpen = FALSE                                    */
/*                 - pointer to config data                                  */
/*                                                                           */
/* Note:        The parallel driver returns an NT status from an equivalent  */
/*              routine.  Eventually, we may do the same via ErrorCode param.*/
/*              But for now we don't set error code, because the NTSTATUS    */
/*              returned from the parallel driver routine is ignored.        */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean DeviceInit (
                    PDRIVER_OBJECT  pDriverObject,
                    CHAR *           DeviceName,
                    PCONFIGDATA     pConfigData
                   )
{
  STRING         NameString;            /* counted string for NT             */
  PDEVICE_OBJECT pDeviceObject;         /* allocated by NT for our device    */
  PDX            pDX;                   /* pointer to our device extension   */
  NTSTATUS       Status;
  UNICODE_STRING UniNameString;         /* same thing in UniCode             */

  if (!AdapterExistenceCheck(pConfigData))
  {
    TRACE_EVENT (DiNa);                 /* DeviceInit: No adapter            */
    DEBUG_PRINT (("IBMSYNC: Existence check failed for Adapter %s\n",
                                                  pConfigData->FlavourName));
    return (FALSE);                     /* no device so can't initialise devc*/
  }

  RtlInitString (&NameString, DeviceName);
  Status = RtlAnsiStringToUnicodeString (&UniNameString, &NameString, TRUE);
  ASSERT(NT_SUCCESS(Status));           /* UniNameString now has unicode dvnm*/

  Status = IoCreateDevice(
                 pDriverObject,
                 sizeof( IBMSYNC_DEVICE_EXTENSION ),
                 &UniNameString,
                 FILE_DEVICE_DATALINK,
                 0,
                 TRUE,                  /* open exclusively                  */
                 &pDeviceObject
                 );
  RtlFreeUnicodeString (&UniNameString);

  /***************************************************************************/
  /* Note that we cannot log an error here since we do not hace a Device     */
  /* Object.                                                                 */
  /***************************************************************************/
  if (!NT_SUCCESS(Status))
  {
    TRACE_EVENT (DiNd);                 /* DeviceInit: No device             */
    DEBUG_PRINT (("IBMSYNC: Cannot create Device : %s\n", DeviceName));
    return (FALSE);
  }

  /***************************************************************************/
  /* We have a device object OK - which is the main thing to do at device    */
  /* init time.  Other initialisation stuff we leave till device opened.     */
  /***************************************************************************/

  pDeviceObject->Flags       |=DO_DIRECT_IO;
  pDX                        = pDeviceObject->DeviceExtension;
  pDX->DeviceIsOpen          = FALSE;
  pDX->AdapterIsClosing      = FALSE;
  pDX->PowerFailed           = FALSE;

  /***************************************************************************/
  /* In theory, we don't have to do this config data copying (we could just  */
  /* use a pointer to it, but that would be a) slower; b) tedious to code    */
  /***************************************************************************/

  pDX->pDeviceObject         = pDeviceObject;
  pDX->ConfigData            = *pConfigData;
  pDX->Name[0]               = 'A';         /* adapter                       */
  pDX->Name[1]               = pConfigData->FlavourName[4];

  IoInitializeDpcRequest(pDeviceObject, DPCRoutine);

  DEBUG_PRINT (("IBMSYNC: Created Device : %s\n", DeviceName));
  return (TRUE);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        DPCRoutine                                                   */
/*                                                                           */
/* Purpose:                                                                  */
/*                                                                           */
/* Params:                                                                   */
/*                                                                           */
/* Return Value:None                                                         */
/*                                                                           */
/**PROC-**********************************************************************/


VOID  DPCRoutine(
                 IN PKDPC          pDpc,
                 IN PDEVICE_OBJECT pDeviceObject,
                 IN PIRP           pIrp,
                 IN PVOID          DeferredContext
                )
{
  PDX           pDX          = pDeviceObject->DeviceExtension;

  TRACE_EVENT (<DPC);

  UNREFERENCED_PARAMETER (pDpc);
  UNREFERENCED_PARAMETER (pIrp);
  UNREFERENCED_PARAMETER (DeferredContext);

  ASSERT (pDX->DeviceIsOpen);

  if (pDX->DeviceIsOpen &&
      pDX->DPCAction & DPC_ACTION_PULSE &&
      pDX->pUserEvent NE NULL)
  {
//    KePulseEvent (pDX->pUserEvent, 0, FALSE); /* 0 pri boost, no wait        */
//    KePulseEvent missing from API?
      KeSetEvent (pDX->pUserEvent, 0, FALSE);
  }
  pDX->DPCAction = 0;                   /* we've done all requested actions  */

  TRACE_EVENT (DPC>);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        DriverEntry(                                                 */
/*                          PDRIVER_OBJECT DriverObject                      */
/*                          IN PUNICODE_STRING RegistryPath                  */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     As defined by NT.  Called once only when NT loads driver     */
/*                                                                           */
/* Params:      PDRIVER_OBJECT: pointer to us                                */
/*                                                                           */
/* Return Value:NTSTATUS:       STATUS_SUCCESS / STATUS_UNSUCCESSFUL         */
/*                              Unsuccessful if no devices initialised       */
/*                                                                           */
/**PROC-**********************************************************************/


NTSTATUS DriverEntry (IN PDRIVER_OBJECT DriverObject,
                      IN PUNICODE_STRING RegistryPath
                     )
{
  CHAR       DeviceName [80];
  BOOLean    GotADevice = FALSE;
  NTSTATUS   RetStatus  = STATUS_UNSUCCESSFUL;
  CONFIGDATA *pConfigData;

//  _asm int 3;

  TRACE_INIT();
  TRACE_EVENT(Entr);

  while (GetDriverSpec (DeviceName, &pConfigData))
  {
    if (DeviceInit (DriverObject, DeviceName, pConfigData))
    {
      GotADevice = TRUE;
      TRACE_EVENT (DevI);
    }
  }

  if ((GotADevice) && (GetInterfaceType (DriverObject)))
  {
    DriverObject->MajorFunction[IRP_MJ_CLOSE]           = EntryPointClose;
    DriverObject->MajorFunction[IRP_MJ_CREATE]          = EntryPointOpen;
    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]  = EntryPointDevIoctl;
    DriverObject->DriverUnload                          = EntryPointUnload;

    RetStatus = STATUS_SUCCESS;
  }
  else
  {
    TRACE_EVENT (DrvF);
  }

  return (RetStatus);
}

/*****************************************************************************/
/*                                                                           */
/* Name         EntryPointClose                                                     */
/*                                                                           */
/* Purpose      Close Request Packet Handler                                 */
/*                                                                           */
/*              This is the CLOSE processor.  It flushes pending requests    */
/*              and ensures that the hardware and LCB fields are tidily      */
/*              closed down.                                                 */
/*                                                                           */
/* Params    IN None                                                         */
/*                                                                           */
/*          OUT Link hardware closed and pending requests flushed.           */
/*                                                                           */
/*****************************************************************************/

NTSTATUS EntryPointClose
                         (
                          PDEVICE_OBJECT pDeviceObject,
                          PIRP           pIrp
                         )
{
  PDX           pDX          = pDeviceObject->DeviceExtension;

  NTSTATUS Status;

  TRACE_NEWLINE();
  TRACE_EVENT ({EPC);

  pIrp->IoStatus.Information = 0;

  if (pDX->DeviceIsOpen)                /* use can close OK                  */
  {
    pIrp->IoStatus.Status = STATUS_SUCCESS;

    /*************************************************************************/
    /* Clear down the transmitter and receiver and release the h/w.          */
    /*************************************************************************/

    Close8273Sequence (pDX);            /* orderly close-down of adapter     */

    IoDisconnectInterrupt (pDX->Interrupt);

    if (pDX->ConfigData.Irql2)
    {
      IoDisconnectInterrupt (pDX->Interrupt2);
    }

                       TRACE_EVENT (EPC1);
    MmUnlockPages          (pDX->RcvInfo.pRcvMdl);
                       TRACE_EVENT (EPC2);
    IoFreeMdl              (pDX->RcvInfo.pRcvMdl);
                       TRACE_EVENT (EPC3);
    MmFreeContiguousMemory (pDX->RcvInfo.pRcvBufArray);

                       TRACE_EVENT (EPC4);
    MmUnlockPages          (pDX->pSendMdl);
                       TRACE_EVENT (EPC5);
    IoFreeMdl              (pDX->pSendMdl);
                       TRACE_EVENT (EPC6);
    pDX->pSendMdl         = NULL;
    MmFreeContiguousMemory (pDX->pSendBuf);
                       TRACE_EVENT (EPC7);
    pDX->pSendBuf         = NULL;
                       TRACE_EVENT (EPC8);

  //  if (pDX->pIRMdl != NULL)                         /*IRMdl?*/
  //  {                                                /*IRMdl?*/
  //    MmUnmapLockedPages (pDX->pIR, pDX->pIRMdl);    /*IRMdl?*/
  //    MmUnlockPages      (pDX->pIRMdl);              /*IRMdl?*/
  //                     TRACE_EVENT (EPC9);           /*IRMdl?*/
  //    IoFreeMdl          (pDX->pIRMdl);              /*IRMdl?*/
  //                     TRACE_EVENT (EPC0);           /*IRMdl?*/
  //    pDX->pIRMdl = NULL;                            /*IRMdl?*/
  //  }                                                /*IRMdl?*/

    pDX->pIR    = &pDX->OurIR;

    pDX->GrabbedResources = 0;
    pDX->DeviceIsOpen     = FALSE;
    pDX->pUserEvent       = NULL;
  }
  else
  {
    pIrp->IoStatus.Status = STATUS_FILE_CLOSED;
  }

  Status = pIrp->IoStatus.Status;
  CompleteIoRequest (pIrp);

  TRACE_EVENT (EPC});
  return (Status);
}


/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        EntryPointDevIoctl (                                         */
/*                          PDEVICE_OBJECT pDeviceObject                     */
/*                          PIRP           pIrp                              */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     Main ioctl entry point.                                      */
/*                                                                           */
/* Params:   IN DeviceObject:   Our device.                                  */
/*           IN PIrp            The IRP in question.                         */
/*                                                                           */
/* Return Value:depends on called routine                                    */
/*                                                                           */
/* Operation:                                                                */
/*                                                                           */
/**PROC-**********************************************************************/

NTSTATUS EntryPointDevIoctl (
                             PDEVICE_OBJECT pDeviceObject,
                             PIRP           pIrp
                            )
{
  BOOLean       OKToContinue = TRUE;
  PDX           pDX          = pDeviceObject->DeviceExtension;
  PIO_STACK_LOCATION
                pIrpSp       = IoGetCurrentIrpStackLocation(pIrp);
  UCHAR  *      UserBufferPtr;
#ifdef IBMSYNC_TRACE
  UCHAR       * OriginalTrcPtr = TrcPtr;
  BOOLEAN       ResetTrcPtr    = FALSE;
#endif

  ASSERT (pDX->DeviceIsOpen);           /* open before any Ioctls please     */

  TRACE_NEWLINE();
  TRACE_EVENT({EPD);
  TRACE_DWORD (pDeviceObject);
  TRACE_DWORD (pDX);
  TRACE_DWORD (pIrp);
  TRACE_DWORD (pIrpSp->IRS_CODE);
  TRACE_ACTION (Op,CAST(pDX->ADAPTERBASE,ULONG) & 0xFFFF);

  pDX->IoctlRetStatus = STATUS_SUCCESS;
  pDX->Information    = 0L;
  pDX->IoctlCurrentIrp= pIrp;

  /***************************************************************************/
  /* The subroutines return the success/fail indication as their return      */
  /* value, and additional information in pDX->Information                   */
  /***************************************************************************/

  switch (pIrpSp->IRS_CODE)
  {

//  case IoctlCodeSetInterfaceRecord                               /*IRMdl?*/
//                              : IoctlSetInterfaceRecord(pDX);    /*IRMdl?*/
//                                break;                           /*IRMdl?*/

    case IoctlCodeReadInterfaceRecord                              /*IRMdl?*/
                                : if (pIrpSp->IRS_OUTLEN NE sizeof(IR))
                                  {
                                    pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
                                    pDX->Information = IO_ERR_READ_IR_BUFFER_WRONG_SIZE;
                                  }
                                  else
                                  {
#ifdef IBMSYNC_TRACE
                                    ResetTrcPtr = TRUE;
#endif
                                    UserBufferPtr = MmMapLockedPages
                                               (pIrp->MdlAddress, KernelMode);
                                    memcpy (UserBufferPtr, pDX->pIR, sizeof(IR));
                                  }
                                  break;

    case IoctlCodeSetEvent      : if (pIrp->UserEvent NE NULL)
                                  {
                                    pDX->pUserEvent = pIrp->UserEvent;
                                    KeSetEvent (pDX->pUserEvent, 0, FALSE);
                                  }
                                  else
                                  {
                                    pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
                                    pDX->Information = IO_ERR_SET_EVENT_NO_EVENT;
                                  }
                                  break;

    case IoctlCodeSetLinkChar   : KeSynchronizeExecution
                                       ( pDX->Interrupt,
                                         (PKSYNCHRONIZE_ROUTINE)
                                                            IoctlSetLinkConfig,
                                         (PVOID) pDX
                                       );
                                  break;

    case IoctlCodeRxFrame       : KeSynchronizeExecution
                                       ( pDX->Interrupt,
                                         (PKSYNCHRONIZE_ROUTINE) IoctlRxFrame,
                                         (PVOID) pDX
                                       );
                                  break;

    case IoctlCodeAbortReceiver : IoctlAbortReceiver   (pDX);
                                  break;

    case IoctlCodeGetV24        : KeSynchronizeExecution
                                      ( pDX->Interrupt,
                                        (PKSYNCHRONIZE_ROUTINE) GetV24Input,
                                        (PVOID) pDX
                                      );
                                  /* data-> pDX->IR; no return status */
#ifdef IBMSYNC_TRACE
                                  {
                                    static UCHAR LastV24In = 0;
                                    ResetTrcPtr = CAST(
                                                  pDX->pIR->V24In EQ LastV24In,
                                                       BOOLEAN);
                                    LastV24In = pDX->pIR->V24In;
                                  }
#endif
                                  break;

    case IoctlCodeTxFrame       : KeSynchronizeExecution
                                      ( pDX->Interrupt,
                                        (PKSYNCHRONIZE_ROUTINE) IoctlTxFrame,
                                        (PVOID) pDX
                                      );
                                  break;

    case IoctlCodeAbortTransmit : KeSynchronizeExecution
                                      ( pDX->Interrupt,
                                        (PKSYNCHRONIZE_ROUTINE)
                                                        IoctlAbortTransmitter,
                                        (PVOID) pDX
                                     );
                                  break;

    case IoctlCodeSetV24        : if (!KeSynchronizeExecution
                                      ( pDX->Interrupt,
                                        (PKSYNCHRONIZE_ROUTINE)
                                                             IoctlSetV24Output,
                                        (PVOID) pDX
                                      )
                                     )
                                  {
                                    pDX->IoctlRetStatus = STATUS_DATA_ERROR;
                                    pDX->Information = IO_ERR_HARDWARE_CMD_TIMEOUT_1;
                                  }
                                  break;

    default:
//                                _asm int 3;         // break into kernel debugger
                                  pDX->IoctlRetStatus =
                                                    STATUS_INVALID_DEVICE_REQUEST;
                                  pDX->Information = IO_ERR_INVALID_IOCTL_CODE;
  }

  if (NT_SUCCESS(pDX->IoctlRetStatus))
  {
    // The Ioctl worked OK, but how about 8273 commands - check the
    // timeout flag, which is set by 8273 WAITUNTIL macro.  Signal a fatal
    // hardware error if it's set.

    if (pDX->HardwareError)
    {
      pDX->IoctlRetStatus = STATUS_DATA_ERROR;
      pDX->Information    = IO_ERR_HARDWARE_CMD_TIMEOUT_2;
    }
  }
  else
  {
    TRACE_NTFAILURE(pDX->IoctlRetStatus);
    TRACE_ACTION   (Ii,pDX->Information & 0xFFFF);
  }

  /***************************************************************************/
  /* Note that at this point a non-zero value of pDX->Information does not   */
  /* mean an error - it could be a byte count of info returned in a buffer.  */
  /***************************************************************************/
  if (!NT_SUCCESS(pDX->IoctlRetStatus))
  {
    LogDriverError( pDeviceObject,
                    pDX->IoctlRetStatus,
                    pDX->Information,
                    pIrpSp->MajorFunction,
                    pIrpSp->IRS_CODE );
  }

  pIrp->IoStatus.Status      = pDX->IoctlRetStatus;
  pIrp->IoStatus.Information = pDX->Information;

  TRACE_EVENT(EPD});
  CompleteIoRequest (pIrp);

#ifdef IBMSYNC_TRACE
  if (ResetTrcPtr)
    TrcPtr = OriginalTrcPtr;
#endif

  return(pDX->IoctlRetStatus);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        EntryPointISR (                                              */
/*                             PKINTERRUPT pInterrupt                        */
/*                             PVOID       Context = pDeviceObject           */
/*                            )                                              */
/*                                                                           */
/* Purpose:     Handle interrupt.                                            */
/*                                                                           */
/* Params:   IN pInterrupt                                                   */
/*           IN Context                                                      */
/*                                                                           */
/* Return Value:BOOLean:        TRUE if device successfully initialised      */
/*                                                                           */
/* Operation:                                                                */
/*                                                                           */
/* Notes     1. The OS/2 device driver shares interrupts; this version does  */
/*              not because the interrupt object needs to be attached to a   */
/*              device.  I hope we can get away it.                          */
/*           2. Return value is meant to indicate some powerfail stuff ...   */
/*              not bothering with it for now.                               */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLEAN EntryPointISR (
                       PKINTERRUPT pInterrupt,
                       PVOID       Context
                      )
{
  PDEVICE_OBJECT pDeviceObject= (PDEVICE_OBJECT) Context;
  PDX            pDX          = pDeviceObject->DeviceExtension;
  BOOLean        OriginalDPCAction  = pDX->DPCAction;
  UCHAR          Status;                /* the 8273 status, not the NT Status*/
  BOOLEAN        ReturnValue;

  UNREFERENCED_PARAMETER (pInterrupt);

//TRACE_NEWLINE();
//TRACE_EVENT (<ISR);

  ASSERT (pDX != NULL);
//TRACE_OBJECT(Ad,(*pDX));

  ASSERT (pDX->DeviceIsOpen);

  if (pDX->AdapterIsClosing ||
      !pDX->DeviceIsOpen)
  {
    // We reset the 8273 but there was an interrupt still pending... ignore it.
    // This is safe because by resetting the 8273 we should have removed the
    // cause of the interrupt.
    return(TRUE);
  }

  ReturnValue = CAST (                                                         \
                (IO_IN (pDX->ADAPTERBASE+AR_8273S) & (AS_RXINT|AS_TXINT)) != 0,\
                 BOOLEAN                                                       \
                );              /* return true if we expected the interrupt  */
  do                            /* while we have an interrupt result avail.  */
  {

    /*************************************************************************/
    /* To be on the safe side, don't do anything further until the command   */
    /* busy bit in the status register has gone off.                         */
    /*************************************************************************/
    WAITUNTIL (pDX, AS_CMBSY, EQ, 0);   /* wait for busy bit to go off       */

    Status = IO_IN (pDX->ADAPTERBASE+AR_8273S);
    ASSERT (BITSOFF(Status, AS_CRBFF | AS_CPBFF | AS_CMBFF | AS_CMBSY));

    if (Status & AS_RXINT)      /* receiver action                           */
    {
      /***********************************************************************/
      /* Note we don't stop DMA on receiver.  The design of the buffers has  */
      /* taken into account receiving a full 8 frames+RR in DMA mode - so    */
      /* that we can take account of the 8273 feature of continuing to       */
      /* recieve when in DMA mode.                                           */
      /***********************************************************************/

      if (Status & AS_RXIRA)
      {
        /*********************************************************************/
        /* We have a receiver result.  Read the result bytes into the        */
        /* device extension and after reading, action immediately            */
        /*********************************************************************/
       for (pDX->RxResultCount = 0;
            pDX->RxResultCount < (sizeof (pDX->RxResultBuffer) - 1);
                                       /* last byte never gets filled in !  */
           )
       {
         pDX->RxResultBuffer[pDX->RxResultCount++] =
                                         IO_IN (pDX->ADAPTERBASE + AR_8273R);

         KeStallExecutionProcessor (1L);   /* let the chip settle down again*/
         WAITUNTIL(pDX, (AS_RXIRA | AS_RXINT), NE, AS_RXINT);

         TRACE_DATABYTE (RxR, pDX->RxResultBuffer[pDX->RxResultCount]);

         /*******************************************************************/
         /* either the INT bit went off or IRA went on                      */
         /* If INT went off, ignore the rest - even if IRA went on          */
         /* (shouldn't have)                                                */
         /*******************************************************************/

         if (BITSOFF(LastWaitUntilStatus, AS_RXINT))
           break;
       }

        /*********************************************************************/
        /* We do nothing if receiver is not active.  This sitn. arises when  */
        /*                                                                   */
        /* - we prepare to disable the recver (start into synchronized execn)*/
        /* - a result becomes available                                      */
        /* - the synchronized execution carries on and disables the receiver */
        /*                                                                   */
        /* This only arises when we are in DMA mode - for SDLC.  (For PIO,   */
        /* receiver kept active).  Make life simple by ignoring result       */
        /* otherwise we get FSM's knickers in a twist.                       */
        /*********************************************************************/

        if (pDX->RxFSMCurState EQ RxFSMStateReady)
        {
          while (pDX->RxResultCount > 0)
          {
            /*****************************************************************/
            /* There will either be one or two result groups set.  The case  */
            /* of two groups arises when one result has occurred but not     */
            /* been read by the time another result comes in.                */
            /*                                                               */
            /* The second result is only one byte so we special case this to */
            /* make it easier get the rxresults down to the start again.     */
            /*****************************************************************/

            if (pDX->RxResultBuffer[0]  EQ ARxR_A1OK ||/* A1/A2 match, rcv OK */
                pDX->RxResultBuffer[0]  EQ ARxR_A2OK)
            {
              RxFSMEvent (pDX, RxFSMInputGoodReceive);
              pDX->RxResultBuffer[0] = pDX->RxResultBuffer[5];
              pDX->RxResultCount -= 5;
            }
            else
            {
              RxFSMEvent (pDX, RxFSMInputReceiverError);
              pDX->RxResultBuffer[0] = pDX->RxResultBuffer[1];
              pDX->RxResultCount -= 1;
            }
          }
        }
      }
      else
      {
        /*********************************************************************/
        /* IRA not set - no result available, must be for received character */
        /*                                                                   */
        /* Not that neither here nor below in tx do check for overrunning    */
        /* the buffer - we just believe 8273 is OK (and it hasn't failed in  */
        /* this area yet.  ASSERTions to guard against buffer overruns       */
        /* should be done in completion code.                                */
        /*********************************************************************/
        ASSERT (BITSOFF(pDX->GrabbedResources, GRABBEDRESOURCE_GOTDMA));
                                            /* must be PIO then              */
        *(pDX->pRxPIOData++) = IO_IN (pDX->ADAPTERBASE+AR_8273D);

//      TRACE_DATABYTE(RxD, *(pDX->pRxPIOData-1));
      }
    }

    if (Status & AS_TXINT)           /* transmitter action                           */
    {

      if (Status & AS_TXIRA)
      {
        /*********************************************************************/
        /* We have a transmitter result.  Read the result bytes into the     */
        /* device extension and after reading, request a DPC.                */
        /*                                                                   */
        /* The DPCAction tells the DPC why it is being called                */
        /*                                                                   */
        /* This implementation also assumes that only one transmitter result */
        /* will be outstanding at any one time, so there is only one         */
        /* TxResult byte.                                                    */
        /*********************************************************************/

        pDX->TxResult = IO_IN (pDX->ADAPTERBASE + AR_8273T);
        TRACE_DATABYTE (TxR, pDX->TxResult);
        if (pDX->TxResult EQ ATxR_TxCompleteOK)
        {
          TxFSMEvent (pDX, TxFSMInputEOTx);
        }
        else if (pDX->TxResult EQ ATxR_ErrTxFrameAborted)
        {
          TxFSMEvent (pDX, TxFSMInputAbortd);
        }
        else
        {
          TxFSMEvent (pDX, TxFSMInputEOTx_Err);
        }
      }
      else
      {
        /*********************************************************************/
        /* IRA not set - no result available, must be to tx next character   */
        /*********************************************************************/
        ASSERT (BITSOFF(pDX->GrabbedResources, GRABBEDRESOURCE_GOTDMA));
                                            /* must be PIO then              */
//      TRACE_DATABYTE (TxD, *pDX->pTxPIOData);
        IO_OUT(pDX->ADAPTERBASE+AR_8273D, *(pDX->pTxPIOData++));
      }
    }
    Status = IO_IN (pDX->ADAPTERBASE + AR_8273S);
  }
  while (Status & (AS_TXINT|AS_RXINT));

  if (!OriginalDPCAction &&             /* on entry, no dpc actions          */
      pDX->DPCAction)                   /* but on exit, we have DPC actions  */
  {
    IoRequestDpc (pDeviceObject, NULL, NULL); /* DPC will pulse event for us   */
  }

//XTRACE_EVENT (ISR>);
  return(ReturnValue);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        EntryPointOpen (                                             */
/*                          PDEVICE_OBJECT pDeviceObject                     */
/*                          PIRP           pIrp                              */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     Initialise the next device (from GetDriverSpec)              */
/*                                                                           */
/* Params:   IN DeviceObject:   Our device.                                  */
/*           IN PIrp            The IRP in question.                         */
/*                                                                           */
/* Return Value:BOOLean:        TRUE if device successfully initialised      */
/*                                                                           */
/* Operation:                                                                */
/*              1. Copy over pre-initialised data sequences                  */
/*                                                                           */
/*              2. Init for NT                                               */
/*                 - allocate transmit and receive buffers DMA-able          */
/*                 - DeviceObject                                            */
/*                   - DO_DIRECT_IO                                          */
/*                 - IoInitializeDpcRequest                                  */
/*                 - ConnectInterrupt                                        */
/*                                                                           */
/*              3. Init our own data                                         */
/*                 - DeviceIsOpen = FALSE                                    */
/*                                                                           */
/**PROC-**********************************************************************/

NTSTATUS EntryPointOpen (
                         PDEVICE_OBJECT pDeviceObject,
                         PIRP           pIrp
                        )
{
//ULONG         AddressSpace;
  KAFFINITY     Affinity;
  int           Information  = 0;
  KIRQL         InterruptLevel;
  CCHAR         InterruptVector;
  BOOLean       OKToContinue = TRUE;
  PDX           pDX          = pDeviceObject->DeviceExtension;
  NTSTATUS      Status       = STATUS_UNSUCCESSFUL;
  int           WindDownLevel= 0;

  ASSERT (!pDX->DeviceIsOpen);          /* device defined as exclusive usage!*/

  TRACE_NEWLINE();
  TRACE_EVENT({EPO);
  TRACE_ACTION (Op,CAST(pDX->ADAPTERBASE, ULONG) & 0xFFFF);

  pIrp->IoStatus.Information = 0L;

  pDX->GrabbedResources = 0;            /* clear the list of resources       */



  if (InterfaceType == MicroChannel)
  {
    if (!pDX->ConfigData.MPAAAdapterIdentifier)
    {
      OKToContinue = FALSE;
      Status       = STATUS_INSUFFICIENT_RESOURCES;
      Information  = IO_ERR_NEEDS_ISA_BUS;
      TRACE_EVENT (OcWb);
    }
  }
  else
  {
    if (pDX->ConfigData.MPAAAdapterIdentifier)
    {
      OKToContinue = FALSE;
      Status       = STATUS_INSUFFICIENT_RESOURCES;
      Information  = IO_ERR_NEEDS_MCA_BUS;
      TRACE_EVENT (OcWb);
    }
  }


  //
  // Map the memory for the control registers for the parallel device
  // into virtual memory.  This code needs to be activated when moving to
  // a platform with memory-mapped I/O.
  //

//  AddressSpace = pDX->ConfigData.AddressSpace;
//  CardAddress  = HalTranslateBusAddress(
//                    InterfaceType,
//                    pDX->ConfigData->BusNumber,
//                    pDX->ConfigData->AdapterBaseAddress,
//                    &AddressSpace
//                    );
//
//  if (!AddressSpace)
//  {
//      ParDeviceObject->UnMapRegisters = TRUE;
//      ParDeviceObject->DeviceRegisters = MmMapIoSpace(
//                                       CardAddress,
//                                       PARALLEL_REGISTER_LENGTH,
//                                       FALSE
//                                       );
//
//  } else {
//
//      ParDeviceObject->UnMapRegisters = FALSE;
//      ParDeviceObject->DeviceRegisters = (PVOID)CardAddress.LowPart;
//
//  }
//
//  if (!ParDeviceObject->DeviceRegisters) {
//
//      DbgPrint("Couldn't map the device registers.\n");
//      IoDeleteDevice(DeviceObject);
//      return STATUS_NONE_MAPPED;
//
//  }


  /***************************************************************************/
  /* 1. NT-related device initialisation                                     */
  /***************************************************************************/

  if (OKToContinue)
  {
    InterruptVector = HalGetInterruptVector(
                          InterfaceType,
                          pDX->ConfigData.BusNumber,
                          pDX->ConfigData.Irql,
                          pDX->ConfigData.Vector,
                          &InterruptLevel,
                          &Affinity
                          );

    Status = IoConnectInterrupt (
                &pDX->Interrupt,
                 EntryPointISR,
                 pDeviceObject,
                 NULL,
                 InterruptVector,
                 InterruptLevel,                /* interrupt IRQ level       */
                 InterruptLevel,                /* synchronize IRQ level     */
                 pDX->ConfigData.InterruptMode,
                 pDX->ConfigData.Shareable,
                 Affinity,                      /* processor number          */
                 FALSE                          /* no save floating pt contxt*/
                 );

    if (!NT_SUCCESS(Status))
    {
      OKToContinue = FALSE;
      Status       = STATUS_INSUFFICIENT_RESOURCES;
      Information  = IO_ERR_CANT_CONNECT_INTERRUPT_1;
      TRACE_EVENT (OcCi);
    }
  }

  if (OKToContinue)
  {
    WindDownLevel = 15;

    if (pDX->ConfigData.Irql2)
    {
      /***********************************************************************/
      /* A second interrupt is required                                      */
      /***********************************************************************/
      ASSERT (pDX->ConfigData.FlavourName[0] EQ 'S');
                                        /* only the SDLC card is so dumb     */


      if (OKToContinue)
      {
        InterruptVector = HalGetInterruptVector(
                              InterfaceType,
                              pDX->ConfigData.BusNumber,
                              pDX->ConfigData.Irql2,
                              pDX->ConfigData.Vector2,
                              &InterruptLevel,
                              &Affinity
                              );

        Status = IoConnectInterrupt (
                    &pDX->Interrupt2,
                     EntryPointRogueInterrupt,
                     pDeviceObject,
                     NULL,
                     InterruptVector,
                     InterruptLevel,                /* interrupt IRQ level       */
                     InterruptLevel,                /* synchronize IRQ level     */
                     pDX->ConfigData.InterruptMode2,
                     pDX->ConfigData.Shareable2,
                     1,                         /* processor number          */
                     FALSE                      /* no save floating pt contxt*/
                     );

        if (!NT_SUCCESS(Status))
        {
          OKToContinue = FALSE;
          Status       = STATUS_INSUFFICIENT_RESOURCES;
          Information  = IO_ERR_CANT_CONNECT_INTERRUPT_2;
          TRACE_EVENT (OcCi);
        }
      }
    }
  }

  /***************************************************************************/
  /* 2. Buffer allocation and init                                           */
  /***************************************************************************/

  if (OKToContinue)
  {
    WindDownLevel = 20;
    OKToContinue  = AllocateDMAMemory (sizeof(RCVBUFARRAY),
                                       (PVOID *)&pDX->RcvInfo.pRcvBufArray,
                                       &pDX->RcvInfo.pRcvMdl,
                                       &pDX->RcvInfo.RcvBufPhysAddr,
                                       &Information
                                      );
    if (!OKToContinue)
    {
      Status = STATUS_INSUFFICIENT_RESOURCES;
      // Information set in above call to AllocateDmaMemory
    }
  }
  if (OKToContinue)
  {
    WindDownLevel = 30;

    RCVINFO_INIT(pDX);

    pDX->RxFSMCurState = RxFSMStateIdle;
    OKToContinue       = AllocateDMAMemory (SENDBUF_SIZE,
                                            (PVOID *)&pDX->pSendBuf,
                                            &pDX->pSendMdl,
                                            &pDX->SendBufPhysAddr,
                                            &Information
                                           );
    if (!OKToContinue)
    {
      Status = STATUS_INSUFFICIENT_RESOURCES;
      // Information set in above call to AllocateDmaMemory
    }
  }
  if (OKToContinue)
  {
    WindDownLevel      = 40;
    pDX->TxFSMCurState = TxFSMStateIdle;
    pDX->TxConsecutiveUnderrunCount = 0;

    COPY8273CMD (pDX, CmdStringReadPortA        );
    COPY8273CMD (pDX, CmdStringResetOpMode      );
    COPY8273CMD (pDX, CmdStringResetSerialIOMode);
    COPY8273CMD (pDX, CmdStringSetOpMode        );
    COPY8273CMD (pDX, CmdStringSetSerialIOMode  );
    COPY8273CMD (pDX, CmdStringDataTransferMode );
    COPY8273CMD (pDX, CmdStringResetPortB       );
    COPY8273CMD (pDX, CmdStringSetPortB         );
    COPY8273CMD (pDX, CmdStringReceive          );
    COPY8273CMD (pDX, CmdStringTransmit         );
    COPY8273CMD (pDX, CmdStringAbortTransmit    );
    COPY8273CMD (pDX, CmdStringDisableReceiver  );

    pDX->pIR          = &pDX->OurIR;    /* use dummy IR for now until real   */

    pDX->pIR->V24In   = 0;
    pDX->pIR->V24Out  = 0;
    pDX->pIR->RxFrameCount= 0;
    pDX->pIR->TxMaxFrSizeNow = INIT_MAXFRSIZENOW;

//  pDX->pIRMdl = NULL;                 /*IRMdl?*/

    pDX->pUserEvent = NULL;             /* null until Ioctl: SetEvent        */
  }

  /***************************************************************************/
  /*    SDLC device initialisation                                           */
  /***************************************************************************/
  if (OKToContinue)
  {
    if (!KeSynchronizeExecution(pDX->Interrupt,
                                SynchEntryPointOpen,
                                (PVOID)pDX))
    {
      Status       = STATUS_DATA_ERROR;
      Information  = pDX->Information;
      OKToContinue = FALSE;
    }
  }

  if (OKToContinue)
  {
    if (pDX->HardwareError)
    {
      Status       = STATUS_DATA_ERROR;
      Information  = IO_ERR_HARDWARE_CMD_TIMEOUT_3;
      OKToContinue = FALSE;
    }
  }

  if (!OKToContinue)
  {
    TRACE_ACTION (Wl, WindDownLevel & 0xFFFF);

    if (WindDownLevel >= 15)
      IoDisconnectInterrupt( pDX->Interrupt);

    if (pDX->ConfigData.Irql2)
    {
      if (WindDownLevel >= 20)
        IoDisconnectInterrupt( pDX->Interrupt2);
    }

    if (WindDownLevel >= 30)
    {
      MmUnlockPages          (pDX->RcvInfo.pRcvMdl);
      IoFreeMdl              (pDX->RcvInfo.pRcvMdl);
      MmFreeContiguousMemory (pDX->RcvInfo.pRcvBufArray);
    }
    if (WindDownLevel >= 40)
    {
      MmUnlockPages          (pDX->pSendMdl);
      IoFreeMdl              (pDX->pSendMdl);
      pDX->pSendMdl         = NULL;
      MmFreeContiguousMemory (pDX->pSendBuf);
      pDX->pSendBuf         = NULL;
    }

    LogDriverError( pDeviceObject,
                    Status,
                    Information,
                    IoGetCurrentIrpStackLocation(pIrp)->MajorFunction,
                    0L );
  }

  pIrp->IoStatus.Status = Status;
  pIrp->IoStatus.Information = Information;
  if (Status EQ STATUS_SUCCESS)
  {
    pDX->DeviceIsOpen = TRUE;
  }

  TRACE_EVENT(EPO});
  TRACE_NTFAILURE (Status);

  CompleteIoRequest (pIrp);
  return (Status);
}

/*****************************************************************************/
/*                                                                           */
/* Name         EntryPointRogueInterrupt                                     */
/*                                                                           */
/* Purpose      Mode Status ISR.                                             */
/*                                                                           */
/*              Should never be called!!!  This ISR is used to handle int4   */
/*              on SDLC cards (modem status and timers).  Since the int is   */
/*              explicitly masked off in adapter set-up, it is a severe err  */
/*              if we actually get one!                                      */
/*                                                                           */
/*                                                                           */
/* Params    IN None                                                         */
/*                                                                           */
/*          OUT DLC appl hardware status updated.                            */
/*                                                                           */
/*****************************************************************************/

BOOLEAN EntryPointRogueInterrupt (
                                  PKINTERRUPT pInterrupt,
                                  PVOID       Context
                                 )
{
  PDEVICE_OBJECT pDeviceObject= (PDEVICE_OBJECT) Context;
  PDX            pDX          = pDeviceObject->DeviceExtension;

  UNREFERENCED_PARAMETER (pInterrupt);

  TRACE_EVENT ({EP?);

  /***************************************************************************/
  /* Update hardware error status and trigger an event for Link process via  */
  /* DPC                                                                     */
  /***************************************************************************/

  pDX->pIR->StatusArray[SA_HardwareError]++;
  pDX->pIR->StatusCount++;

  pDX->DPCAction |= DPC_ACTION_PULSE;
  IoRequestDpc (pDeviceObject, NULL, NULL); /* DPC will pulse event for us   */

  /***************************************************************************/
  /* Reset the modem status logic and turn the 8273 off - it is being too    */
  /* disruptive to keep active!                                              */
  /***************************************************************************/

  WR_N_DELAY (pDX->ADAPTERBASE + AR_8255B, A55_ResetModemStatusCh+
                                           A55_Reset8273Off);
  WR_N_DELAY (pDX->ADAPTERBASE + AR_8255B, A55_Reset8273Off);

  TRACE_EVENT (EP?});
  return(FALSE);                        /* interrupt wasn't expected         */
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        EntryPointUnload (                                           */
/*                          PDRIVER_OBJECT pDriverObject                     */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     Unload the device - don't need to do anything?               */
/*                                                                           */
/* Params:   IN DriverObject:   Our device.                                  */
/*                                                                           */
/* Return Value:None                                                         */
/*                                                                           */
/* Operation:                                                                */
/*                                                                           */
/**PROC-**********************************************************************/

VOID
EntryPointUnload (
                  IN PDRIVER_OBJECT pDriverObject
                 )
{
  IoDeleteDevice( pDriverObject->DeviceObject );
  return;
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         GetInterfaceType (                                           */
/*                                PDRIVER_OBJECT pDriverObject               */
/*                               )                                           */
/*                                                                           */
/* Purpose      Hide the vagaries of roasting squirrels                      */
/*                                                                           */
/* Return Value BOOLean : TRUE if InterfaceType (global) set up OK           */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean GetInterfaceType (
                          IN PDRIVER_OBJECT  pDriverObject
                         )
{

  PUNICODE_STRING           RegistryPath = pDriverObject->HardwareDatabase;
  PRTL_QUERY_REGISTRY_TABLE Parameters = NULL;
  UNICODE_STRING            ParametersPath;
  OBJECT_ATTRIBUTES         ParametersAttributes;
  HANDLE                    ParametersKey;
  UNICODE_STRING            Identifier;
  UNICODE_STRING            MCAString;
  NTSTATUS                  Status = STATUS_SUCCESS;
  ULONG                     Information;


  TRACE_EVENT([GIT);

  Identifier.Buffer = NULL;

  /***************************************************************************/
  /* Set up the Registry path to check if \EisaAdapter\0 key exists.         */
  /***************************************************************************/

  RtlInitUnicodeString (&ParametersPath, NULL);

  ParametersPath.MaximumLength = RegistryPath->Length +
                                 sizeof(L"\\MultifunctionAdapter\\0") +
                                 4;

  ParametersPath.Buffer = ExAllocatePool(
                              PagedPool,
                              ParametersPath.MaximumLength
                              );

  if (!ParametersPath.Buffer)
  {
    TRACE_EVENT (GIT1);
    Information = IO_ERR_GET_IF_TYPE_1;
    Status      = STATUS_UNSUCCESSFUL;
  }
  else
  {

    RtlZeroMemory(
        ParametersPath.Buffer,
        ParametersPath.MaximumLength
        );
    RtlAppendUnicodeStringToString(
        &ParametersPath,
        RegistryPath
        );
    RtlAppendUnicodeToString(
        &ParametersPath,
        L"\\EisaAdapter\\0"
        );

    /*************************************************************************/
    /* Attempt to open the key - if we can this is Eisa bus.                 */
    /*************************************************************************/

    InitializeObjectAttributes(
        &ParametersAttributes,
        &ParametersPath,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    if (NT_SUCCESS(ZwOpenKey(
                       &ParametersKey,
                       MAXIMUM_ALLOWED,
                       &ParametersAttributes
                       )))
    {
      InterfaceType = Eisa;
      TRACE_EVENT (Eisa);
      DEBUG_PRINT (("IBMSYNC: Bus type is Eisa\n"));
      TRACE_EVENT(GIT]);

      ExFreePool(ParametersPath.Buffer);
      return(TRUE);
    }
  }



  if (NT_SUCCESS(Status))
  {
    /*************************************************************************/
    /* Reset the Registry path to check for Identifier string in key         */
    /* \MultifunctionAdapter\0.                                              */
    /*************************************************************************/

    RtlZeroMemory(
        ParametersPath.Buffer,
        ParametersPath.MaximumLength
        );

    ParametersPath.Length = 0;

    RtlAppendUnicodeStringToString(
        &ParametersPath,
        RegistryPath
        );
    RtlAppendUnicodeToString(
        &ParametersPath,
        L"\\MultifunctionAdapter\\0"
        );

    /*************************************************************************/
    /* Allocate the Rtl query table.                                         */
    /*************************************************************************/

    Parameters = ExAllocatePool(
                     PagedPool,
                     sizeof(RTL_QUERY_REGISTRY_TABLE) * 2
                     );

    if (!Parameters)
    {
      TRACE_EVENT (GIT2);
      Information = IO_ERR_GET_IF_TYPE_2;
      Status      = STATUS_UNSUCCESSFUL;
    }
    else
    {

      /***********************************************************************/
      /* Allocate memory to except Identifier type.                          */
      /***********************************************************************/
      RtlInitUnicodeString (&Identifier, NULL);

      Identifier.MaximumLength = 20;    /* Room for "EISA", "MCA" or "ISA"   */

      Identifier.Buffer = ExAllocatePool(
                                  PagedPool,
                                  Identifier.MaximumLength
                                  );

      if (!Identifier.Buffer)
      {
        TRACE_EVENT (GIT3);
        Information = IO_ERR_GET_IF_TYPE_3;
        Status      = STATUS_UNSUCCESSFUL;
      }
      else
      {
        /*********************************************************************/
        /* OK now finally pick up the Bus Type (Identifier) string from the  */
        /* Registry.                                                         */
        /*********************************************************************/

        RtlZeroMemory(
              Parameters,
              sizeof(RTL_QUERY_REGISTRY_TABLE) * 2
              );

        Parameters[0].Flags         = RTL_QUERY_REGISTRY_DIRECT;
        Parameters[0].Name          = L"Identifier";
        Parameters[0].EntryContext  = &Identifier;
        Parameters[0].DefaultType   = REG_SZ;
        Parameters[0].DefaultData   = L"None";
        Parameters[0].DefaultLength = 8;


        if (!NT_SUCCESS(RtlQueryRegistryValues(
                           RTL_REGISTRY_ABSOLUTE,
                           ParametersPath.Buffer,
                           Parameters,
                           NULL,
                           NULL
                           )))
        {
          TRACE_EVENT (GIT4);
          Information = IO_ERR_GET_IF_TYPE_4;
          Status      = STATUS_UNSUCCESSFUL;
        }
        else
        {
          RtlInitUnicodeString (&MCAString, L"MCA");

          if (!RtlCompareUnicodeString(&MCAString, &Identifier, TRUE))
          {
            InterfaceType = MicroChannel;
            TRACE_EVENT (MCA );
            DEBUG_PRINT (("IBMSYNC: Bus type is MicroChannel\n"));
          }
          else
          {
            InterfaceType = Isa;
            TRACE_EVENT (ISA );
            DEBUG_PRINT (("IBMSYNC: Bus type is Isa\n"));
          }
        }
      }
    }
  }

  /***************************************************************************/
  /* Free any allocated memory before returning.                             */
  /***************************************************************************/

  if (ParametersPath.Buffer)
      ExFreePool(ParametersPath.Buffer);

  if (Identifier.Buffer)
      ExFreePool(Identifier.Buffer);

  if (Parameters)
      ExFreePool(Parameters);

  /***************************************************************************/
  /* OK, so which Device Object do we use here?  The driver can have         */
  /* multiple Device Objects set up; also the reading of the bus type from   */
  /* the Registry is done just once (i.e not on a per-Device basis).         */
  /*                                                                         */
  /* We use the last one hanging from the list - to get to this stage we     */
  /* must have at least one.                                                 */
  /***************************************************************************/
  if (!NT_SUCCESS(Status))
  {
    LogDriverError( pDriverObject->DeviceObject,
                    Status,
                    Information,
                    0L,
                    0L );
  }


  TRACE_EVENT(GIT]);

  return(Status == STATUS_SUCCESS);

}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         GetDriverSpec (                                              */
/*                             CHAR *DriverName,                             */
/*                            PCONFIGDATA *pConfigData                       */
/*                            );                                             */
/*                                                                           */
/* Purpose      Hide the vagaries of initialising                            */
/*                                                                           */
/* Params   OUT DriverName: e.g. COM$DL01, null-terminated, 8+1 chars        */
/*          OUT FlavourName: e.g. SDLC or MPAA1, null-terminated, 5+1 chars  */
/*          OUT pConfigData: the config data record to be set up             */
/*                           (a pointer is passed, not the structure itself) */
/*                                                                           */
/* Return Value BOOLean:    TRUE if there was another driver spec to get     */
/*                                                                           */
/* Operation    At a guess, this will                                        */
/*              - enumerate all SDLC device drivers in CFG Reg.              */
/*                (RegEnumKeyEx(Key:Master\Software\CS\Drivers)              */
/*                  or CS\SDLCDrivers?                                       */
/*              - if necessary, check the driver is an SDLC one              */
/*              - read config info to extract device name and type           */
/*              - get other config data into ConfigData                      */
/*                                                                           */
/* Notes        Uses static variables to indicate position in config; so     */
/*              no indication from user that this is first time is required. */
/*                                                                           */
/*              Called only during DriverEntry.                              */
/*                                                                           */
/*              The fact that a driver/flavour is returned does not imply    */
/*              the device is usable - this must be checked by calling       */
/*              DeviceInit.                                                  */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean  GetDriverSpec (CHAR *DeviceName,
                        PCONFIGDATA *pConfigData
                       )
{
  static int DeviceNumber = 0;

  /* very temporary - until we find out how to read config registry. */

  if (++DeviceNumber > AT_COUNT)
    return (FALSE);

  strcpy (DeviceName, "\\Device\\COMDL$00");
  DeviceName[strlen(DeviceName)-1] = ((char) ('0' + DeviceNumber));
  *pConfigData = &ConfigData[DeviceNumber-1];
  return(TRUE);

}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         GetV24Input  (                                                  */
/*                            PDX pDX                                        */
/*                           );                                              */
/*                                                                           */
/* Purpose      Interpret pin inputs as presented by SDLC cards and stuff    */
/*              into IR fields                                               */
/*                                                                           */
/* Params   IN  pDX - the device extension                                   */
/*                                                                           */
/* Return Value None                                                         */
/*                                                                           */
/**PROC-**********************************************************************/

void GetV24Input (PDX pDX)
{
  UCHAR o;

  TRACE_EVENT (<GV2);

  pDX->pIR->V24In = 0;                  /* reset IR value.                   */

  /***************************************************************************/
  /* Reset the 'modem status changed' bit because after we read this         */
  /* hasn't changed                                                          */
  /***************************************************************************/

  WR_N_DELAY (pDX->ADAPTERBASE+AR_8255B,
              IO_IN (pDX->ADAPTERBASE+AR_8255B) & CAST(~8,UCHAR));
                                         /* 8 = modem status changed          */

  /***************************************************************************/
  /* Read the 8255 Port C settings to get the current state of Test          */
  /***************************************************************************/

  if (BITSOFF(IO_IN (pDX->ADAPTERBASE+AR_8255C), 0x40))
  {
    pDX->pIR->V24In |= IR_IV24Test;    /* &40 = 0 => Test is Active         */
  }

  /***************************************************************************/
  /* Read the 8255 Port A settings.                                          */
  /***************************************************************************/

  o = (IO_IN (pDX->ADAPTERBASE+AR_8255A));

  if (!(o&1))                          /* &1=0 implies RI on at interface    */
  {
    pDX->pIR->V24In |= IR_IV24RI;
  }
  if (!(o&2))                          /* &2=0 implies DCD on at interface   */
  {
    pDX->pIR->V24In |= IR_IV24DCD;
  }
  if (!(o&8))                          /* &8=0 implies CTS on at interface   */
  {
    pDX->pIR->V24In |= IR_IV24CTS;
  }

  /***************************************************************************/
  /* Used to do the 8273 Port A Reading to get hold of DSR here.  There are  */
  /* slight problems with doing that                                         */
  /* - the 8273 can get upset if given three commands at once (e.g Rx, Tx    */
  /*    AND CmdStringReadPortA) (this is surmountable)                       */
  /***************************************************************************/

  if (pDX->RxFSMCurState NE RxFSMStateReady || /* not receiving ... or       */
      pDX->TxFSMCurState EQ TxFSMStateIdle)    /* transmitter idle           */
  {
    pDX->LastPortA = Write8273Cmd (pDX, pDX->CmdStringReadPortA);
  }

  if (pDX->LastPortA & 4)               /* is the DSR bit set???             */
  {
    pDX->pIR->V24In |= IR_IV24DSR;
  }
  TRACE_EVENT (GV2>);

}

/*****************************************************************************/
/*                                                                           */
/* Name         InitialiseAdapter                                            */
/*                                                                           */
/* Purpose      Initialises and checks the hardware for a link.              */
/*                                                                           */
/* Params    IN pDX, plus Synchronised, plus interrupt routines set up       */
/*                                                                           */
/* Return Value False on failure, plus SynchInformation set                  */
/*                                                                           */
/*****************************************************************************/

BOOLean InitialiseAdapter (PDX pDX)
{
  BOOLean rc;

  TRACE_EVENT (<InA);

  /***************************************************************************/
  /* Resetting the adapter and check that it accepts commands.  Then call    */
  /* the link options handler to set it up in the default mode.              */
  /***************************************************************************/

   AdapterReset(pDX);

  /***************************************************************************/
  /* The adapter is now primed and ready to go.  Set up a default link       */
  /* characteristics configuration and call link set-up.  The link set-up    */
  /* routine SetLinkConfig returns carry set if problems occur.              */
  /***************************************************************************/

  pDX->LinkMaxFrameSize = DEFAULT_FRAME_SIZE;
  pDX->LinkOptionsByte = 0;

  if (SetLinkConfig(pDX))
  {
    SetV24Output (pDX);
    GetV24Input (pDX);
    rc = TRUE;                          /* OK return code                    */
  }
  else
  {
    SynchTerminateAdapter(pDX);
    pDX->Information = IO_ERR_HARDWARE_INIT_FAILURE;
    rc = FALSE;
    TRACE_EVENT (HwIF);
    TRACE_RC(rc);
  }

  TRACE_EVENT (InA>);
  return (rc);
}

/*****************************************************************************/
/*                                                                           */
/* Name         IoctlAbortReceiver                                           */
/*                                                                           */
/* Purpose      Abort Receiver IOCtl Processor                               */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT Requested function is processed. No return code              */
/*                                                                           */
/*****************************************************************************/

void IoctlAbortReceiver (PDX pDX)
{
  TRACE_EVENT ([IAR);
  RxFSMEvent (pDX, RxFSMInputStop);     /* terminate with extreme prejudice  */

  /***************************************************************************/
  /* Then reset the buffer pointers, discarding all the data in the receive  */
  /* buffer.                                                                 */
  /***************************************************************************/

  RCVINFO_INIT(pDX);
  TRACE_EVENT (IAR]);
}

/*****************************************************************************/
/*                                                                           */
/* Name         IoctlAbortTransmitter                                        */
/*                                                                           */
/* Purpose      Abort Transmitter IOCtl Processor                            */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT None                                                         */
/*                                                                           */
/*****************************************************************************/

void IoctlAbortTransmitter (PDX pDX)
{
  TRACE_EVENT ([IAT);
  /***************************************************************************/
  /* First stop the transmitter (if it is running).                          */
  /***************************************************************************/

  TxFSMEvent (pDX, TxFSMInputStop);

  /*0025****************************************************************/
  /*0025* Clear the underrun count.                                    */
  /*0025****************************************************************/

  pDX->TxConsecutiveUnderrunCount = 0;

  /***************************************************************************/
  /* Then reset the transmit buffer pointers, discarding all the data in the */
  /* buffer.                                                                 */
  /***************************************************************************/

  pDX->TxNextToTransmit = 0;
  pDX->TxNextToBuffer   = 0;

  /*0025**********************************************************************/
  /*0025* Set up the maximum buffer size and the interface record            */
  /*0025* 'initialisation' buffer size.                                      */
  /*0025**********************************************************************/

  pDX->TxStartUnusedArea = SENDBUF_SIZE;
  pDX->pIR->TxMaxFrSizeNow = INIT_MAXFRSIZENOW;
  XTRACE_ACTION (Tz, pDX->pIR->TxMaxFrSizeNow);
  TRACE_EVENT (IAT]);
}

/*****************************************************************************/
/*                                                                           */
/* Name         IoctlSetInterfaceRecord                                      */
/*                                                                           */
/* Purpose      / *IRMdl?* / Obsolete                                          */
/*              Set Interface Record Address handler                         */
/*                                                                           */
/* Params    IN pDX                                                          */
/*              Implicit: the input buffer address (IrpSp->InputBufferLength */
/*                      + Irp->AssociatedIrp->SystemBuffer) describes the    */
/*                      interface record                                     */
/*                                                                           */
/*          OUT None                                                         */
/*                                                                           */
/* Side Effect  pDX->IoctlRetStatus & Information                            */
/*                                                                           */
/* Return Value void                                                         */
/*                                                                           */
/*****************************************************************************/

//void IoctlSetInterfaceRecord (PDX pDX)
//{
//  PIRP          pIrp         = pDX->IoctlCurrentIrp;
//  PIO_STACK_LOCATION
//                pIrpSp       = IoGetCurrentIrpStackLocation(pIrp);
//
//  TRACE_EVENT ([IoI);
//
//  /***************************************************************************/
//  /* The old story: allocate an MDL, probe and lock, map to system address   */
//  /* space and save the resulting system address in pIR.                     */
//  /*                                                                         */
//  /* Free up Mdl and unlock pages if this is a subsequent call.              */
//  /***************************************************************************/
//
//  if (pDX->pIRMdl != NULL)
//  {
//    /*************************************************************************/
//    /* Protect ourselves from bozo users calling IoctlSetInterfaceRecrd twice*/
//    /*************************************************************************/
//    ASSERT (pDX->pIR != NULL);
//    MmUnmapLockedPages (pDX->pIR, pDX->pIRMdl);                    /*IRMdl?*/
//    IoFreeMdl (pDX->pIRMdl);
//  }
//
//  pDX->pIRMdl = IoAllocateMdl(pIrp->AssociatedIrp.SystemBuffer,
//                              pIrpSp->IRS_INLEN,
//                              sizeof (IRP),
//                              FALSE,    /* this is not a secondary buffer    */
//                              FALSE,    /* no charge to quota                */
//                              (PIRP)NULL/* not attached to IRP               */
//                             );
//
//  if (pDX->pIRMdl NE NULL)              /* We could do the mapping           */
//  {
//    MmProbeAndLockPages (pDX->pIRMdl, KernelMode, IoModifyAccess);
//
//    pDX->pIR = MmMapLockedPages(pDX->pIRMdl, KernelMode);
//    pDX->pIR->RxFrameCount   = 0;
//    pDX->pIR->TxMaxFrSizeNow = INIT_MAXFRSIZENOW;
//    pDX->pIR->StatusCount    = 0;
//    pDX->pIR->V24In          = 0;
//    pDX->pIR->V24Out         = 0;
//    RtlZeroMemory (pDX->pIR->StatusArray, sizeof (pDX->pIR->StatusArray));
//    TRACE_EVENT (pIR_);
//    TRACE_DWORD (pDX->pIR);
//    XTRACE_ACTION (Tz, pDX->pIR->TxMaxFrSizeNow);
//  }
//  else
//  {
//    pDX->pIR = NULL;
//    pDX->Information    = INFO_CANT_ALLOCATE_MDL;
//    pDX->IoctlRetStatus = STATUS_INSUFFICIENT_RESOURCES;
//  }
//  TRACE_EVENT (IoI]);
//}

/*****************************************************************************/
/*                                                                           */
/* Name         IoctlSetLinkConfig                                           */
/*                                                                           */
/* Purpose      Set Link Characteristics IOCtl Processor                     */
/*                                                                           */
/*              Sets the link configuration on the basis of the supplied     */
/*              parameters.  This routine also aborts any outstanding        */
/*              activity in case there is a conflict between old and new     */
/*              parameters (switching to DMA for instance!).                 */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT                                                              */
/*                                                                           */
/* Side Effect  pDX->IoctlRetStatus & Information                            */
/*                                                                           */
/*****************************************************************************/

BOOLean IoctlSetLinkConfig (PDX pDX)
{
  PIRP          pIrp         = pDX->IoctlCurrentIrp;
  PIO_STACK_LOCATION
                pIrpSp       = IoGetCurrentIrpStackLocation(pIrp);
  SLPARMS     *pParms        = pIrp->AssociatedIrp.SystemBuffer;
  UCHAR         NewOpt       = pParms->SLLinkOptionsByte; /* easier to type  */
  BOOLean       rc           = FALSE;   /* assume the worst!                 */

  TRACE_EVENT ([IoC);

  /***************************************************************************/
  /* Issue the abort calls to get Tx and Rx synchronised with this.          */
  /***************************************************************************/

  IoctlAbortReceiver (pDX);
  IoctlAbortTransmitter(pDX);

  /***************************************************************************/
  /* Read the parameter data.  Check that frame will fit the buffer.         */
  /***************************************************************************/

  if (pIrpSp->IRS_INLEN NE sizeof(SLPARMS))
  {
    pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
    pDX->Information = IO_ERR_LINKCHAR_BUF_WRONG_SIZE;
  }
  else
  if (pParms->SLFrameSize < 267)    /* don't really want < 2+6+3+256 bytes!? */
  {
    pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
    pDX->Information = IO_ERR_FRAME_BUF_TOO_SMALL;
  }
  else
  if (pParms->SLFrameSize > 2048)   /* really SDLC frame bigger than 2Kb?    */
  {
    pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
    pDX->Information = IO_ERR_FRAME_BUF_TOO_BIG;
  }
  else
  if (pParms->SLLinkOptionsByte & LinkOption_InternalClock)
  {
    /*************************************************************************/
    /* We have been asked to supply internal clocks (not supported by these  */
    /* adapters).                                                            */
    /*************************************************************************/

    pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
    pDX->Information = IO_ERR_NO_CLOCKS;
  }
  else
  {
    pDX->LinkMaxFrameSize = pParms->SLFrameSize;

    /*************************************************************************/
    /* Pick up the addresses and put them in the LCB.  Then set General or   */
    /* specific receive depending on whether the addresses are zero.         */
    /*************************************************************************/

    pDX->OurAddress1         = pParms->SLOurAddress1;
    pDX->OurAddress2         = pParms->SLOurAddress2;
    pDX->CmdStringReceive[4] = pParms->SLOurAddress1;
    pDX->CmdStringReceive[5] = pParms->SLOurAddress2;

    if (pParms->SLOurAddress1 EQ 0 &&
        pParms->SLOurAddress2 EQ 0)
    {
      /***********************************************************************/
      /* if both addresses are zero, we are SDLC primary and can accept      */
      /* frames targetted at any address                                     */
      /***********************************************************************/
      pDX->CmdStringReceive[0] = 0xC0;  /* set 3-byte General Rx command     */
      pDX->CmdStringReceive[1] = 2;
    }
    else
    {
      pDX->CmdStringReceive[0] = 0xC1;  /* set 5-byte Specific Rx command    */
      pDX->CmdStringReceive[1] = 4;
    }

    /*************************************************************************/
    /* Next, pick up the Link Options and make any necessary changes.        */
    /*************************************************************************/

    if (NewOpt & LinkOption_FullDuplex  /* turn off DMA if the link will     */
        && NewOpt & LinkOption_DMA)     /* be full duplex.                   */
    {
      /***********************************************************************/
      /* Check whether the caller asked for DMA, but we couldn't support it  */
      /* - return warning error if so.                                       */
      /***********************************************************************/

      NewOpt &= ~LinkOption_DMA;
      pDX->Information = IO_ERR_NO_DMA_FDX; /* but r/c is OK.                */
    }

    pDX->LinkOptionsByte = NewOpt;      /* set the new options byte          */


    /*************************************************************************/
    /* Clear the statistics data on the IF record if the appl wants to.      */
    /*************************************************************************/

    if (NewOpt & LinkOption_ResetStatistics)
    {                                   /* clear the statistics ??           */
      RtlZeroMemory (pDX->pIR->StatusArray, sizeof(pDX->pIR->StatusArray));
      pDX->pIR->StatusCount = 0;
    }

    /*************************************************************************/
    /* Now set the options and start the receiver.                           */
    /*************************************************************************/

    if (SetLinkConfig (pDX))            /* call hardware to set options      */
    {
      RxFSMEvent (pDX, RxFSMInputStart);/* start receiver if possible        */
      rc = TRUE;                        /* set good return code              */
    }
    else
    {
      pDX->IoctlRetStatus = STATUS_DATA_ERROR;
      pDX->Information    = IO_ERR_HARDWARE_CMD_TIMEOUT_4;
    }
  }

  TRACE_EVENT (IoC]);
  return(rc);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         IoctlRxFrame                                                 */
/*                                                                           */
/* Purpose      Receive Request processor                                    */
/*                                                                           */
/*              Copies next available frame to the application buffer.       */
/*                                                                           */
/* Params    IN IRP: buffer length  in Stack->OutputBufferLength (IRS_OUTLEN)*/
/*                   buffer pointer in UserBuffer                            */
/*                                                                           */
/*          OUT Available data, if any                                       */
/*                                                                           */
/* Side Effect  pDX->IoctlRetStatus & Information                            */
/*                                                                           */
/* Return Value TRUE - ignored                                               */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean IoctlRxFrame (PDX pDX)
{
  PIRP               pIrp         = pDX->IoctlCurrentIrp;
  PIO_STACK_LOCATION pIrpSp       = IoGetCurrentIrpStackLocation(pIrp);
  RFD               *pRFD;
  short              RxLength;
  UCHAR  *           GiveMeACharPtr;

  TRACE_EVENT ([IoR);

  /***************************************************************************/
  /* Check to see if there are any frames waiting in the buffer.  The ISR    */
  /* fills the buffer by incrementing the 'head' pointer but stops before it */
  /* catches up with the 'tail'.                                             */
  /***************************************************************************/

  if (RFD_CAN_GET(pDX))          /* then there is data present */
  {
    pRFD = &pDX->RcvInfo.RFDArray[pDX->RcvInfo.RFDNextToGet];

    /*************************************************************************/
    /* We need to copy the data from our buffer into the user's buffer.      */
    /* The actual output length goes into the Information field              */
    /*************************************************************************/

    RxLength = pRFD->RcvdDataLength;
    if (CAST(RxLength, ULONG) + 2 > pIrpSp->IRS_OUTLEN)
    {
      /***********************************************************************/
      /* Output buffer not as big as A + C + Data received                   */
      /***********************************************************************/
      pDX->IoctlRetStatus = STATUS_BUFFER_TOO_SMALL;
    }
    else
    {
      ASSERT (RxLength >= 0);           /* n.b. but length = 0 is OK         */
                                        /* (RR's will have length of 0 in    */
                                        /* buffer, which we report to  user  */
                                        /* as length of 0                    */

      if (RxLength >= 0)                /* != -1 => really something there   */
      {
        GiveMeACharPtr = MmMapLockedPages(pIrp->MdlAddress, KernelMode);

        GiveMeACharPtr[0] = pRFD->SDLCAddressByte;
        GiveMeACharPtr[1] = pRFD->SDLCControlByte;
        RtlMoveMemory (&GiveMeACharPtr[2], pRFD->StartAddr, RxLength);
      }
    }

    /*************************************************************************/
    /* We signal the actual received length back in the IoStatusBlock, via   */
    /* pDX.                                                                  */
    /*************************************************************************/
    pDX->Information = RxLength+2;

    RFD_GOT(pDX);                   /* release the buffer we got             */

    /*************************************************************************/
    /* Lastly decrement the count of received frames available in the shared */
    /* data area.                                                            */
    /*************************************************************************/

    pDX->pIR->RxFrameCount--;
  }
  else
  {
    pDX->Information = 0;
    TRACE_EVENT (IoRN);
  }

  TRACE_EVENT (IoR]);
  return (TRUE);
}

/*****************************************************************************/
/*                                                                           */
/* Name         IoctlSetV24Output                                            */
/*                                                                           */
/* Purpose      Handles the IoCtl to set the V24 Output                      */
/*                                                                           */
/* Params    IN pDX                                                          */
/*              Implicit input: V24Out byte in IR is what is to be pumped out*/
/*                                                                           */
/*          OUT TRUE                                                         */
/*                                                                           */
/* Side Effect  pDX->IoctlRetStatus & Information                            */
/*                                                                           */
/*****************************************************************************/

BOOLean IoctlSetV24Output (PDX pDX)
{
  TRACE_EVENT ([IVO);
// temporary hack !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  pDX->pIR->V24Out = *(CAST (pDX->IoctlCurrentIrp->UserBuffer, UCHAR *));
// temporary hack !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

  if (!SetV24Output(pDX))                         /* call hardware processor */
  {
    return (FALSE);
  }

  TRACE_EVENT (IVO]);
  return (TRUE);
}

/*****************************************************************************/
/*                                                                           */
/* Name         IoctlTxFrame                                                     */
/*                                                                           */
/* Purpose      Transmit frame request handler.  Puts frames into the Tx     */
/*              buffer ready for the Tx FSM to use.                          */
/*                                                                           */
/*              Transmit Request Processor.                                  */
/*                                                                           */
/* Params    IN pIrp parameters point to request packet data and length      */
/*                                                                           */
/*          OUT Data transferred to Tx buffer ready to send.                 */
/*              Transmitter FSM kicked.                                      */
/*                                                                           */
/* Side Effect  pDX->IoctlRetStatus & Information                            */
/*                                                                           */
/*****************************************************************************/

BOOLean IoctlTxFrame (PDX pDX)
{
  ULONG              BufferSpaceNeeded;
  ULONG              BufferSpaceAvailable;
  PIRP               pIrp         = pDX->IoctlCurrentIrp;
  PIO_STACK_LOCATION pIrpSp       = IoGetCurrentIrpStackLocation(pIrp);
  BOOLean            rc           = TRUE;   /* normally OK                   */

  TRACE_EVENT ([IoT);

  BufferSpaceNeeded = pIrpSp->IRS_OUTLEN
                      + 2;              /* allow for frame length header     */

  XTRACE_ACTION (Tn, BufferSpaceNeeded);
  XTRACE_ACTION (Tx, pDX->LinkMaxFrameSize);
  XTRACE_ACTION (Tb, pDX->TxNextToBuffer);
  XTRACE_ACTION (Tt, pDX->TxNextToTransmit);

  if (CAST(pIrpSp->IRS_OUTLEN, int) < 2)
  {
    /*************************************************************************/
    /* The minimum frame size is 2                                           */
    /*************************************************************************/

     pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
     pDX->Information    = IO_ERR_TX_FRAME_TOO_SMALL;
     rc = FALSE;
     TRACE_RC(rc);
     return(rc);
  }

  if (CAST(pIrpSp->IRS_OUTLEN, int) > pDX->LinkMaxFrameSize)
  {
    /*************************************************************************/
    /* The frame is bigger than the maximum specified                        */
    /*************************************************************************/

     pDX->IoctlRetStatus = STATUS_INVALID_PARAMETER;
     pDX->Information    = IO_ERR_TX_FRAME_TOO_BIG;
     rc = FALSE;
     TRACE_RC(rc);
     return(rc);
  }

  /***************************************************************************/
  /* Check to see if there is room for the data in the transmit buffer.      */
  /* This code fills the buffer by incrementing the 'ToTransmit' pointer but */
  /* stops before it catches up with the 'tail'.  The ISR takes frames from  */
  /* the tail pointer, incrementing it until it matches ToTransmit.          */
  /***************************************************************************/

  /***************************************************************************/
  /* The ToBuffer and ToTransmit pointers could be anywhere.  This code      */
  /* checks for ToTransmit < ToBuffer and works out the difference -> AX.    */
  /* If the ToTransmit is greater than the ToBuffer, there could be space at */
  /* either end if the buffer - the top end is checked and, if too small,    */
  /* the ToTransmit is moved round to the start and then checked as          */
  /* ToTransmit < ToBuffer.                                                  */
  /***************************************************************************/

  if (pDX->TxNextToBuffer >= pDX->TxNextToTransmit)
  {                                     /* ToTransmit is in front of ToBuffer*/
    /*************************************************************************/
    /* The free space is at the end of the buffer.  If this is too small,    */
    /* set the 'end' for the ISR to the current ToTransmit and then move     */
    /* ToTransmit round to the front.  Note: we don't go up to SENDBUF_SIZE  */
    /* because the  TxStartUnusedArea must start on that                     */
    /*************************************************************************/

    BufferSpaceAvailable = SENDBUF_SIZE - 1 - pDX->TxNextToBuffer;
    if (BufferSpaceNeeded > BufferSpaceAvailable)
                                        /* not enough space at the end ?     */
    {
      pDX->TxStartUnusedArea = pDX->TxNextToBuffer;
                                        /* ISR will wrap at this point       */
      pDX->TxNextToBuffer    = 0;
      BufferSpaceAvailable   = pDX->TxNextToTransmit-1;/* free up to bfr area*/

      XTRACE_ACTION (Tu, pDX->TxStartUnusedArea);
      XTRACE_ACTION (Tb, pDX->TxNextToBuffer);
      XTRACE_ACTION (Ta, BufferSpaceAvailable);
    }
  }
  else
  {
    /*************************************************************************/
    /* the free space is from NextToTransmit up to NextToBuffer              */
    /*************************************************************************/

    BufferSpaceAvailable   = pDX->TxNextToBuffer - 1 - pDX->TxNextToTransmit;
    XTRACE_ACTION (Ta, BufferSpaceAvailable);
  }

  if (BufferSpaceAvailable >= BufferSpaceNeeded)
  {
    /*************************************************************************/
    /* The 'Max Tx Frame is initialised to be a little less than half the    */
    /* total size of the transmit buffer and is decremented by half the size */
    /* of each frame put in (rounded UP).  It is kept at half because the    */
    /* bfr can get split into two pieces and we need a contiguous area.      */
    /*************************************************************************/

    pDX->pIR->TxMaxFrSizeNow -= (BufferSpaceNeeded + 1) >> 1;
    XTRACE_ACTION (Tz, pDX->pIR->TxMaxFrSizeNow);
    pDX->pSendBuf [pDX->TxNextToBuffer]   = LOBYTE(BufferSpaceNeeded-2);
    pDX->pSendBuf [pDX->TxNextToBuffer+1] = HIBYTE(BufferSpaceNeeded-2);
                                                /* set length to data length */
                                                /* (-2 => less length itself)*/
    RtlMoveMemory (&(pDX->pSendBuf[pDX->TxNextToBuffer+2]),
                   pIrp->UserBuffer,
                   BufferSpaceNeeded-2);

    /*************************************************************************/
    /* Now set up the new buffer ToTransmit position.                        */
    /*************************************************************************/

    pDX->TxNextToBuffer += BufferSpaceNeeded;
    XTRACE_ACTION (Tb, pDX->TxNextToBuffer);
    ASSERT (pDX->TxNextToBuffer < SENDBUF_SIZE);

    /*************************************************************************/
    /* Kick the Transmit FSM to start it off.                                */
    /*************************************************************************/

    TxFSMEvent (pDX, TxFSMInputStart);

    // and returned status is TRUE;
  }
  else
  {
    /*************************************************************************/
    /* There is no room for the frame - return an error.                     */
    /*************************************************************************/

     pDX->IoctlRetStatus = STATUS_BUFFER_TOO_SMALL;
     pDX->Information    = IO_ERR_TX_BUFFER_FULL;
     rc = FALSE;
     TRACE_RC(rc);
  }

  TRACE_EVENT (IoT]);

  return (rc);

}

/*****************************************************************************/
/*                                                                           */
/* Name         RxFSMActionInvalid                                           */
/*                                                                           */
/* Purpose      Error input to FSM                                           */
/*                                                                           */
/*              This routine should never be called as it implies an         */
/*              'impossible state/input combination.  This is taken to mean  */
/*              that adapter status has been incorrectly reported and is     */
/*              treated as a hardware error.                                 */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT Error processing started by RQ FSM input                     */
/*                                                                           */
/*****************************************************************************/

void RxFSMActionInvalid (PDX pDX)
{
  TRACE_EVENT (RAI:);

  pDX->pIR->StatusArray[SA_HardwareError]++;
  pDX->pIR->StatusCount ++;
  pDX->DPCAction |= DPC_ACTION_PULSE;

  TRACE_EVENT (RAI;);
}

/*****************************************************************************/
/*                                                                           */
/* Name         RxFSMActionRestart                                           */
/*                                                                           */
/* Purpose      Coming back after having held rx so transmitter can go       */
/*              so - we can switch buffers so DMA can run for the whole buf  */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*              Implicit input: that when the receiver was stopped that the  */
/*              'NowBeingPut' RFD for this adapter was allocated and init-   */
/*              ialised properly.  So this is just a 'start reciver'.        */
/*                                                                           */
/*          OUT Receiver started                                             */
/*                                                                           */
/*                                                                           */
/*****************************************************************************/

void RxFSMActionRestart (PDX pDX)
{
  RFD     * pRFD = & pDX->RcvInfo.RFDArray[pDX->RcvInfo.RFDNowBeingPut];

  TRACE_EVENT (RRs:);

  pDX->LastPortA = Write8273Cmd (pDX, pDX->CmdStringReadPortA);

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    StopDMA (pDX);
    XASSERT (!pDX->DMAIsActive);

    /*************************************************************************/
    /* only worry about this if we using DMA and if other buffer not in use  */
    /*************************************************************************/
    if (!(RCVBUF_INUSE(pRFD->BufPtr->OtherBuffer)))
    {
      /* use other buffer */
      pRFD->BufPtr    = pRFD->BufPtr->OtherBuffer;
      pRFD->StartIndex= 0;
      pRFD->StartAddr = &pRFD->BufPtr->Data[0];
    }
  }
  RxFSMActionStart (pDX);
  TRACE_EVENT (RRs;);
}

/*****************************************************************************/
/*                                                                           */
/* Name         RxFSMActionStart                                             */
/*                                                                           */
/* Purpose      Starts the Receiver and DMA (if configured).                 */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*              Implicit input: that when the receiver was stopped that the  */
/*              'NowBeingPut' RFD for this adapter was allocated and init-   */
/*              ialised properly.  So this is just a 'start reciver'.        */
/*                                                                           */
/*          OUT Receiver started                                             */
/*                                                                           */
/*                                                                           */
/*****************************************************************************/

void RxFSMActionStart (PDX pDX)
{
  RFD     * pRFD = & pDX->RcvInfo.RFDArray[pDX->RcvInfo.RFDNowBeingPut];
  int       AvailableByteCount = RCVDATABUF_SIZE - 1 - pRFD->StartIndex;
                                        /* allocate all the rest of the buffr*/
                                        /* This really is the available bytes*/
  USHORT    CmdByteCount = CAST (AvailableByteCount - 2, USHORT);
                                        /* number of bytes to command 8273   */
  TRACE_EVENT (RSt:);

  XASSERT (AvailableByteCount >= CAST (pDX->LinkMaxFrameSize, int));
                                        /* frame must fit into rest of buffer*/
                                        /* or previous code screwed up       */
  XTRACE_ACTION (Ra, AvailableByteCount);

  /***************************************************************************/
  /* Prime the DMA if necessary.                                             */
  /***************************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    if (!pDX->DMAIsActive)
    {
      /***********************************************************************/
      /* If the DMA is already running we just leave it running.  Otherwise  */
      /* need to start it                                                    */
      /***********************************************************************/
      PHYSICAL_ADDRESS L;

      L = pRFD->BufPtr->DataPhysAddr;
      L.LowPart += (ULONG) pRFD->StartIndex;
      StartDMA (pDX,
                L,
                CmdByteCount,           /* should be one less, but we've left*/
                                        /* two bytes slop above anyway       */
                DMACmdWrite
             );
    }
    XASSERT (pDX->DMAIsActive);
  }
  else
  {
    pDX->pRxPIOData = pRFD->StartAddr;
    XTRACE_EVENT (RPIO);
    XTRACE_DWORD (pDX->pRxPIOData);
  }

  pDX->CmdStringReceive[2] = LOBYTE(CmdByteCount);
  pDX->CmdStringReceive[3] = HIBYTE(CmdByteCount);

  Write8273Cmd (pDX, pDX->CmdStringReceive);

  TRACE_EVENT (RSt;);
}

/****************************************************************************/
/*                                                                          */
/* Name         RxFSMActionStop                                                    */
/*                                                                          */
/* Purpose      Stops the Receiver and DMA (if configured).                 */
/*                                                                          */
/* Params    IN pDX                                                         */
/*                                                                          */
/*          OUT Receiver stopped                                            */
/*                                                                          */
/****************************************************************************/

void RxFSMActionStop (PDX pDX)
{
  TRACE_EVENT (RSo:);

  /*********************************************************************/
  /* Check whether DMA is supported and kill the DMA Channel if so.    */
  /*********************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    StopDMA (pDX);
    XASSERT (!pDX->DMAIsActive);
  }

  /*********************************************************************/
  /* Issue a Disable command to the 8273.                              */
  /*********************************************************************/

  Write8273Cmd (pDX, pDX->CmdStringDisableReceiver);

  /***************************************************************************/
  /* Following IO_DELAY is an attempt to prevent a hang reading port A in    */
  /* this situation                                                          */
  /***************************************************************************/
  IO_DELAY(5L);
  pDX->LastPortA = Write8273Cmd (pDX, pDX->CmdStringReadPortA);

  TRACE_EVENT (RSo;);
}

/****************************************************************************/
/*                                                                          */
/* Name         RxFSMActionRcvError                                                    */
/*                                                                          */
/* Purpose      Updates the received data statistics and restarts the Rcvr  */
/*                                                                          */
/*              Carries out end-of-frame processing on error results.       */
/*                                                                          */
/* Params    IN BX -> error result from the 8273                            */
/*                                                                          */
/*          OUT Updated Rx stats - receiver restarted.                      */
/*                                                                          */
/****************************************************************************/

void RxFSMActionRcvError (PDX pDX)
{
  /***************************************************************************/
  /* Clear the top nibble of result so we can detect non-octet boundary errs */
  /***************************************************************************/

  UCHAR   ResultCode = CAST(pDX->RxResultBuffer[0] & 0x0F, UCHAR);

  static
  USHORT  ErrorMapArray[] =             /* f: 8273code -> index in StatusArry*/
  {
     /*  0General       */ SA_Spare + 256,      /* +256->stop receiver       */
     /*  1Selective     */ SA_Spare + 256,
     /*  2<unused>      */ SA_HardwareError,    /* shouldn't be generated    */
     /*  3CRC           */ SA_CRC_Error + 256,
     /*  4Abort         */ SA_RxAbort + 256,
     /*  5Idle          */ 0,                   /* don't use                 */
     /*  6EOP           */ SA_Spare,
     /*  7Short         */ SA_RxFrameTooShort + 256,
     /*  8DMAOverrun    */ SA_RxOverrun,
     /*  9BufO          */ SA_RxFrameTooBig,
     /* 10RLSD          */ SA_DCDDrop,
     /* 11RxIntOverrun  */ SA_HardwareError,
     /* 12 tx           */ SA_HardwareError,
     /* 13 tx           */ SA_HardwareError,
     /* 14 tx           */ SA_HardwareError,
     /* 15 tx           */ SA_HardwareError,    /* 15 for 0x0F & value       */
  };

  XASSERT (ResultCode < 12);
  XTRACE_ACTION (Rm, ErrorMapArray[ResultCode]);

  /***************************************************************************/
  /* Filter out 'idle' indications before going through checks               */
  /***************************************************************************/

  if (ResultCode NE ARxR_ErrIdle)       /* no StopRequired - idle stop Rcvr  */
  {
    /*************************************************************************/
    /* Increment the statistics and set the flag to pulse event              */
    /*************************************************************************/

    pDX->pIR->StatusArray[LOBYTE(ErrorMapArray[ResultCode])]++;
    pDX->pIR->StatusCount++;
    pDX->DPCAction |= DPC_ACTION_PULSE;
  }

  /*********************************************************************/
  /* Check whether DMA is supported and kill the DMA Channel if so.    */
  /*********************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    StopDMA (pDX);
    XASSERT (!pDX->DMAIsActive);
  }

  /***************************************************************************/
  /* For those situations that need it (i.e.  where the error has left the   */
  /* receiver active), stop the receiver.  As a result, the receiver will    */
  /* always be stopped after an error.  This is just being paranoid -        */
  /* shouldn't really need to.                                               */
  /***************************************************************************/

  if (HIBYTE(ErrorMapArray[ResultCode]))        /* high byte->stop required  */
  {
    RxFSMActionStop (pDX);
  }

  /***************************************************************************/
  /* Now call the action routine to restart the receiver.                    */
  /***************************************************************************/

  RxFSMActionStart (pDX);

}

/*****************************************************************************/
/*                                                                           */
/* Name         RxFSMActionRcvOK                                             */
/*                                                                           */
/* Purpose      Updates the Receiver buffer data to reflect new data, does   */
/*              some statistics adjustment and kicks the Rx sema4.           */
/*                                                                           */
/*              Carries out end-of-received frame processing.                */
/*                                                                           */
/* Params    IN pDX                                                          */
/*              Implicit:pDX->ResultBuffer has all results                   */
/*                          [0] = OK result code                             */
/*                          [1] = Length (lo)                                */
/*                          [2] = Length (hi)                                */
/*                          [3] = Address                                    */
/*                          [4] = Control                                    */
/*                                                                           */
/*          OUT Updated Rx buffer and stats - receiver restarted.            */
/*                                                                           */
/*****************************************************************************/

void RxFSMActionRcvOK (PDX pDX)
{
                                        /* this is the 'Old' one             */
                                        /* New NowBeingPut                   */
  BOOLean       CanMoveOn = TRUE;       /* won't be true if no more RFDs OR  */
                                        /* no space in this buffer and other */
                                        /* buffer in use                     */
  BOOLean       MustStopReceiver = TRUE;
  short         NewNBP = CAST((pDX->RcvInfo.RFDNowBeingPut+1) % RFDARRAY_SIZE,
                              short);
  RFD         * pNewRFD= & pDX->RcvInfo.RFDArray[NewNBP];
  USHORT        RcvdLength;
  RFD         * pRFD   = & pDX->RcvInfo.RFDArray[pDX->RcvInfo.RFDNowBeingPut];

  TRACE_EVENT (ROk:);
  XTRACE_OBJECT(Rp, (*pRFD));
  XTRACE_DWORD (pRFD);
  XTRACE_OBJECT(Rb, (*(pRFD->BufPtr)));
  XTRACE_ACTION(Rs, pRFD->StartIndex);

  /***************************************************************************/
  /* First, copy results into existing RFD                                   */
  /***************************************************************************/

  XASSERT (pRFD->RcvdDataLength EQ -1);     /* this buffer hasn't been 'filled'  */

  pRFD->SDLCAddressByte = pDX->RxResultBuffer[3];
  pRFD->SDLCControlByte = pDX->RxResultBuffer[4];
  pRFD->RcvdDataLength  = MAKEUSHORT (pDX->RxResultBuffer[1],
                                      pDX->RxResultBuffer[2]);
  RcvdLength = pRFD->RcvdDataLength;
  XTRACE_ACTION (Rl,RcvdLength);

  /***************************************************************************/
  /* Double-check the length the 8273 tells us compared to what we rcvd on   */
  /* the interrupt side                                                      */
  /***************************************************************************/

#ifdef XDEBUG

  if (BITSOFF(pDX->GrabbedResources,GRABBEDRESOURCE_GOTDMA))
    XASSERT (CAST(pRFD->RcvdDataLength, int) EQ
                                          (pDX->pRxPIOData - pRFD->StartAddr));
#endif

  /***************************************************************************/
  /* Now figure out a) if we can fit next frame into this buffer, and b)     */
  /* failing that if we can start using the other buffer                     */
  /***************************************************************************/

  if (NewNBP EQ pDX->RcvInfo.RFDNextToGet)
  {
    CanMoveOn = FALSE;                  /* can't wrap round onto next to get */
  }
  else
  {

    /*************************************************************************/
    /* Set up the new startindex - this is OK even if we don't move onto new */
    /* RFD because it's not being used.                                      */
    /*                                                                       */
    /* Then see if we can let the receiver run - if not, we'll have to stop  */
    /* it and switch DMA etc.                                                */
    /*                                                                       */
    /* If we let the receiver run, we must calculate exactly where the data  */
    /* for the next frame is going to go!                                    */
    /*                                                                       */
    /* Note that if we are receiving into a given buffer, we always have the */
    /* rest of that buffer to play with, because we only strt using a new    */
    /* buffer if all received frames that were in it have been got out.      */
    /*************************************************************************/

    pNewRFD->StartIndex = CAST(pRFD->StartIndex + RcvdLength, short);

    if (CAST(pNewRFD->StartIndex, int) +
          CAST(pDX->LinkMaxFrameSize, int) +
          2                             /* +2 for possible CRC dribble       */
        < RCVDATABUF_SIZE)              /* (and some more paranoia).         */
    {
      /***********************************************************************/
      /* next packet fits into this buffer - can let receiver run            */
      /***********************************************************************/

      pNewRFD->BufPtr    = pRFD->BufPtr;
      pNewRFD->StartAddr = &pNewRFD->BufPtr->Data[pNewRFD->StartIndex];
      MustStopReceiver   = FALSE;
    }
    else
    {
      /***********************************************************************/
      /* This buffer is hopeless, so how about the other one - have all it's */
      /* frams been pulled?  If so, we can use it                            */
      /***********************************************************************/

      if (RCVBUF_INUSE(pRFD->BufPtr->OtherBuffer))
      {
        CanMoveOn = FALSE;              /* cant use him either               */
      }
      else
      {
        /* use other buffer */
        pNewRFD->BufPtr    = pRFD->BufPtr->OtherBuffer;
        pNewRFD->StartIndex= 0;
        pNewRFD->StartAddr = &pNewRFD->BufPtr->Data[0];
      }
    }
  }

  /***************************************************************************/
  /* If necessary, stop the receiver                                         */
  /***************************************************************************/

  if (MustStopReceiver)
  {
    RxFSMActionStop (pDX);
  }

  /***************************************************************************/
  /* If we will be moving on, do it & increment the count of waiting frames. */
  /* Whatever happens, we must signal that RFD is empty via RcvdDataLength       */
  /***************************************************************************/

   if (CanMoveOn)
   {
     RFD_PUT(pDX);

     pDX->RcvInfo.RFDNowBeingPut = NewNBP;
     pRFD                        = pNewRFD;
     pDX->pIR->RxFrameCount++;          /* show Rx frame available           */
   }
   pRFD->RcvdDataLength = -1;           /* empty !                           */

  TRACE_OBJECT(Rp, (*pRFD));
  XTRACE_DWORD (pRFD);
  XTRACE_OBJECT(Rb, (*(pRFD->BufPtr)));
  XTRACE_ACTION(Rs, pRFD->StartIndex);

  /***************************************************************************/
  /* A frame is ready for the application (or the buffer is full and the     */
  /* application should do some receiving) - set the flag so that its        */
  /* semaphore will be cleared by the ISR when we return.                    */
  /***************************************************************************/

  pDX->DPCAction |= DPC_ACTION_PULSE;

  /***************************************************************************/
  /* Now call the action routine to restart the receiver.                    */
  /***************************************************************************/

  RxFSMActionStart (pDX);

  TRACE_EVENT (ROk;);
}

/*****************************************************************************/
/*                                                                           */
/* Name         RxFSMEvent                                                   */
/*                                                                           */
/* Purpose      Interprets inputs from the Request handlers and from the     */
/*              ISR and calls the relevant action routines.                  */
/*                                                                           */
/*              There are two fundamental states:                            */
/*                                                                           */
/*              - idle                                                       */
/*              - ready (i.e. receiver is active)                            */
/*                                                                           */
/*              Each of these states, however, can also exist in two states  */
/*              depending on the activity of the transmitter which has to    */
/*              restrain the receiver when it is using the DMA.  These sub-  */
/*              states are idle(held) and ready(held).                       */
/*                                                                           */
/* Purpose      Routes processing to the relevant action routines.  The FSM  */
/*              action must be synchronized.  The action routines are allowed*/
/*              to call back to the FSM.                                     */
/*                                                                           */
/* Params    IN pDX                                                          */
/*              FSMInput - the input value                                   */
/*              Implicit - pDX->RxResultBuffer result bytes pulled from      */
/*                              Rx status register on 8273                   */
/*                                                                           */
/*          OUT Action routine called and new state set.                     */
/*                                                                           */
/*****************************************************************************/

void RxFSMEvent (PDX pDX, int Input)
{
  RXFSMENTRY *e      = &RxFSM [Input] [pDX->RxFSMCurState];

  /***************************************************************************/
  /* The FSM consists of a 'state' which is actually the address of the      */
  /* current FSM 'column'.  The 'input' is an offset down the column at      */
  /* which is located an action routine address and a new state.             */
  /***************************************************************************/

  TRACE_EVENT (<RFE);

  ASSERT (pDX->RxFSMCurState < RXFSMSTATECOUNT);
  ASSERT (Input              < RXFSMINPUTCOUNT);
  TRACE_EVENTNAME(RxFSMStateNames[pDX->RxFSMCurState]);
  TRACE_EVENTNAME(RxFSMInputNames[Input]);

  /***************************************************************************/
  /* Reset the state to the new state specified in the FSM table             */
  /* (Important to do this before the actions because some of them then do   */
  /* recursive calls to FSMEvent                                             */
  /***************************************************************************/

  pDX->RxFSMCurState = e->NewState;
  ASSERT (e->NewState        <  RXFSMSTATECOUNT);
  TRACE_EVENTNAME(RxFSMStateNames[pDX->RxFSMCurState]);

  /***************************************************************************/
  /* Do the action specified by the FSM table entry                          */
  /***************************************************************************/
  (*(e->pRxActionRoutine)) (pDX);

  TRACE_EVENT (RFE>);
}

/*****************************************************************************/
/*                                                                           */
/* Name         FSMNullAction                                                */
/*                                                                           */
/* Purpose      Carries out a 'nul' action for both Tx and Rx FSM            */
/*                                                                           */
/*****************************************************************************/

void FSMNullAction (PDX pDX)
{
  UNREFERENCED_PARAMETER (pDX);
  return;
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         SetLinkConfig (pDX)                                          */
/*                                                                           */
/* Purpose      Link Configuration Characteristics set-up.                   */
/*              Primes the 8273 and V24 interface in the manner required     */
/*              required by a particular set of link configuration options.  */
/*                                                                           */
/* Params    IN Link Options byte (pDX->LinkOptions) set up                  */
/*              Implicit input: the adapter has been reset and is ready to go*/
/*                                                                           */
/*          OUT Hardware is set up as indicated or carry set.                */
/*                                                                           */
/* Return Value BOOLean:    TRUE if options set up OK, otherwise FALSE       */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean SetLinkConfig (PDX pDX)
{
  UCHAR o;
  BOOLean rc;

  TRACE_EVENT (<SLC);

  ASSERT (pDX->HardwareError EQ FALSE);   /* Reset function should turn off  */
                                          /* hardware error flag             */

  /***************************************************************************/
  /* Check the configuration bits and use the data to set up the mode set    */
  /* and reset commands held on the LCB.  Then run through the whole batch   */
  /* in a tight loop at the end.                                             */
  /***************************************************************************/

  /***************************************************************************/
  /* Operating mode                                                          */
  /***************************************************************************/
  o = AP_OMBFR;                           /* assume default (buffered)       */
  if (pDX->LinkOptionsByte & LinkOption_HDLC)
  {
    o |= AP_OMX25;                        /* set HDLC Abort                  */
  }

  pDX->CmdStringSetOpMode[2]  = CAST(o, UCHAR);
  pDX->CmdStringResetOpMode[2]= CAST(o | APR_OM, UCHAR);

  /***************************************************************************/
  /* Serial IO Mode                                                          */
  /***************************************************************************/
  o = 0;                                /* assume SIO mode is NRZ */
  if (pDX->LinkOptionsByte & LinkOption_NRZI)           /* then check for NRZI selected */
  {
    o |= AP_IONZI;                      /* set NRZI bit in the mask */
  }
  pDX->CmdStringSetSerialIOMode[2]= CAST (o, UCHAR);
  pDX->CmdStringResetSerialIOMode[2]= CAST (o | APR_IO, UCHAR);
                                          /* set the unused bits for reset */
  /***************************************************************************/
  /* Sort out DMA.  Assume for now we will be able to use an architectural   */
  /* NT-defined way of avoiding conflicts on DMA channels,                   */
  /***************************************************************************/

  pDX->CmdStringDataTransferMode[0]= 0x97;/* assume Data Transfer is PIO */
  pDX->CmdStringDataTransferMode[2]= 1;
  pDX->GrabbedResources &= ~GRABBEDRESOURCE_GOTDMA; /*and clear DMA privelege*/

  if (pDX->LinkOptionsByte & LinkOption_DMA &&  /* DMA Requested ?           */
      pDX->ConfigData.DMAChannel NE 0)          /* and DMA possible for card */
  {
    /*************************************************************************/
    /* Read this link's configured DMA channel and check for non-0           */
    /*************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// guff to do with protecting ourselves from other guy using same DMA channel
// reinstate later if necessary
//
//      if <SI eq <offset SELCBL1>>
//
//         mov DI,offset SELCBL2
//
//      else
//
//         mov DI,offset SELCBL1
//
//      }
//
//     /**********************************************************************/
//     /* Check whether the links use the same DMA and grab it if not.       */
//     /* Otherwise look to see whether the other link does not currently    */
//     /* have DMA.                                                          */
//     /**********************************************************************/
//
//      if <AL ne pDX->DMAChan> OR
//      test pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA
//      if z
///////////////////////////////////////////////////////////////////////////////

    pDX->GrabbedResources |= GRABBEDRESOURCE_GOTDMA; /*  register ownership! */
    pDX->DMAIsActive      =  FALSE;
    pDX->CmdStringDataTransferMode[0]= 0x57;     /*  and set DT mode to DMA  */
    pDX->CmdStringDataTransferMode[2]= 0xFE;     /* ..rather than interrupts */

    /*************************************************************************/
    /* If this is MCA Extended DMA then we have to set up an I/O transfer    */
    /* address.                                                              */
    /*************************************************************************/

    if (pDX->ConfigData.DMAChannel NE StandardDMAChannel)
    {
///////////////////////////////////////////////////////////////////////////////
//?ML?
// from semdh.asm
//                  and AL,0fh                  ;clear top four bits
//                  out LDD_DMAEFR,AL           ;write out the fncn cmnd
//
//                  mov AX,LCB.LCBPrtBase
//                  add AX,LDD_73Data
//                  out LDD_DMAEFE,AL           ;write out the LOB
//                  SEPDHDLY            ;let it take effect
//                  mov AL,AH
//                  out LDD_DMAEFE,AL           ;write out the HOB
//////////////////////////////////////////////////////////////////////////////

      USHORT Addr = CAST(pDX->ADAPTERBASE+AR_8273D, USHORT);

      WR_N_DELAY (DMAExtdFnRegister, CAST(pDX->ConfigData.DMAChannel & 0x0F,  \
                                          UCHAR));
      WR_N_DELAY (DMAExtdFnRegister, LOBYTE(Addr));   /* write out the LOB   */
      WR_N_DELAY (DMAExtdFnRegister, HIBYTE(Addr));   /* and HOB             */
    }
  }

  /***************************************************************************/
  /* Now actually issue the mode setting commands to the 8273.               */
  /***************************************************************************/

  Write8273Cmd(pDX,pDX->CmdStringResetOpMode);
  Write8273Cmd(pDX,pDX->CmdStringResetSerialIOMode);
  Write8273Cmd(pDX,pDX->CmdStringSetOpMode);
  Write8273Cmd(pDX,pDX->CmdStringSetSerialIOMode);
  Write8273Cmd(pDX,pDX->CmdStringDataTransferMode);
  rc = !pDX->HardwareError;

  TRACE_RCFALSE(rc);
  TRACE_EVENT (SLC>);
  return (rc);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         SetV24Output (                                               */
/*                            PDX pDX                                        */
/*                           );                                              */
/*                                                                           */
/* Purpose      Primes the 8273 and V24 interfaces as requested in IR->V24Out*/
/*                                                                           */
/* Params   IN  pDX - the device extension                                   */
/*              Implicit input: V24Out byte in IR is what is to be pumped out*/
/*                                                                           */
/* Return Value BOOLean: TRUE if commands etc. correctly written             */
/*                                                                           */
/* Operation                                                                 */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLean SetV24Output (PDX pDX)
{
  /**********************************************************************/
  /* Do the 8255 Port B settings first.  Note that their sense is      */
  /* logically inverted and 0 = ON.                                    */
  /*********************************************************************/

  UCHAR o;
  UCHAR V24Out = pDX->pIR->V24Out;

  o = 8;                                /* NOT reset modem status logic      */
  o |= (V24Out & IR_OV24DSRS)           /* user wants DSRS on?               */
       ? 0                              /* 0 in port B turns on DSRS         */
       : 1;
  o |= (V24Out & IR_OV24SlSt)           /* user wants Select Standby on?     */
       ? 0                              /* 0 in port B turns on Select Stndby*/
       : 2;
  o |= (V24Out & IR_OV24Test)           /* user wants Test on?               */
       ? 0                              /* 0 in port B turns on Test         */
       : 4;

  WR_N_DELAY (pDX->ADAPTERBASE + AR_8255B, o);

  /*********************************************************************/
  /* Now do the 8273 Port B settings.                                  */
  /*********************************************************************/

  o = 0;
  o |= (V24Out & IR_OV24RTS)            /* user wants RTS up?                */
       ? 1
       : 0;
  o |= (V24Out & IR_OV24DTR)            /* user wants DTR up?                */
       ? 4
       : 0;

  pDX->CmdStringSetPortB[2]= o;               /* save value in 'set' cmd     */
  pDX->CmdStringResetPortB[2]= CAST(0xC0 | o, UCHAR);
                                              /* and also in 'reset' cmd     */

  Write8273Cmd (pDX,pDX->CmdStringSetPortB);    /* turn on what should be on */
  Write8273Cmd (pDX,pDX->CmdStringResetPortB); /*and turn off what shouldn't*/

  return(!pDX->HardwareError);
}

/*****************************************************************************/
/*                                                                           */
/* Name         StartDMA (                                                   */
/*                        PDX              pDX                               */
/*                        PHYSICAL_ADDRESS PhysicalAddress                   */
/*                        USHORT           BufferLength                      */
/*                        UCHAR            OpCode                            */
/*                       );                                                  */
/*                                                                           */
/* Purpose      Issues a Receive/Transmit command to the DMA.  The address   */
/*              and length information are all in registers, other than the  */
/*              segment physical base address which is in global data.       */
/*                                                                           */
/* Params    IN PhysicalAddress (not System VAS address!)                    */
/*              BufferLength is maximum data length                          */
/*              (although the 8273 controls exactly how many transfers are   */
/*              pulled).  So this count really acts as a stopper and should  */
/*              (due to stupid design of 8273) be one less than overflow val */
/*              OpCode is DMACmdRead/Write                                   */
/*                                                                           */
/*          OUT Command issued to DMA.                                       */
/*                                                                           */
/* Modified:    31/03/88 Initial coding                                      */
/*                                                                           */
/*****************************************************************************/

void StartDMA(PDX              pDX,
              PHYSICAL_ADDRESS PhysicalAddress,
              USHORT           BufferLength,
              UCHAR            OpCode)
{
  PHYSICAL_ADDRESS a;
  USHORT           b;

  TRACE_EVENT (<SDM);

  ASSERT (!DMACrosses64K(PhysicalAddress.LowPart, BufferLength));

  /***************************************************************************/
  /* First, test whether the channel number is set to 1.  If not, then this  */
  /* is an MCA machine using a non-standard channel, so dothe MCA version of */
  /* this routine                                                            */
  /***************************************************************************/

  if (pDX->ConfigData.DMAChannel EQ StandardDMAChannel)/* good ole channel one       */
  {
    /*************************************************************************/
    /* Mask off the channel we're about to play with.                        */
    /*************************************************************************/

    IO_OUT (DMAMaskRegister, DMAMaskChannel1);

    /*************************************************************************/
    /* The hard part is generating an address to give the DMA chip.  We      */
    /* already know, because initialisation ensures it, that the buffer will */
    /* not span a 64k boundary, but we have to generate the full physical    */
    /* address from the offset supplied in DI and the physical address of    */
    /* DGROUP read in during initialisation.                                 */
    /*************************************************************************/

    IO_OUT (DMAFirstByteFlipFlop,0);    /* data ignored, OUT resets FF       */

    a = PhysicalAddress;

    WR_N_DELAY (DMAPhysAddress, a.LowPart & 0xFF);  /* first, set physaddr low byte  */
    a.LowPart = a.LowPart >> 8;
    WR_N_DELAY (DMAPhysAddress, a.LowPart  & 0xFF);  /* next, set physaddr high byte  */
    a.LowPart = a.LowPart >> 8;
    WR_N_DELAY (DMAPageRegister,a.LowPart  & 0xFF)   /* finally 64k page register value*/

    /*************************************************************************/
    /* Next give the chip the maximum size count passed in BufferLength.     */
    /*************************************************************************/

    WR_N_DELAY (DMAFirstByteFlipFlop,0);    /* ML did this so I will too ?   */

    b = BufferLength;
    WR_N_DELAY (DMACountRegister, a.LowPart & 0xFF);
    b = b >> 8;
    WR_N_DELAY (DMACountRegister, a.LowPart & 0xFF);

    /*************************************************************************/
    /* Set up the mode value as passed in.                                   */
    /*************************************************************************/

    WR_N_DELAY (DMAModeRegister, OpCode);

    /*************************************************************************/
    /* Finally kick the chip off by clearing the channel mask bit.           */
    /*************************************************************************/

    IO_OUT (DMAMaskRegister, DMAClearChannel1);

  }
  else
  {
    /*************************************************************************/
    /* This must be an MCA machine with programmable DMA channels.  The only */
    /* actual option at present is '7' for the second MPA/A.                 */
    /*************************************************************************/

    ASSERT (pDX->ConfigData.DMAChannel EQ 7);

    /*************************************************************************/
    /* Set the mask for this channel.                                        */
    /*************************************************************************/

    IO_OUT (DMAExtdFnRegister, pDX->ConfigData.DMAChannel | MCADMASetMask);

    /*************************************************************************/
    /* Give the DMA registers the data xfer start address.                   */
    /*************************************************************************/

    IO_OUT (DMAExtdFnRegister, pDX->ConfigData.DMAChannel | MCADMAAddress);

    a = PhysicalAddress;

    WR_N_DELAY (DMAExtdFnRegister, a.LowPart & 0xFF);      /* and then set address bits 0-7 */
    a.LowPart = a.LowPart >> 8;
    WR_N_DELAY (DMAExtdFnRegister, a.LowPart & 0xFF);      /* set address bits 8 - 15       */
    a.LowPart = a.LowPart >> 8;
    WR_N_DELAY (DMAExtdFnRegister, a.LowPart & 0xFF);      /* set address bits 16 - 23      */

    /*************************************************************************/
    /* Now set up the maximum xfer count.                                    */
    /*************************************************************************/

    IO_OUT (DMAExtdFnRegister, pDX->ConfigData.DMAChannel | MCADMACount);

    a.LowPart = BufferLength;
    WR_N_DELAY (DMAExtdFnRegister, a.LowPart & 0xFF);      /* length low byte               */
    a.LowPart = a.LowPart >> 8;
    WR_N_DELAY (DMAExtdFnRegister, a.LowPart & 0xFF);      /* length high byte              */

    /*************************************************************************/
    /* Now set the mode register for the channel (read/write).  This is done */
    /* on the basis of the original old-style mode being for read or for     */
    /* write.                                                                */
    /*************************************************************************/

    IO_OUT (DMAExtdFnRegister, pDX->ConfigData.DMAChannel | MCADMAMode);

    WR_N_DELAY (DMAExtdFnRegister, (OpCode EQ DMACmdWrite) ? 0x0D : 0x05);

    /*************************************************************************/
    /* Last job is to reset the mask register for the channel.               */
    /*************************************************************************/

     IO_OUT (DMAExtdFnRegister, pDX->ConfigData.DMAChannel | MCADMAClearMask);
  }
  pDX->DMAIsActive = TRUE;
  TRACE_EVENT (SDM>);
}

/*****************************************************************************/
/*                                                                           */
/* Name         StopDMA                                                      */
/*                                                                           */
/* Purpose      Link Device Driver Stop DMA Routine.                         */
/*                                                                           */
/*              Kills the DMA by masking the appropriate channel.            */
/*                                                                           */
/* Params    IN pDX                                                          */
/*              Implicit: DMA channel allocated                              */
/*                                                                           */
/*          OUT Command issued to DMA.                                       */
/*                                                                           */
/*****************************************************************************/

void StopDMA (PDX pDX)
{
  TRACE_EVENT (<ZDM);

  ASSERT (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA);

  if (pDX->ConfigData.DMAChannel EQ StandardDMAChannel)
  {
    /*************************************************************************/
    /* Channel #1 - set the mask directly.                                   */
    /*************************************************************************/

    IO_OUT (DMAMaskRegister, DMAMaskChannel1);
  }
  else
  {
    /*************************************************************************/
    /* Not channel 1 - set the mask using the Extended function addressing   */
    /* register.                                                             */
    /*************************************************************************/

    IO_OUT (DMAExtdFnRegister, pDX->ConfigData.DMAChannel | MCADMASetMask);
  }
  pDX->DMAIsActive = FALSE;
  TRACE_EVENT (ZDM>);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        SynchReset8273 (                                             */
/*                          PDEVICE_OBJECT pDeviceObject                     */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     Reset the 8273 and set the 'Closing' flag so that following  */
/*              interrupts are ignored (until we disable)                    */
/*              with interrupt processing                                    */
/*                                                                           */
/* Params:   IN Context         is really pDX                                */
/*                                                                           */
/* Return Value:None                                                         */
/*                                                                           */
/* Operation:                                                                */
/*                                                                           */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLEAN SynchReset8273 (PVOID Context)
{
  PDX pDX = (PDX) Context;
  TRACE_EVENT (<XR8);

  pDX->AdapterIsClosing = TRUE;
  AdapterReset (pDX);

  TRACE_EVENT(XR8>);

  return (FALSE);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        SynchTerminateAdapter(                                       */
/*                          PDEVICE_OBJECT pDeviceObject                     */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     Close a particular device - stuff that needs to be interlockd*/
/*              with interrupt processing                                    */
/*                                                                           */
/* Params:   IN Context         is really pDX                                */
/*                                                                           */
/* Return Value:None                                                         */
/*                                                                           */
/* Operation:                                                                */
/*                                                                           */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLEAN SynchTerminateAdapter (PVOID Context)
{
  PDX pDX = (PDX) Context;
  TRACE_EVENT (<XTA);

  TerminateAdapter (pDX);

  TRACE_EVENT(XTA>);

  return (FALSE);
}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name:        SynchEntryPointOpen (                                        */
/*                          PDEVICE_OBJECT pDeviceObject                     */
/*                         )                                                 */
/*                                                                           */
/* Purpose:     Initialise the next device (from GetDriverSpec)              */
/*                                                                           */
/* Params:   IN Context         is really pDX                                */
/*                                                                           */
/* Return Value:BOOLEAN:        True if object opened OK                     */
/*                                                                           */
/* Operation:                                                                */
/*              1. Copy over pre-initialised data sequences                  */
/*                                                                           */
/*                                                                           */
/**PROC-**********************************************************************/

BOOLEAN SynchEntryPointOpen (PVOID Context)
{
  PDX pDX = (PDX) Context;
  BOOLEAN rc;
  TRACE_EVENT (<XEO);

  rc = CAST (InitialiseAdapter (pDX), BOOLEAN);
                                        /* if OK, InitAdapter returns TRUE   */
  TRACE_EVENT(XEO>);
  return (rc);
}

/*****************************************************************************/
/*                                                                           */
/* Name         TerminateAdapter                                             */
/*                                                                           */
/* Purpose      Link Device Driver Hardware Termination.                     */
/*                                                                           */
/*              Does the physical termination, but leaves assigned resurces  */
/*              still assigned                                               */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT Hardware is cleared down.                                    */
/*                                                                           */
/*****************************************************************************/

void TerminateAdapter (PDX pDX)
{
  /***************************************************************************/
  /* Reset the 8255 and 8273.                                                */
  /***************************************************************************/

  IO_OUT (pDX->ADAPTERBASE + AR_8255B, A55_Reset8273On);

  /***************************************************************************/
  /* Documentation says use a long delay while resetting!                    */
  /* (but I'm blowed if I can find where it says it.  Try 10 microsecs.)     */
  /***************************************************************************/

  KeStallExecutionProcessor (10L);

  WR_N_DELAY (pDX->ADAPTERBASE + AR_8255B, A55_Reset8273Off);
  WR_N_DELAY (pDX->ADAPTERBASE + AR_8255B, A55_Reset8273Off);

  IO_OUT (pDX->ADAPTERBASE + AR_8255C, A55_ResetPortC);

  /***************************************************************************/
  /* Ensure that an MPCA is completely 'turned off'.                         */
  /***************************************************************************/

  if (pDX->ConfigData.MPCAModePort NE 0)
  {
    IO_OUT (pDX->ConfigData.MPCAModePort, AC_MPCAD); /* MPCAD = disable       */
    TRACE_DATABYTE (Mpc, AC_MPCAD);
  }

  /***************************************************************************/
  /* Check whether this channel was using the DMA and clear it down.         */
  /***************************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    StopDMA(pDX);                        /* stop the channel doing anything */
    pDX->GrabbedResources &= ~GRABBEDRESOURCE_GOTDMA;
  }
}

/****************************************************************************/
/*                                                                          */
/* Name         TxFSMActionAbort                                                    */
/*                                                                          */
/* Purpose      Aborts the current transmission.                            */
/*                                                                          */
/* Params    IN pDX.  Transmitter going presumably.                         */
/*                                                                          */
/*          OUT Receiver started                                            */
/*                                                                          */
/****************************************************************************/

void TxFSMActionAbort (PDX pDX)
{
  TRACE_EVENT (TAb:);

  Write8273Cmd (pDX, pDX->CmdStringAbortTransmit);

  TRACE_EVENT (TAb;);
}

/****************************************************************************/
/*                                                                          */
/* Name         TxFSMActionEndError                                                    */
/*                                                                          */
/* Purpose      Handles Tx error conditions by recording and retrying       */
/*                                                                          */
/*              Updates statistics,                                         */
/*              Restarts the transmitter.                                   */
/*                                                                          */
/* Params    IN pDX, 8273 result code in TxResult                           */
/*                                                                          */
/*          OUT Tx Stats updated, transmitter restarted                     */
/*                                                                          */
/****************************************************************************/

void TxFSMActionEndError (PDX pDX)
{
  TRACE_EVENT (TEr:);

  /*********************************************************************/
  /* Check whether DMA is supported and kill the DMA Channel if so.    */
  /*********************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    StopDMA (pDX);
    XASSERT (!pDX->DMAIsActive);
  }

  /***************************************************************************/
  /* Increment the stats count and set the flag to pulse the event           */
  /***************************************************************************/

  pDX->pIR->StatusCount++;
  pDX->DPCAction |= DPC_ACTION_PULSE;

  /***************************************************************************/
  /* Basically we translate the error to an index into the stats array held  */
  /* in the interface record and increment it.                               */
  /***************************************************************************/

  TRACE_DATABYTE (TEb, pDX->TxResult);

  if (pDX->TxResult EQ ATxR_ErrTxUnderrun)
  {
    /*************************************************************************/
    /* Check how many times we have tried to resend this frame.  If this was */
    /* the third attempt then give up, otherwise try again.                  */
    /*************************************************************************/

    pDX->TxConsecutiveUnderrunCount++;      /* increment count of underruns  */
    if (pDX->TxConsecutiveUnderrunCount > 2)
    {
      TxFSMActionEndOK(pDX);                /* forget this frame - pretend OK*/
    }
    else
    {
       TxFSMEvent (pDX, TxFSMInputStart);   /* tell FSM to send again        */
    }

    pDX->pIR->StatusArray[SA_TxUnderrun]++; /* transmitter underrun          */
  }
  else if (pDX->TxResult EQ ATxR_ErrTxCTSDrop)
  {
    TxFSMActionEndOK (pDX);                 /* forget this frame - pretend OK*/
    pDX->pIR->StatusArray[SA_CTSDrop]++;    /* clear to send dropped         */
  }
  else
  {
    TxFSMActionEndOK (pDX);                 /* forget this frame - pretend OK*/
    pDX->pIR->StatusArray[SA_HardwareError]++;
                                            /* unknown result byte!          */
  }

  TRACE_EVENT (TEr;);
}

/****************************************************************************/
/*                                                                          */
/* Name         TxFSMActionEndOK                                                    */
/*                                                                          */
/* Purpose      Frame transmission complete.                                */
/*                                                                          */
/*              Handles the frame transmission completion actions.          */
/*              Updates the Tx buffer data.                                 */
/*              Starts the next transmission if data is available.          */
/*                                                                          */
/* Params    IN pDX                                                         */
/*                                                                          */
/*          OUT Tx Data Buffer updated                                      */
/*                                                                          */
/****************************************************************************/

void TxFSMActionEndOK (PDX pDX)
{
  USHORT FreedLength;                   /* how many bytes freed up from bufr */

  TRACE_EVENT (TOk:);

  /*********************************************************************/
  /* Check whether DMA is supported and kill the DMA Channel if so.    */
  /*********************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    StopDMA (pDX);
    XASSERT (!pDX->DMAIsActive);
  }

  /*********************************************************************/
  /* Clear the count of consecutive transmitter underruns.             */
  /*********************************************************************/

  pDX->TxConsecutiveUnderrunCount = 0;

  /***************************************************************************/
  /* Read the 'tail' and work out the length of the area freed up by the     */
  /* completion of this transmission.  Divide that by two and add it to the  */
  /* 'maximum' Tx frame size on the interface.  The 'max' is in fact half    */
  /* the total size of the transmit buffer (with some allowance for length   */
  /* words and rounding up) because the free area within the buffer can be   */
  /* broken into at most two non-contiguous sections.                        */
  /***************************************************************************/

  FreedLength = CAST(MAKEUSHORT (pDX->pSendBuf[pDX->TxNextToTransmit  ],  \
                                 pDX->pSendBuf[pDX->TxNextToTransmit+1])
                           + 2,         /* +2 : freeing up length as well    */
                     USHORT);

  pDX->pIR->TxMaxFrSizeNow += (FreedLength + 1) / 2;
                                        /* making sure we round UP!          */
  XTRACE_ACTION (Tz, pDX->pIR->TxMaxFrSizeNow);

  /***************************************************************************/
  /* Calculate the new next-to-transmit point, wrapping if necessary         */
  /***************************************************************************/

  pDX->TxNextToTransmit += FreedLength;
  if (pDX->TxNextToTransmit >= pDX->TxStartUnusedArea)
  {
    /*************************************************************************/
    /* The last buffer we sent was the right-most in the buffer - so we must */
    /* reset the unused area and NextToTranmsit                              */
    /*************************************************************************/

    pDX->TxStartUnusedArea = SENDBUF_SIZE;
    pDX->TxNextToTransmit  = 0;
    XTRACE_ACTION (Tu, pDX->TxStartUnusedArea);
   }
   XTRACE_ACTION (Tt, pDX->TxNextToTransmit);


  /***************************************************************************/
  /* Now check whether there is more data in the buffer and send it.  If     */
  /* there are no more frames, check whether the application has finished    */
  /* the current transmission and stop the transmitter if so.                */
  /***************************************************************************/

  if (pDX->TxNextToTransmit NE pDX->TxNextToBuffer)
  {
    TxFSMEvent (pDX, TxFSMInputStart);
  }
  else
  {
    /*************************************************************************/
    /* The buffer is empty so we need to stop the transmitter if we are half */
    /* duplex and we have indeed sent the poll/final bit (as defined in the  */
    /* 'C' byte of the last transmit command (ooh, it's so tricky).          */
    /*************************************************************************/

    if (BITSOFF(pDX->LinkOptionsByte, LinkOption_FullDuplex) &&
        pDX->CmdStringTransmit[5] & POLLFINL
       )
    {
      TxFSMEvent (pDX, TxFSMInputStop);
    }

    /*************************************************************************/
    /* We have to tell the DLC software when the buffer goes empty.          */
    /*************************************************************************/

    pDX->DPCAction |= DPC_ACTION_PULSE; /* do a PULSE-EVENT from the DPC     */
  }
  TRACE_EVENT (TOk;);
}

/*****************************************************************************/
/*                                                                           */
/* Name         TxFSMActionInvalid                                                     */
/*                                                                           */
/* Purpose      Error input to FSM                                           */
/*                                                                           */
/*              This routine should never be called as it implies an         */
/*              'impossible state/input combination.  This is taken to mean  */
/*              that adapter status has been incorrectly reported and is     */
/*              treated as a hardware error.                                 */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT Error processing started by RQ FSM input                     */
/*                                                                           */
/*****************************************************************************/

void TxFSMActionInvalid (PDX pDX)
{
  TRACE_EVENT(T!!!);
  pDX->pIR->StatusArray[SA_HardwareError]++;
  pDX->pIR->StatusCount++;
  pDX->DPCAction |= DPC_ACTION_PULSE;
}

/****************************************************************************/
/*                                                                          */
/* Name         TxFSMActionStart                                            */
/*                                                                          */
/* Purpose      Activates the Transmitter.                                  */
/*                                                                          */
/*              Raise RTS (if not raised already).                          */
/*              Start flag streaming.                                       */
/*              Stop the receiver (if not full duplex).                     */
/*              Initiate the first transmission.                            */
/*                                                                          */
/* Params    IN Data held in Device Object                                  */
/*                                                                          */
/*          OUT Transmitter Active and Transmit command issued.             */
/*                                                                          */
/****************************************************************************/

void TxFSMActionStart (PDX pDX)
{
  TRACE_EVENT (TSt:);
  /***************************************************************************/
  /* Put the transmitter into Flag Stream mode while it is active.           */
  /***************************************************************************/

  pDX->CmdStringSetOpMode[2] |= 1;      /* turn on flag stream mode          */
  Write8273Cmd (pDX, pDX->CmdStringSetOpMode);

  /***************************************************************************/
  /* Ensure that RTS is up.                                                  */
  /***************************************************************************/

  pDX->CmdStringSetPortB[2] |= 1;       /* 1 = RTS line - turn it up         */
  Write8273Cmd (pDX, pDX->CmdStringSetPortB);
  pDX->pIR->V24Out |= IR_OV24RTS;       /* record fact that RTS is on!       */

  /***************************************************************************/
  /* We are just starting the transmitter up.  If the link is not full       */
  /* duplex then stop the receiver while we are transmitting.                */
  /***************************************************************************/

  if (BITSOFF(pDX->LinkOptionsByte, LinkOption_FullDuplex))
  {
    RxFSMEvent (pDX, RxFSMInputHold);
  }

  /***************************************************************************/
  /* Lastly, start the first transmission.                                   */
  /***************************************************************************/

  TxFSMActionXmitNext (pDX);
  TRACE_EVENT (TSt;);

}

/*****************************************************************************/
/*                                                                           */
/* Name         TxFSMActionStop                                              */
/*                                                                           */
/* Purpose      Closes down the transmitter                                  */
/*                                                                           */
/*              Drops RTS (if 2-wire)                                        */
/*              Stops flag stream and (if not HDLC)                          */
/*              Re-enables the receiver (if half duplex)                     */
/*                                                                           */
/* Params    IN pDX                                                          */
/*                                                                           */
/*          OUT Receiver started if HDX                                      */
/*                                                                           */
/*****************************************************************************/

void TxFSMActionStop (PDX pDX)
{
  TRACE_EVENT (TZap);
  /*********************************************************************/
  /* Check whether DMA is supported and kill the DMA Channel if so.    */
  /*********************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    StopDMA (pDX);
    XASSERT (!pDX->DMAIsActive);
  }

  if (BITSOFF(pDX->LinkOptionsByte, LinkOption_4Wire))
  {
    /*************************************************************************/
    /* Modem 2-wire : Turn off RTS and drop it in the interface flags.       */
    /*************************************************************************/

     pDX->CmdStringSetPortB[2]   &= ~1;
     pDX->CmdStringResetPortB[2] &= ~1;
     Write8273Cmd (pDX, pDX->CmdStringResetPortB);
     pDX->pIR->V24Out &= ~IR_OV24RTS;   /* tell appl that RTS is off!        */
  }

  /*********************************************************************/
  /* Turn off Flag Stream Mode.                                        */
  /*********************************************************************/

  pDX->CmdStringSetOpMode[2]  &= ~1;
  pDX->CmdStringResetOpMode[2]&= ~1;
  Write8273Cmd (pDX, pDX->CmdStringResetOpMode);

  pDX->LastPortA = Write8273Cmd (pDX, pDX->CmdStringReadPortA);

  /*********************************************************************/
  /* Release the receiver if this is a half duplex link.               */
  /*********************************************************************/

  if (BITSOFF(pDX->LinkOptionsByte, LinkOption_FullDuplex))
  {
    RxFSMEvent (pDX, RxFSMInputRelease);
  }
}

/*****************************************************************************/
/*                                                                           */
/* Name         TxFSMActionXmitNext                                          */
/*                                                                           */
/* Purpose      Transmits the Next Frame in the Buffer                       */
/*                                                                           */
/*              Set up the transmit command                                  */
/*              Set up the DMA (if used)                                     */
/*              Issue the transmit command.                                  */
/*                                                                           */
/* Params    IN pDX, plus xmitter already active                             */
/*                                                                           */
/*              NextToTransmit: +0) length of buffered frame (A+C+data)      */
/*                              +1)                                          */
/*                              +2  A(ddress)                                */
/*                              +3  C(ontrol)                                */
/*                                                                           */
/*              Total buffer taken up is Length + 2.                         */
/*                                                                           */
/*          OUT Transmission under way.                                      */
/*                                                                           */
/*****************************************************************************/

void TxFSMActionXmitNext (PDX pDX)
{
  USHORT LengthFor8273;
  UCHAR *p;
  UCHAR  TempLo;
  UCHAR  TempHi;

  TRACE_EVENT (TSn:);

  /***************************************************************************/
  /* Before issuing the transmit command, read in the current Port A value   */
  /* as we won't be able to if both the transmitter and receiver become      */
  /* active concurrently.                                                    */
  /***************************************************************************/

  pDX->LastPortA = Write8273Cmd (pDX, pDX->CmdStringReadPortA);

  /***************************************************************************/
  /* Read the current tail position and check for a buffer wrap (tail =      */
  /* current length of buffer).                                              */
  /***************************************************************************/

  if (pDX->TxNextToTransmit >= pDX->TxStartUnusedArea)
  {
    /*************************************************************************/
    /* The last buffer we sent was the right-most in the buffer - so we must */
    /* reset the unused area and NextToTranmsit                              */
    /*************************************************************************/

    pDX->TxStartUnusedArea = SENDBUF_SIZE;
    pDX->TxNextToTransmit  = 0;
    XTRACE_ACTION (Tu, pDX->TxStartUnusedArea);
    XTRACE_ACTION (Tt, pDX->TxNextToTransmit);
  }

  /***************************************************************************/
  /* Found the next frame - read its length and set up Tx Command.           */
  /***************************************************************************/

  p = &pDX->pSendBuf[pDX->TxNextToTransmit];
  TempLo = *(p++);                              /* first two bytes are length*/
  TempHi = *(p++);
  LengthFor8273 = CAST (TempLo + (TempHi <<8)-2,/* allow for the buffered A+C*/
                        USHORT);

  ASSERT (CAST(LengthFor8273, int  ) < pDX->LinkMaxFrameSize);
  ASSERT (CAST(LengthFor8273, short) >= 0);

  pDX->CmdStringTransmit[2] = LOBYTE(LengthFor8273);
                                        /* and put it into the cmd           */
  pDX->CmdStringTransmit[3] = HIBYTE(LengthFor8273);

  /***************************************************************************/
  /* and set up the (buffered) A & C in the command                          */
  /***************************************************************************/

  TRC_IN_DW (*p);
  pDX->CmdStringTransmit[4] = *(p++);           /* next two bytes are A&C    */
  pDX->CmdStringTransmit[5] = *(p++);

  pDX->pTxPIOData = p;                          /* next is PIO data start pos*/
  TRACE_EVENT (TPIO);
  TRACE_DWORD (pDX->pTxPIOData);

  /***************************************************************************/
  /* Now prime the DMA if necessary                                          */
  /***************************************************************************/

  if (pDX->GrabbedResources & GRABBEDRESOURCE_GOTDMA)
  {
    PHYSICAL_ADDRESS L;
    if (pDX->DMAIsActive)
    {
      StopDMA (pDX);
      XASSERT (!pDX->DMAIsActive);
    }


    L = pDX->SendBufPhysAddr;
    L.LowPart += pDX->TxNextToTransmit + 4;

    StartDMA(pDX,
             L,
             LengthFor8273,              /* but 8273 will control how many snt*/
             DMACmdRead                  /* set the mode */
            );
    XASSERT (pDX->DMAIsActive);
  }

  /******************************************************************/
  /* Lastly, issue the transmit command to the 8273.                */
  /******************************************************************/

  Write8273Cmd (pDX, pDX->CmdStringTransmit);

  TRACE_EVENT (TSn;);
}

/****************************************************************************/
/*                                                                          */
/* Name         TxFSMEvent                                                  */
/*                                                                          */
/* Purpose      Interprets inputs from the Request handlers and from the    */
/*              ISR and calls the relevant action routines.                 */
/*                                                                          */
/* Params    IN pDX, and FSM input                                          */
/*                                                                          */
/*          OUT Action routine called and new state set.                    */
/*                                                                          */
/****************************************************************************/

void TxFSMEvent (PDX pDX, int Input)
{
  TXFSMENTRY *e      = &TxFSM [Input] [pDX->TxFSMCurState];

  /***************************************************************************/
  /* The FSM consists of a 'state' which is actually the address of the      */
  /* current FSM 'column'.  The 'input' is an offset down the column at      */
  /* which is located an action routine address and a new state.             */
  /***************************************************************************/

  TRACE_EVENT (<TFE);

  ASSERT (pDX->TxFSMCurState < TXFSMSTATECOUNT);
  ASSERT (Input              < TXFSMINPUTCOUNT);
  TRACE_EVENTNAME(TxFSMStateNames[pDX->TxFSMCurState]);
  TRACE_EVENTNAME(TxFSMInputNames[Input]);

  /***************************************************************************/
  /* Reset the state to the new state specified in the FSM table             */
  /* (Important to do this before the actions because some of them then do   */
  /* recursive calls to FSMEvent                                             */
  /***************************************************************************/

  pDX->TxFSMCurState = e->NewState;
  ASSERT (e->NewState        <  TXFSMSTATECOUNT);
  TRACE_EVENTNAME(TxFSMStateNames[pDX->TxFSMCurState]);

  (*(e->pTxActionRoutine)) (pDX);

  TRACE_EVENT (TFE>);

}

/**PROC+**********************************************************************/
/*                                                                           */
/* Name         Write8273Cmd (                                               */
/*                            PDX    pDX                                     */
/*                            UCHAR *Cmd;                                    */
/*                           );                                              */
/*                                                                           */
/* Purpose      Send out a command array (in <cmd>, <length>, <params> format*/
/*                                                                           */
/* Params   IN  pDX - the device extension                                   */
/*          IN  Cmd - ptr to first char of command                           */
/*                                                                           */
/* Return Value If command is READ 8273 PORT A/B return value read, otherwise*/
/*              return value is meaningless.                                 */
/*                                                                           */
/* Operation    None                                                         */
/*                                                                           */
/* Notes        1. <length> may be 0                                         */
/*              2. Write8273Cmd (OS/2 DD precursor of this routine used to   */
/*                 return carry on error.  We don't bother as this was only  */
/*                 check in one place.                                       */
/*                                                                           */
/**PROC-**********************************************************************/

UCHAR Write8273Cmd (PDX pDX, UCHAR *Cmd)
{
  int   ParameterCount;
  UCHAR Result = 0;

  TRACE_EVENT (<Cmd);
  TRACE_CHAR (*Cmd);                    /* trace command and first 3 params  */
  TRACE_CHAR (*(Cmd+2));
  TRACE_CHAR (*(Cmd+3));
  TRACE_CHAR (*(Cmd+4));

  WAITUNTIL(pDX,AS_CMBSY|AS_CMBFF,EQ,0);/* wait for busy and full to go off  */
  if (pDX->HardwareError)               /* stop if previous error or error   */
    return(0);                          /* this time                         */

  IO_OUT (pDX->ADAPTERBASE+AR_8273S,    /* status reg also command register  */
          *Cmd++);                      /* command is first byte             */

  if (*(Cmd-1) EQ 0x22 || *(Cmd-1) EQ 0x23)
  {                                     /* what we sent out was Read Port A/B*/
    WAITUNTIL(pDX,AS_CRBFF,EQ,AS_CRBFF);/* wait for result buff full to go on*/
    Result = IO_IN (pDX->ADAPTERBASE+AR_8273P);
  }
  ParameterCount = *(Cmd++);            /* parameter count is next           */
  while (ParameterCount--)
  {
    WAITUNTIL (pDX,                     /* wait for busy to go on, but       */
          (AS_CMBSY|AS_CMBFF|AS_CPBFF), /* command and param buffs are clear */
               EQ, AS_CMBSY);

    IO_OUT (pDX->ADAPTERBASE+AR_8273P, /* write params to parameter port     */
            (UCHAR)*(Cmd++));
  }

  TRACE_EVENT (Cmd>);
  return(Result);
}

/*****************************************************************************/
/*                                                                           */
/* Name         LogDriverError                                               */
/*                                                                           */
/* Purpose      Allocates an error log entry, copies over the given info     */
/*              and writes it to the error log file.                         */
/*                                                                           */
/* Params    IN pDeviceObject                                                */
/*           IN FinalStatus       : NT Status returned to calling service    */
/*           IN UniqueErrorValue  : bottom word is the resource message id   */
/*           IN MajorFunctionCode : for the current Irp                      */
/*           IN IoControlCode     : Ioctl code                               */
/*                                                                           */
/*****************************************************************************/

VOID LogDriverError (
                     PDEVICE_OBJECT pDeviceObject,
                     NTSTATUS       FinalStatus,
                     ULONG          UniqueErrorValue,
                     UCHAR          MajorFunctionCode,
                     ULONG          IoControlCode
                    )
{
  PIO_ERROR_LOG_PACKET ErrorLogEntry;

  TRACE_EVENT (<LDE);

  ErrorLogEntry = IoAllocateErrorLogEntry(
                      pDeviceObject,
                      (UCHAR)sizeof(IO_ERROR_LOG_PACKET)
                      );

  if (ErrorLogEntry NE NULL)
  {
    ErrorLogEntry->ErrorCode         = UniqueErrorValue;

    ErrorLogEntry->FinalStatus       = FinalStatus;
    ErrorLogEntry->UniqueErrorValue  = UniqueErrorValue;
    ErrorLogEntry->MajorFunctionCode = MajorFunctionCode;
    ErrorLogEntry->IoControlCode     = IoControlCode;

    ErrorLogEntry->SequenceNumber = pDeviceObject->ReferenceCount;

    IoWriteErrorLogEntry(ErrorLogEntry);
  }

  TRACE_EVENT (LDE>);
}


/*****************************************************************************/
/* Notes.                                                                    */
/*                                                                           */
/* 1. The OS/2 device driver registers the MPA interrupt 3 as exclusive but  */
/* then shares it internally.  We can't do this unfortunately on NT because  */
/* IoConnectInterrupt requires the device object as a parameter.  We will    */
/* therefore have to register MPAA interrupts as shared.  The problem with   */
/* the shared interrupts on OS/2 was that phantom interrupts could be        */
/* generated (and, because we didn't have to service any cards, the          */
/* interrupt request stayed asserted).  As NT is well-designed and tested,   */
/* this won't occur so we can use shared interrupts.                         */
/*                                                                           */
/* 2. Should we action receive results immediately?  As background, we do    */
/* action all events immediately except for Transmitter results which are    */
/* passed to the DPC routine.  NT religious dogma says we should not action  */
/* the rx result immediately either, but pass them to the DPC routine.  The  */
/* danger in doing this is that we will miss the start of the next frame.    */
/* This isn't a problem in DMA, because we're just letting it rip.  But in   */
/* PIO mode, we can be some while getting to restart the receiver if we drop */
/* down to DPC level.  So... action al rx interrupts immediately.            */
/*                                                                           */
/*                                                                           */
/*****************************************************************************/


//on termination, unlock any locked memory and free the MDLs (e.g.
//InterfaceRecordMdl)
//
//need to initialise InterfaceRecordMdl = NULL
