/**************************************************************************
 *
 * SOURCE FILE NAME = vkbdbuf.c
 *
 * DESCRIPTIVE NAME = Virtual Keyboard Device Driver (VKBD) Buffer management
 *
 * Copyright : COPYRIGHT IBM CORPORATION, 1991, 1992
 *             Copyright Microsoft Corporation, 1990
 *             LICENSED MATERIAL - PROGRAM PROPERTY OF IBM
 *             REFER TO COPYRIGHT INSTRUCTION FORM#G120-2083
 *             RESTRICTED MATERIALS OF IBM
 *             IBM CONFIDENTIAL
 *
 * VERSION = V2.0
 *
 * DATE      02/13/92
 *
 * DESCRIPTION
 *
 *          This module contains all the VKBD's scan buffer and
 *          ROM BIOS input buffer management routines.
 *
 * FUNCTIONS  VKNextScanPtr
 *            VKPrevScanPtr
 *            VKAddScan
 *            VKAddDebugEntry
 *            VKPeekScan
 *            VKRemoveScan
 *            VKClearScan
 *            VKTransferScanHook
 *            VKTransferScan
 *            VKSimulateInt
 *            VKAddKey
 *            VKNextKeyPtr
 *            VKClearKeys
 *            VKAddExtraKey
 *            VKNextExtraKeyPtr
 *            VKTransferExtraKey
 *
 * ENTRY POINTS:
 *
 * DEPENDENCIES:
 *
 * NOTES
 *
 * STRUCTURES
 *
 * EXTERNAL REFERENCES
 *
 * EXTERNAL FUNCTIONS
 *
 * CHANGE ACTIVITY =
 *  DATE      FLAG        APAR   CHANGE DESCRIPTION
 *  --------  ----------  -----  --------------------------------------
 *  mm/dd/yy  @Vr.mpppxx  xxxxx  xxxxxxx
 *  11/25/88                     MTS Created.
 *  03/20/95 116009 Change Team  Ensure vdm is alive in VKTransferScanHook
 ****************************************************************************/

#define  INCL_MI
#include <mvdm.h>
#define  INCL_VIDEODEVREQ
#include <vvd.h>
#include "vkbdp.h"

#ifdef   VDDSTRICT
MODNAME = __FILE__;
#endif

/*
**    External References
*/

/*
**      Global Data
*/
extern HIRQ hirq;                      /* handle for keyboard IRQ interrupt */
extern FPFNPDD fpfnPKVDDProc;          /* address of PDD entry point        */
extern PCPENTRY pRXDefault;

/*
**      Instance Data
*/
extern FLAGS flVDM;                    /* per-VDM flags                       */
extern BYTE virtCommand;               /* virtual keyboard command register   */
extern BYTE virtKbdEnable;             /* virtual keyboard enable state       */
extern BYTE virtStatus;                /* current virtual Status byte         */
extern BYTE virtInput;                 /* current virtual input port byte     */
extern BYTE virtBuff;                  /* current virtual Buffer byte         */
extern BYTE virtKbdLastScan;           /* virtual keyboard last scan          */
                                       /* (for RESEND)                        */
extern KEYPKT akpScans[];              /* ring buffer for scan codes          */
extern PKEYPKT pkpScanHead;      /* pointer to head of scan code ring buffer  */
extern PKEYPKT pkpScanTail;      /* pointer to tail of scan code ring buffer  */
extern HHOOK hhookTransferScanHook;    /* hook handle for VKTransferScanHook  */
extern HHOOK hhookPasteTimeout;        /* hook handle for VKPasteTimeout      */
extern HVDD hvddVideo;                 /* handle to video VDD                 */
extern KEYPKT kpCur;                   /* last key packet removed             */
extern UCHAR achExtraKeys[MAX_EXTRAKEYS *2];
extern ULONG iExtraKeyHead;
extern ULONG iExtraKeyTail;
#pragma  BEGIN_GLOBAL_CODE

/****************************************************************************
 *
 * FUNCTION NAME = VKNextScanPtr
 *
 * DESCRIPTION   =
 *
 *      Returns the next virtual keyboard scan code buffer position.
 *
 *      Note that this routine can be called at any task time or interrupt
 *      time for the VDM (see CONTEXT below), but it doesn't need to
 *      have the VDM handle.  This is because the instance offset of
 *      akpScans is the same for each VDM and this routine is a general
 *      purpose routine to increment the pointer and compare it with the
 *      end offset of the buffer end.  Therefore, everything is relative
 *      to the base of instance data.  Make sure that physical interrupts
 *      are disabled, to avoid race conditions.
 *
 * ENTRY
 *     interrupts disabled
 *     pScan   = pointer to some scan code buffer position
 *
 * EXIT
 *     Pointer to next scan code buffer position
 *
 * CONTEXT
 *     Task-time
 *     Interrupt
 *
 * PSEUDOCODE
 *     advance pScan by 1
 *     if pScan equals endScans
 *       set pScan equal to akpScans       // wrap-around
 *     endif
 *
 ****************************************************************************/

PKEYPKT PRIVENTRY VKNextScanPtr(PKEYPKT pScan)
{
  return ((++pScan == &akpScans[MAX_SCANS])?(PKEYPKT)&akpScans[0]:pScan);
}                                      /* VKNextScanPtr                      */

/****************************************************************************
 *
 * FUNCTION NAME = VKPrevScanPtr
 *
 * DESCRIPTION   =
 *
 *      Returns the previous virtual keyboard scan code buffer position.
 *
 *      Note that this routine can be called at any task time or interrupt
 *      time for the VDM (see CONTEXT below), but it doesn't need to
 *      have the VDM handle.  This is because the instance offset of
 *      akpScans is the same for each VDM and this routine is a general
 *      purpose routine to increment the pointer and compare it with the
 *      end offset of the buffer end.  Therefore, everything is relative
 *      to the base of instance data.  Make sure that physical interrupts
 *      are disabled, to avoid race conditions.
 *
 * ENTRY
 *     interrupts disabled
 *     pScan   = pointer to some scan code buffer position
 *
 * EXIT
 *     Pointer to previous scan code buffer position
 *
 * CONTEXT
 *     Task-time
 *     Interrupt
 *
 * PSEUDOCODE
 *     if pScan equals akpScans
 *       set pScan equal to endScans       // wrap-around
 *     else
 *         backup pScan by 1
 *     endif
 *
 ****************************************************************************/

PKEYPKT PRIVENTRY VKPrevScanPtr(PKEYPKT pScan)
{
  return ((pScan == &akpScans[0])?(PKEYPKT)&akpScans[MAX_SCANS-1]:--pScan);
}                                      /* VKPrevScanPtr                      */

/****************************************************************************
 *
 * FUNCTION NAME = VKAddScan
 *
 * DESCRIPTION   =
 *
 *      Adds a scan code to the scan code buffer (akpScans).  If the
 *      buffer is full, beep.  Otherwise, add the scan code.  Then
 *      check to see if an interrupt should be simulated now.
 *
 *      The ADDSCAN_RESPONSE option is used with scan codes
 *      that should be added to the head of the buffer.  This
 *      is only in case scan codes are clogging up in our virtual
 *      hardware keyboard buffer, and the VDM might expect an immediate
 *      acknowledgement to some recent command.
 *
 *      Note that this routine can be called in two different contexts
 *      for the same VDM (see CONTEXT below), hence the need to pass
 *      the VDM handle and make sure that physical interrupts are disabled,
 *      to avoid race conditions.
 *
 * ENTRY
 *     hvdm       = VDM handle
 *     pkp        = pointer to keypkt to add
 *     flType     = one or more of the ADDSCAN_* flags
 * EXIT
 *     TRUE if SUCCESS, FALSE if overflow
 *
 * CONTEXT
 *     VDM Task
 *     Interrupt
 *
 * PSEUDOCODE
 *     disable interrupts
 *     if not ADDSCAN_RESPONSE
 *       if VKNextScanPtr(hvdm,hvdm->pkpScanTail) equals hvdm->pkpScanHead
 *         enable interrupts
 *         call VKBeep                           // buffer full
 *       else
 *         store scan code at hvdm->pkpScanTail
 *         set hvdm->pkpScanTail to next scan ptr
 *         enable interrupts
 *       endif
 *     else
 *       if VKPrevScanPtr(hvdm,hvdm->pkpScanHead) equals hvdm->pkpScanTail
 *         enable interrupts
 *         call VKBeep                           // buffer full
 *       else
 *         set hvdm->pkpScanHead to previous scan ptr
 *         store scan code at hvdm->pkpScanHead
 *         enable interrupts
 *       endif
 *     endif
 *     if at task time
 *       call VKTransferScan to see if an interrupt should be simulated
 *     else if at interrupt time and no pending context hook
 *       call VDHArmContextHook to execute VKTransferScan at task time
 *       set VDM_XFERPENDING
 *     endif
 *
 ****************************************************************************/

BOOL PRIVENTRY VKAddScan(HVDM hvdm,PKEYPKT pkp,ULONG flType)
{
  BOOL fSuccess = FALSE;
  PKEYPKT pScan,*ppScan;
  ULONG ulFlags;
  ulFlags = SAVEFLAGS();
  DISABLE();

  if (!(flType&ADDSCAN_RESPONSE))
  {
    if (!(flType&ADDSCAN_PASTEINPUT) && (REFHVDM(hvdm, BOOL, flVDM)&VDM_PASTING
       ))
    {
      if (pkp->kp_Scan == pRXDefault->cp_RXTable[ASC_ESC].rx_chScan)
        REFHVDM(hvdm, BOOL, flVDM) |= VDM_ENDPASTING;
      RESTOREFLAGS(ulFlags);
      return  TRUE;
    }
    ppScan = pVDM(hvdm, PPKEYPKT, &pkpScanTail);    /* ppScan -> pkpScanTail  */
    pScan = VKNextScanPtr(*ppScan);                 /* pScan -> next location */
    if (pScan != REFHVDM(hvdm, PKEYPKT, pkpScanHead))
    {
      *pVDM(hvdm, PKEYPKT, *ppScan) = *pkp;         /* store the scan code    */
      *ppScan = pScan;                 /* pkpScanTail points to next position */
      fSuccess++;
    }
  }
  else
  {                              /* insert scan code to the head of the queue */
    ppScan = pVDM(hvdm, PPKEYPKT, &pkpScanHead);
    pScan = VKPrevScanPtr(*ppScan);    /* pScan -> prev. loc. */
    if (pScan != REFHVDM(hvdm, PKEYPKT, pkpScanTail))
    {
      *ppScan = pScan;         /* pkpScanHead points to the previous location */
      *pVDM(hvdm, PKEYPKT, pScan) = *pkp;        /* store the scan code       */
      fSuccess++;
    }
  }
  if (!(REFHVDM(hvdm, BOOL, flVDM)&VDM_XFERPENDING))
  {
    if (!(flType&ADDSCAN_INTERRUPT))
    {
      RESTOREFLAGS(ulFlags);
      VKTransferScan(hvdm, (flType&ADDSCAN_PASTEINPUT));
    }
    else
    {
      REFHVDM(hvdm, BOOL, flVDM) |= VDM_XFERPENDING;
      RESTOREFLAGS(ulFlags);
      *((HVDM *)VDHQueryHookData(REFHVDM(hvdm, HHOOK, hhookTransferScanHook)))
         = hvdm;
      VDHArmContextHook(REFHVDM(hvdm, HHOOK, hhookTransferScanHook),
         GLOBAL_CONTEXT_HOOK);
    }
  }

#ifndef  STUB_VPIC
  if (!fSuccess && !(REFHVDM(hvdm, FLAGS, flVDM)&VDM_WINDOWED) &&
     !(pkp->kp_Scan&SCAN_BREAKMASK))   /* only beeps on makes */
    VKBeep();
#endif
  return  fSuccess;
}                                      /* VKAddScan                          */

#ifdef   MYDEBUG

extern INT iNextDebugEntry;
extern DEBUGENTRY adeDebug[MAX_DEBUG_ENTRIES];

/****************************************************************************
 *
 * FUNCTION NAME = VKAddDebugEntry
 *
 * DESCRIPTION   =
 *
 *
 * INPUT         = (HVDM hvdm,UCHAR op,UCHAR value)
 *
 * OUTPUT        = NONE
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/


VOID PRIVENTRY VKAddDebugEntry(HVDM hvdm,UCHAR op,UCHAR value)
{
  DISABLE();
  if (REFHVDM(hvdm, INT, iNextDebugEntry) >= MAX_DEBUG_ENTRIES)
    INT3();
  else
  {
    REFHVDM(hvdm, DEBUGENTRY, adeDebug[iNextDebugEntry]).de_op = op;
    REFHVDM(hvdm, DEBUGENTRY, adeDebug[iNextDebugEntry]).de_value = value;
    REFHVDM(hvdm, INT, iNextDebugEntry)++;
  }
  ENABLE();
}

#endif

/****************************************************************************
 *
 * FUNCTION NAME = VKPeekScan
 *
 * DESCRIPTION   =
 *
 *      Classifies the next scan code to be retrieved from the virtual
 *      keyboard buffer (akpScans), according to what action should be
 *      taken as far as simulating an interrupt for it.  Note that it is
 *      NOT acceptable to always wait for an IRET before simulating the
 *      next interrupt;  in particular, a VDM's Int 09 handler may expect
 *      to receive additional ACK-type interrupts from the keyboard if
 *      it is reprogramming the keyboard WHILE PROCESSING certain other
 *      keyboard interrupts.
 *
 *      Note that this routine can be called in two different contexts
 *      for the same VDM (see CONTEXT below), hence the need to pass
 *      the VDM handle and make sure that physical interrupts are disabled,
 *      to avoid race conditions.
 *
 * ENTRY
 *     interrupts disabled
 *     hvdm = VDM handle
 *
 * EXIT
 *     PEEKSCAN_NONE     if VDM has no more scan codes
 *     PEEKSCAN_WAITEOI  if next scan code should be simulated at EOI
 *     PEEKSCAN_WAITIRET if next scan code should be simulated at IRET
 *     PEEKSCAN_PAUSE    no scan code available (pasting breather)
 *
 * CONTEXT
 *     Task-time
 *
 * PSEUDOCODE
 *     if hvdm->pkpScanHead not equal to hvdm->pkpScanTail
 *       if packet at hvdm->pkpScanHead has FKEYPKT_WAITEOI bit set
 *         return PEEKSCAN_WAITEOI
 *       else
 *         return PEEKSCAN_WAITIRET
 *       endif
 *     else
 *       return PEEKSCAN_NONE
 *     endif
 *
 * RETURN-NORMAL = NONE
 *
 * RETURN-ERROR  = NONE
 *
 ****************************************************************************/

RETCODE PRIVENTRY VKPeekScan(HVDM hvdm)
{
  RETCODE rc;
  PKEYPKT pScan;
  while (TRUE)
  {
    rc = PEEKSCAN_NONE;

    /*
    ** While even one key is buffered, we should pause pasting
    */

    if ((REFHVDM(hvdm, BOOL, flVDM)&VDM_PASTING) && (pVDMBase(hvdm)->rb_npKTail
       != pVDMBase(hvdm)->rb_npKHead))
    {
      REFHVDM(hvdm, BOOL, flVDM) |= VDM_PASTEPAUSE;
      if (!(REFHVDM(hvdm, BOOL, flVDM)&VDM_TIMERARMED|VDM_PASTEDONE))
      {
        REFHVDM(hvdm, BOOL, flVDM) |= VDM_TIMERARMED;
        VDHArmTimerHook(REFHVDM(hvdm, HHOOK, hhookPasteTimeout), 500, 0);
      }
      rc = PEEKSCAN_PAUSE;
    }
    else
    {
      pScan = REFHVDM(hvdm, PKEYPKT, pkpScanHead);
      if (pScan != REFHVDM(hvdm, PKEYPKT, pkpScanTail))
        if (pVDM(hvdm, PKEYPKT, pScan)->kp_fsKey&FKEYPKT_WAITEOI)
          rc = PEEKSCAN_WAITEOI;
        else
          rc = PEEKSCAN_WAITIRET;
    }
    if (rc == PEEKSCAN_NONE && REFHVDM(hvdm, BOOL, flVDM)&VDM_PASTEDONE)

      /*
      ** Continue looping, because scan codes
      ** may have been added to the buffer by vkSysEndPaste
      */

      vkSysEndPaste(hvdm);
    else
      break;
  }
  return  rc;
}                                      /* VKPeekScan                         */

/****************************************************************************
 *
 * FUNCTION NAME = VKRemoveScan
 *
 * DESCRIPTION   =
 *
 *      Removes a scan code from the scan code buffer (akpScans).  If the
 *      buffer is empty, an error is returned.
 *
 *      Note that this routine can be called in two different contexts
 *      for the same VDM (see CONTEXT below), hence the need to pass
 *      the VDM handle and make sure that physical interrupts are disabled,
 *      to avoid race conditions.
 *
 * ENTRY
 *     hvdm = VDM handle
 *     fPaste = TRUE if called via paste path, FALSE if not
 *
 * EXIT
 *     SUCCESS
 *         returns scan code
 *     FAILURE
 *         returns 0 (buffer empty)
 *
 * CONTEXT
 *     Task-time
 *
 * PSEUDOCODE
 *     disable interrupts
 *     if hvdm->pkpScanHead equals hvdm->pkpScanTail
 *       enable interrupts
 *       return ZERO               // buffer empty
 *     else
 *       get scan code at hvdm->pkpScanHead
 *       set hvdm->pkpScanHead to VKNextScanPtr(hvdm,hvdm->pkpScanHead)
 *       enable interrupts
 *       return scan code
 *     endif
 *
 ****************************************************************************/

BYTE PRIVENTRY VKRemoveScan(HVDM hvdm,BOOL fPaste)
{
  BYTE chScan;
  PULONG piHead;
  LONG nFreeSpace;
  register PKEYPKT *ppScan;
  ULONG ulFlags;

  if (REFHVDM(hvdm, BOOL, flVDM)&VDM_ENDPASTING)
    vkSysEndPaste(hvdm);
  ulFlags = SAVEFLAGS();

  DISABLE();
  ppScan = pVDM(hvdm, PPKEYPKT, &pkpScanHead); /* ppScan -> pkpScanHead */

  if (*ppScan == REFHVDM(hvdm, PKEYPKT, pkpScanTail))
    chScan = 0;
  else
  {                                    /* get first scan code */
    REFHVDM(hvdm, KEYPKT, kpCur) = *pVDM(hvdm, PKEYPKT, *ppScan);
    chScan = REFHVDM(hvdm, UCHAR, kpCur.kp_Scan);
    *ppScan = VKNextScanPtr(*ppScan);  /* advance pkpScanHead */
  }
  nFreeSpace = 0;

  if (REFHVDM(hvdm, BOOL, flVDM)&VDM_PASTING)
  {
    nFreeSpace = *ppScan-REFHVDM(hvdm, PKEYPKT, pkpScanTail)-1;
    if (nFreeSpace < 0)
      nFreeSpace += MAX_SCANS;
  }
  RESTOREFLAGS(ulFlags);

  if (!fPaste)
    if (nFreeSpace == SCANBUF_THRESHOLD || nFreeSpace == MAX_SCANS-1)
      VDHRequestVDD(hvddVideo, hvdm, VVDDEVREQ_POSTPASTE, (PVOID)TRUE, NULL);

  return  chScan;
}                                      /* VKRemoveScan                       */

/****************************************************************************
 *
 * FUNCTION NAME = VKClearScan
 *
 * DESCRIPTION   =
 *
 *      Flushes the virtual keyboard scan code buffer.  This is generally
 *      done only as part of the virtual keyboard emulation (ie, certain
 *      commands have a side-effect of clearing all scan codes buffered in
 *      the keyboard).
 *
 * ENTRY
 *     hvdm
 *
 * EXIT
 *     None
 *
 * CONTEXT
 *     Any
 *
 * PSEUDOCODE
 *     reset both pkpScanHead and pkpScanTail
 *
 ****************************************************************************/

VOID PRIVENTRY VKClearScan(HVDM hvdm)
{
  REFHVDM(hvdm, PKEYPKT, pkpScanHead) = REFHVDM(hvdm, PKEYPKT, pkpScanTail) =
     &akpScans[0];
}                                      /* VKClearScan                        */

#pragma  END_GLOBAL_CODE
#pragma  BEGIN_SWAP_CODE

/****************************************************************************
 *
 * FUNCTION NAME = VKTransferScanHook
 *
 * DESCRIPTION   =
 *
 * ENTRY
 *     phvdm = pointer to VDM handle
 *     pcrf = pointer to client register frame (not used)
 *
 * EXIT
 *     None
 *
 * CONTEXT
 *     Task-time (VDM task or global task)
 *
 * PSEUDOCODE
 *     call VKTransferScan
 *
 ****************************************************************************/


VOID HOOKENTRY VKTransferScanHook(PHVDM phvdm,PCRF pcrf)
{
  /* 116009 - Moved "if" condition before "REFHVDM" statement */
  if (*phvdm)                          /* if vdm still alive */
  {
    REFHVDM(*phvdm, BOOL, flVDM) &= ~VDM_XFERPENDING;
    VKTransferScan(*phvdm, FALSE);
  }
}

/****************************************************************************
 *
 * FUNCTION NAME = VKTransferScan
 *
 * DESCRIPTION   =
 *
 *      Determines if data is available in the scan code buffer (akpScans),
 *      transfers it to the virtual keyboard controller's buffer, and
 *      then checks to see if an interrupt should be simulated at this time.
 *
 *      This should be called whenever the potential exists for updating
 *      virtBuff (and possibly simulating an interrupt).  Callers are:
 *
 *          VKAddScan   (at interrupt-time by the PDD via VKScanProc)
 *          VKAddScan   (at task-time by the virtual keyboard I/O routines)
 *          VKEOIProc   (at VDM task-time)
 *          VKIRETProc  (at VDM task-time)
 *          VKWriteBuff (at VDM task-time)
 *
 *      We don't need to call in when virtKbdEnable changes from clear to
 *      set, because we always call VKAddScan whenever that happens anyway.
 *
 *      One important race is avoided here by skipping the transfer if
 *      STATUS_BUFFERFULL is already/still set.  Without that, two calls
 *      (an interrupt-time and a task-time) might both update virtBuff before
 *      the EOI/IRET-pending flags get set (and in fact, they might NEVER
 *      get set if the VDM has cleared COMMAND_INTENABLE and is simply
 *      polling STATUS_BUFFERFULL and virtBuff to receive keyboard data).
 *
 * ENTRY
 *     phvdm = pointer to VDM handle
 *     fPaste = TRUE if called via paste path, FALSE if not
 *
 * EXIT
 *     None
 *
 * CONTEXT
 *     Task-time (VDM task or global task)
 *
 * PSEUDOCODE
 *     call VKPeekScan(hvdm)
 *     if PEEKSCAN_NONE
 *         or
 *        PEEKSCAN_WAITEOI and VDM_EOIPENDING
 *         or
 *        PEEKSCAN_WAITIRET and VDM_IRETPENDING
 *         return
 *     endif
 *
 *     if hvdm->virtKbdEnable and
 *        COMMAND_DISABLEKBD clear in virtCommand and
 *        STATUS_BUFFERFULL clear in virtStatus
 *         set STATUS_BUFFERFULL and STATUS_NOTINHIBITED in virtStatus
 *         call VKRemoveScan(hvdm) to get the next scan code
 *         put scan code in virtBuff and virtKbdLastScan (in case of RESENDs)
 *         update inhibit states
 *         call VKSimulateInt(hvdm)
 *
 ****************************************************************************/

VOID HOOKENTRY VKTransferScan(HVDM hvdm,BOOL fPaste)
{
  RETCODE rc;
  KINH kinh;
  VDHWakeIdle(hvdm);                   /* wakeup VDM, if idle */

  if (!(REFHVDM(hvdm, BYTE, virtStatus)&STATUS_BUFFERFULL))
  {
    if ((rc = VKPeekScan(hvdm)) <= PEEKSCAN_PAUSE)
      return ;
    if (!(pVDMBase(hvdm)->rb_fbKFlag1&BIOSKFLAG1_HOLDSTATE) && (((rc ==
       PEEKSCAN_WAITEOI) && (REFHVDM(hvdm, BOOL, flVDM)&VDM_EOIPENDING)) ||
       ((rc == PEEKSCAN_WAITIRET) && (REFHVDM(hvdm, BOOL, flVDM)
       &VDM_IRETPENDING))))
    {
      return ;
    }
    if ((rc == PEEKSCAN_WAITEOI) || REFHVDM(hvdm, BYTE, virtKbdEnable) &&
       !(REFHVDM(hvdm, BYTE, virtCommand)&COMMAND_DISABLEKBD))
    {
      REFHVDM(hvdm, BYTE, virtStatus) |= STATUS_BUFFERFULL;
      REFHVDM(hvdm, BYTE, virtBuff) = REFHVDM(hvdm, BYTE, virtKbdLastScan) =
         VKRemoveScan(hvdm, fPaste);
/*
** @IBM Minor problem on 20e's and compatibles:  the QueryInhibit
** @IBM request generates an interrupt, and apparently we don't field
** @IBM it correctly when the mouse is interrupting as well.
** @IBM NEEDS TO BE INVESTIGATED! -JTP
** @IBM
** @IBM #ifdef   BUGBUG
** @IBM
** @IBM
** @IBM    ** Under normal circumstances, update the QueryInhibit state
** @IBM    ** for non-pasted data only;  for pasting, it's unnecessary and
** @IBM    ** just slows pasting down
** @IBM
** @IBM
** @IBM    if (!(REFHVDM(hvdm, FLAGS, flVDM)&VDM_PASTING))
** @IBM    {
** @IBM      kinh.kinh_len = sizeof(KINH);
** @IBM      AssertRC((*fpfnPKVDDProc)(PKBDCMD_QUERYINHIBIT, F16PNULL,
** @IBM                 F16PFROMSSP(&kinh)));
** @IBM      if (kinh.kinh_fInhibited)
** @IBM        REFHVDM(hvdm, BYTE, virtStatus) &= ~STATUS_NOTINHIBITED;
** @IBM      else
** @IBM        REFHVDM(hvdm, BYTE, virtStatus) |= STATUS_NOTINHIBITED;
** @IBM    }
** @IBM #endif
*/

      VKSimulateInt(hvdm);
    }
  }
}                                      /* VKTransferScan                     */

/****************************************************************************
 *
 * FUNCTION NAME = VKSimulateInt
 *
 * DESCRIPTION   =
 *
 *      Determines if all the criteria are satisfied for simulating a
 *      keyboard interrupt to the specified VDM.  This is called
 *      whenever a new scan code is transferred to the virtual controller's
 *      output buffer (ie, VKTransferScan).
 *
 *      Note that when VKTransferScan calls this, we already know that
 *      STATUS_BUFFERFULL is set;  however, in the interests of completeness
 *      and in case any other code wants to call this function, the full
 *      interrupt-criteria is checked.
 *
 * ENTRY
 *     hvdm = VDM handle
 *
 * EXIT
 *     None
 *
 * CONTEXT
 *     Task-time
 *
 * PSEUDOCODE
 *     if STATUS_BUFFERFULL set in hvdm->virtStatus and
 *        COMMAND_INTENABLE set in hvdm->virtCommand
 *         call VDHSetVIRR(hvdm, hirq) to assert virtual keyboard IRQ
 *         set VDM_EOIPENDING and VDM_IRETPENDING
 *
 ****************************************************************************/

VOID PRIVENTRY VKSimulateInt(HVDM hvdm)
{
  if ((REFHVDM(hvdm, BYTE, virtStatus)&STATUS_BUFFERFULL) && (REFHVDM(hvdm,
     BYTE, virtCommand)&COMMAND_INTENABLE))
  {

#ifndef  STUB_VPIC
    VDHSetVIRR(hvdm, hirq);
#endif
    REFHVDM(hvdm, BOOL, flVDM) |= VDM_EOIPENDING|VDM_IRETPENDING;
  }
}                                      /* VKSimulateInt                      */

/****************************************************************************
 *
 * FUNCTION NAME = VKAddKey
 *
 * DESCRIPTION   =
 *
 *      Adds an entry to the ROM BIOS input buffer.  This is currently
 *      only done by normal-key processing, pass-through accent-key
 *      processing, and the Ctrl-Break logic.
 *
 * ENTRY
 *     hvdm = VDM handle
 *     ch   = an ASCII character code (0 if none)
 *     scan = scan code, which may or may not be a "true" scan
 *            code (ie, the translation logic may have returned
 *            an "extended" scan code that apps use to distinguish
 *            the various shifted flavors of certain keys)
 *
 * EXIT
 *     SUCCESS
 *         return TRUE
 *     FAILURE
 *         return FALSE (ROM BIOS input buffer overflow)
 *
 * CONTEXT
 *     Task-time
 *
 * PSEUDOCODE
 *     if VKNextKeyPtr(npKTail) equals npKHead
 *       call VKBeep                       // buffer full
 *       return FALSE
 *     else
 *       store scan code at npKTail
 *       set npKTail to next key ptr
 *       return TRUE
 *          endif
 *
 ****************************************************************************/

BOOL PRIVENTRY VKAddKey(HVDM hvdm,UCHAR ch,UCHAR scan)
{
  register USHORT npNextKey;
  npNextKey = VKNextKeyPtr(hvdm, pVDMBase(hvdm)->rb_npKTail);

  if (npNextKey == pVDMBase(hvdm)->rb_npKHead)
    return  VKAddExtraKey(hvdm, ch, scan);

  REFHVDM(hvdm, USHORT, *(PUSHORT)(pVDMBase(hvdm)->rb_npKTail+ROMDATA_START)) =
     (USHORT)((scan << 8)|ch);
  pVDMBase(hvdm)->rb_npKTail = npNextKey;

  return  TRUE;
}                                      /* VKAddKey                           */

/****************************************************************************
 *
 * FUNCTION NAME = VKNextKeyPtr
 *
 * DESCRIPTION   =
 *
 *      Returns the next ROM BIOS input buffer position.
 *
 * ENTRY
 *     hvdm = VDM handle
 *     pKey = pointer to some input buffer position
 *
 * EXIT
 *     Pointer to next input buffer position
 *
 * CONTEXT
 *     Task-time
 *
 * PSEUDOCODE
 *     advance pKey by 1 word
 *     if pKey equals npKBufEnd
 *       set pKey equal to npKBufStart     // wrap-around
 *     endif
 *
 ****************************************************************************/


USHORT PRIVENTRY VKNextKeyPtr(HVDM hvdm,USHORT pKey)
{
  return (((pKey += 2) == pVDMBase(hvdm)->rb_npKBufEnd)?pVDMBase(hvdm)->
     rb_npKBufStart:pKey);
}                                      /* VKNextKeyPtr                       */

/****************************************************************************
 *
 * FUNCTION NAME = VKClearKeys
 *
 * DESCRIPTION   =
 *
 *      Flushes the ROM BIOS input buffer.  This is currently only done
 *      by the Ctrl-Break logic.
 *
 * ENTRY
 *     hvdm = VDM handle
 *
 * EXIT
 *     None
 *
 * CONTEXT
 *     Task-time
 *
 * PSEUDOCODE
 *     reset both npKHead and npKTail to npKBufStart
 *
 ****************************************************************************/

VOID PRIVENTRY VKClearKeys(HVDM hvdm)
{
  pVDMBase(hvdm)->rb_npKHead = pVDMBase(hvdm)->rb_npKTail = pVDMBase(hvdm)->
     rb_npKBufStart;

  REFHVDM(hvdm, ULONG, iExtraKeyHead) = REFHVDM(hvdm, ULONG,
                                                iExtraKeyTail) = 0;

}                                      /* VKClearKeys                        */

/****************************************************************************
 *
 * FUNCTION NAME = VKAddExtraKey
 *
 *      Adds an entry to the extended key buffer.  This is done
 *      if extended buffering is enabled and VKAddKey is out of space.
 *
 * ENTRY
 *     hvdm = VDM handle
 *     ch   = an ASCII character code (0 if none)
 *     scan = scan code, which may or may not be a "true" scan
 *            code (ie, the translation logic may have returned
 *            an "extended" scan code that apps use to distinguish
 *            the various shifted flavors of certain keys)
 *
 * EXIT
 *     SUCCESS
 *         return TRUE
 *     FAILURE
 *         return FALSE (extended key buffer overflow)
 *
 * CONTEXT
 *     Task-time
 *
 ****************************************************************************/

BOOL PRIVENTRY VKAddExtraKey(HVDM hvdm,UCHAR ch,UCHAR scan)
{
  register ULONG iNextKey,iTail;

  if (!(REFHVDM(hvdm, FLAGS, flVDM)&VDM_EXTRAKEYS) || (iNextKey =
     VKNextExtraKeyPtr(iTail = REFHVDM(hvdm, ULONG, iExtraKeyTail))) == REFHVDM
     (hvdm, ULONG, iExtraKeyHead))
  {

    /*
    ** This is kludge to avoid beeping when we're
    ** called out of context as part of fast pasting
    */

    if (hvdm == CURRENT_VDM)
      VKBeep();

    return  FALSE;
  }
  REFHVDM(hvdm, UCHAR, achExtraKeys[iTail]) = ch;
  REFHVDM(hvdm, UCHAR, achExtraKeys[iTail+1]) = scan;
  REFHVDM(hvdm, ULONG, iExtraKeyTail) = iNextKey;

  return  TRUE;
}                                      /* VKAddExtraKey                      */

/****************************************************************************
 *
 * FUNCTION NAME = VKNextExtraKeyPtr
 *
 * DESCRIPTION   =
 *
 *      Returns the next extended buffer position.
 *
 * ENTRY
 *     iExtraKey = index to some extended buffer position
 *
 * EXIT
 *     Index to next extended buffer position
 *
 * CONTEXT
 *     Task-time
 *
 ****************************************************************************/

ULONG PRIVENTRY VKNextExtraKeyPtr(ULONG iExtraKey)
{
  return (((iExtraKey += 2) == MAX_EXTRAKEYS *2)?0:iExtraKey);
}                                     /* VKNextExtraKeyPtr                  */

/****************************************************************************
 *
 * FUNCTION NAME = VKTransferExtraKey
 *
 * DESCRIPTION   =
 *
 *      If there is space in the ROM BIOS input buffer, and if keys exist
 *      in the extended key buffer, this removes the next extended key and
 *      adds it to the normal input buffer.  This is called from the INT 16h
 *      hook.
 *
 * ENTRY
 *     None
 *
 * EXIT
 *     None
 *
 * CONTEXT
 *     VDM Task-time
 *
 ****************************************************************************/

VOID PRIVENTRY VKTransferExtraKey()
{
  if (VKNextKeyPtr(CURRENT_VDM, VDMBase.rb_npKTail) != VDMBase.rb_npKHead)
  {
    if (iExtraKeyTail != iExtraKeyHead)
    {
      VKAddKey(CURRENT_VDM, achExtraKeys[iExtraKeyHead], achExtraKeys
         [iExtraKeyHead+1]);
      iExtraKeyHead = VKNextExtraKeyPtr(iExtraKeyHead);
    }
  }
}                                      /* VKTransferExtraKey                 */
#pragma  END_SWAP_CODE
