unit Vds;
{ ===================================================================
  DMA Support Library (c) J. Dempster 1997, University of Strathclyde
  V1.0 1/5/97
  V1.1 24/3/98 ... Bug in VDS_ProgramDMAChannel fixed
                   Number of words to transfer now set correctly
                   as NumWords-1
  ===================================================================}
interface

uses winprocs,wintypes;

const
     DATATOBECOPIED = $2 ;
     DISABLEBUFFERALLOCATION = $4 ;
     DISABLEAUTOREMAP = $8 ;
     ALIGN64K = $10 ;
     ALIGN128K = $20 ;

     DMA5_PAGE = $8B ;
     DMA6_PAGE = $89 ;
     DMA7_PAGE = $8A ;
     DMA5_ADDRESS = $C4 ;
     DMA6_ADDRESS = $C8 ;
     DMA7_ADDRESS = $CC ;
     DMA5_COUNT = $C6 ;
     DMA6_COUNT = $CA ;
     DMA7_COUNT = $CE ;
     DMA_FLIPFLOP = $D8 ;
     DMA_MASK = $D4 ;
     DMA_MODE = $D6 ;
     DMA_STATUS = $D0 ;

     CH5_ON = 1 ;
     CH6_ON = 2 ;
     CH7_ON = 3 ;
     CH5_OFF = 5 ;
     CH6_OFF = 6 ;
     CH7_OFF = 7 ;
     CH5_TC = 2 ;
     CH6_TC = 4 ;
     CH7_TC = 8 ;
     CH5_WRITEMODE = $45 ;
     CH5_WRITEMODEA = $55 ;
     CH5_READMODE = $49 ;
     CH5_READMODEA = $59 ;
     CH6_WRITEMODE = $46 ;
     CH6_WRITEMODEA = $56 ;
     CH6_READMODE = $4A ;
     CH6_READMODEA = $5A ;
     CH7_WRITEMODE = $47 ;
     CH7_WRITEMODEA = $57 ;
     CH7_READMODE = $4B ;
     CH7_READMODEA = $5B ;


type
    TDDS = record
         RegionSize : LongInt ;
         Offset : LongInt ;
         Segment : Word ;
         BufferID : Word ;
         PhysicalAddress : LongInt ;
         end ;

    TDMADirection = (WriteToMemory,ReadFromMemory) ;

function VDS_GetVersion( var Version : single ;
                          var DMABufferSize : LongInt ) : Word ;
function VDS_AllocateDMABuffer( var GoodDDS : TDDS ;
                                var GoodHnd : THandle ;
                                BufSize : LongInt ) : Pointer ;
Function VDS_LockDMABuffer( var DDS : TDDS ;
                            Flags : Word ;
                            var BufPointer : Pointer ;
                            BufSize : LongInt ) : Word ;
Function VDS_UnlockDMABuffer( var DDS : TDDS ) : Word ;
Function VDS_RequestDMABuffer( var DDS : TDDS ;
                               Flags : Word ;
                               BufPointer : Pointer ;
                               BufSize : LongInt ) : Word ;
Function VDS_ReleaseDMABuffer( var DDS : TDDS ; Flags : Word ) : Word ;
Function VDS_CopyToDMABuffer( var DDS : TDDS ;
                              BufPointer : Pointer ;
                              BufSize : LongInt ) : Word ;
Function VDS_CopyFromDMABuffer( var DDS : TDDS ;
                              BufPointer : Pointer ;
                              BufSize : LongInt ) : Word ;

function VDS_FreeDMABuffer( var DDS : TDDS ; var Hnd : THandle ) : Word ;
Function VDS_DisableDMATranslation( DMAChannel : Word ) : Word ;
Function VDS_EnableDMATranslation( DMAChannel : Word ) : Word ;
procedure VDS_ProgramDMAChannel( DMAChannel : Integer ;
                                 DMADirection : TDMADirection ;
                                 var DDS : TDDS ;
                                 nBytes : LongInt ;
                                 AutoInitialise : boolean ) ;
procedure VDS_EnableDMAChannel( DMAChannel : Integer ) ;
procedure VDS_DisableDMAChannel( DMAChannel : Integer ) ;
function VDS_GetSegment( Pntr : Pointer ) : Word ;
function VDS_GetOffset( Pntr : Pointer ) : Word ;
function InpB( Port : Word ) : Word ;
function InpW( Port : Word ) : Word ;
procedure OutB( Port,Value : Word ) ;
procedure OutW( Port,Value : Word ) ;
procedure SetBits( var Dest : Word ; Bits : Word ) ;
procedure ClearBits( var Dest : Word ; Bits : Word ) ;

implementation

function VDS_AllocateDMABuffer( var GoodDDS : TDDS ;
                                var GoodHnd : THandle ;
                                BufSize : LongInt ) : Pointer ;
{ ---------------------------------------------------
  Allocate memory from the global heap which is contiguous
  and does not cross a DMA controller page boundary.
  Enter with: BufSize = size of buffer required (bytes)
  Returns :   GoodDDS = VDS DMA memory descriptor
              GoodHnd = Handle of allocated memory buffer
              Result = pointer to memory buffer
  -------------------------------------------------}

Const
     NumTrys = 50 ;
var
   Hnd : Array[0..NumTrys] of THandle ;
   DDS : Array[0..NumTrys] of TDDS ;
   i,n,iGood : Integer ;
   Err : Word ;
   Pntr,GoodPtr : Pointer ;
   Done : boolean ;
begin
     n := 0 ;
     iGood := -1 ;
     repeat
         { Allocate memory from the global heap }
         Hnd[n] := GlobalAlloc( GMEM_FIXED, BufSize ) ;
         if Hnd[n] > 0 then begin
              { Lock and fix the memory }
              GlobalFix( Hnd[n] ) ;
              Pntr := GlobalLock( Hnd[n] ) ;
              Err := VDS_LockDMABuffer( DDS[n],ALIGN128K or DISABLEBUFFERALLOCATION,
                                        Pntr, BufSize ) ;
              { Use this buffer if lock successful }
              if Err = 0 then begin
                 iGood := n ;
                 GoodPtr := Pntr ;
                 end ;
              end ;
         Inc(n) ;
         until (iGood >= 0) or (GetFreeSpace(0) < BufSize) or (n > High(DDS)) ;

    { Free all buffers except the good one }

    for i := 0 to (n-1) do begin
        if (i <> iGood) and (Hnd[i] > 0) then begin
           Err := VDS_UnLockDMABuffer( DDS[i] ) ;
           Done := GlobalUnlock( Hnd[i] ) ;
           GlobalUnfix( Hnd[i] ) ;
           GlobalFree( Hnd[i] ) ;
           end ;
        end ;

    { If allocation was successful ... return pointer to buffer, otherwise NIL }
    if iGood >= 0 then begin
       Result := GoodPtr ;
       GoodHnd := Hnd[iGood] ;
       GoodDDS := DDS[iGood] ;
       end
    else begin
         Result := Nil ;
         GoodHnd := 0 ;
         end ;
    end ;



function VDS_FreeDMABuffer( var DDS : TDDS ; var Hnd : THandle ) : Word ;
{ -----------------------------------------------------------
  Free DMA buffer memory (allocated by VDS_AllocateDMABuffer)
  Enter with : DDS = DMA memory descriptor block
               Hnd = Memory handle
  Return :     Result = Error code (0=OK, $100=invalid handle)
  ------------------------------------------------------------}
var
   Err : Word ;
   Done : boolean ;
begin

     Err := VDS_UnLockDMABuffer( DDS ) ;
     if Hnd > 0 then begin
        Done := GlobalUnlock( Hnd ) ;
        GlobalUnfix( Hnd ) ;
        GlobalFree( Hnd ) ;
        Hnd := 0 ;
        end
     else Err := Err or $100 ;
     end ;


procedure VDS_ProgramDMAChannel( DMAChannel : Integer ;
                                 DMADirection : TDMADirection ;
                                 var DDS : TDDS ;
                                 nBytes : LongInt ;
                                 AutoInitialise : boolean ) ;
{ ----------------------------
  Program selected DMA channel
  ----------------------------}
type
    TDMA = record
         Page : Word ;
         FlipFlop : Word ;
         Address : Word ;
         Count : Word ;
         Mode : Word ;
         Status : Word ;
         Mask : Word ;
         end ;
var
   DMA : TDMA ;
   ReadMode,WriteMode,ReadModeAutoInit,WriteModeAutoInit : Word ;
   EnableDMA,DisableDMA,Err : Word ;
   WordAddress,NumWords,Page,Offset : LongInt ;
begin

     { Set up appropriate port addresses and modes for selected DMA channel }

     case DMAChannel of
          5 : begin
              DMA.Page := DMA5_PAGE ;
              DMA.Mode := DMA_MODE ;
              DMA.FlipFlop := DMA_FLIPFLOP ;
              DMA.Address := DMA5_ADDRESS ;
              DMA.Count := DMA5_COUNT ;
              DMA.Status := DMA_STATUS ;
              DMA.Mask := DMA_MASK ;
              ReadMode := CH5_READMODE ;
              WriteMode := CH5_WRITEMODE ;
              ReadModeAutoInit := CH5_READMODEA ;
              WriteModeAutoInit := CH5_WRITEMODEA ;
              end ;

          6 : begin
              DMA.Page := DMA6_PAGE ;
              DMA.Mode := DMA_MODE ;
              DMA.FlipFlop := DMA_FLIPFLOP ;
              DMA.Address := DMA6_ADDRESS ;
              DMA.Count := DMA6_COUNT ;
              DMA.Status := DMA_STATUS ;
              DMA.Mask := DMA_MASK ;
              ReadMode := CH6_READMODE ;
              WriteMode := CH6_WRITEMODE ;
              ReadModeAutoInit := CH6_READMODEA ;
              WriteModeAutoInit := CH6_WRITEMODEA ;
              end ;

          7 : begin
              DMA.Page := DMA7_PAGE ;
              DMA.Mode := DMA_MODE ;
              DMA.FlipFlop := DMA_FLIPFLOP ;
              DMA.Address := DMA7_ADDRESS ;
              DMA.Count := DMA7_COUNT ;
              DMA.Status := DMA_STATUS ;
              DMA.Mask := DMA_MASK ;
              ReadMode := CH7_READMODE ;
              WriteMode := CH7_WRITEMODE ;
              ReadModeAutoInit := CH7_READMODEA ;
              WriteModeAutoInit := CH7_WRITEMODEA ;
              end ;

          end ;

     { Disable VDS linear->physical address translation }
     Err := VDS_DisableDMATranslation( DMAChannel ) ;

     { Disable channel on DMA controller }
     VDS_DisableDMAChannel( DMAChannel ) ;

     case DMADirection of
          WriteToMemory : begin
             if AutoInitialise then OutB( DMA.Mode,WriteModeAutoInit )
                               else OutB( DMA.Mode,WriteMode ) ;
             end ;
          ReadFromMemory : begin
             if AutoInitialise then OutB( DMA.Mode,ReadModeAutoInit )
                               else OutB( DMA.Mode,ReadMode ) ;
             end ;
          end ;

     { Calculate word address for DMA controller }
     WordAddress := DDS.PhysicalAddress div 2 ;
     Page := WordAddress div $10000 ;
     Offset := WordAddress - Page*$10000 ;
     Page := Page*2 ;

     { Write start address of DMA buffer (page:offset) to DMA controller }
     OutB( DMA.Page, Page ) ;               { Write DMA page }
     OutB( DMA.FlipFlop,1 ) ;               { Reset byte flip-flop }
     OutB( DMA.Address, Offset ) ;          { Address (lo byte) }
     OutB( DMA.Address, Offset div $100 ) ; {Address (hi byte)

     { Write no. of word to be transferred to controller
       (NOTE ... transferred as 16 bit word, NumWord-1 written to register }
     NumWords := (nBytes div 2) - 1 ;
     OutB( DMA.FlipFlop,1 ) ; { Reset byte flip-flop }
     OutB( DMA.Count, NumWords ) ;
     OutB( DMA.Count, NumWords div $100 ) ;

     { Enable channel on DMA controller }
     VDS_EnableDMAChannel( DMAChannel ) ;

     { Restore address translation to VDS }
     Err := VDS_EnableDMATranslation( DMAChannel ) ;

     end ;


procedure VDS_DisableDMAChannel( DMAChannel : Integer ) ;
{ -------------------
  Disable DMA channel
  -------------------}
begin
     case DMAChannel of
          5 : OutB( DMA_MASK, CH5_OFF ) ;
          6 : OutB( DMA_MASK, CH6_OFF ) ;
          7 : OutB( DMA_MASK, CH7_OFF ) ;
          end ;
     end ;


procedure VDS_EnableDMAChannel( DMAChannel : Integer ) ;
{ -------------------
  Enable DMA channel
  -------------------}
begin
     case DMAChannel of
          5 : OutB( DMA_MASK, CH5_ON ) ;
          6 : OutB( DMA_MASK, CH6_ON ) ;
          7 : OutB( DMA_MASK, CH7_ON ) ;
          end ;
     end ;


function VDS_GetVersion( var Version : single ;
                          var DMABufferSize : LongInt ) : Word ;
{ -------------------------------------------------
  Get the VDS version number and Windows DMA buffer size
  Function returns =0 if no errors have occurred
  ------------------------------------------------------}
var
   DMABufferHi,DMABufferLo,Flags : Word ;
   VersionMajor,VersionMinor,Error : Byte ;
label
     NoError ;
begin
     Error := 0 ;
     asm
        mov ah,$81 ;
        mov al,2 ;
        mov dx,0

        int $4b ; { VDS Interrupt }
        mov VersionMajor,ah ;
        mov VersionMinor,al ;
        mov DMABufferHi,SI ;
        mov DMABufferLo,DI ;
        mov Flags,dx ;
        jnc NoError
        mov Error,al ;
NoError:
        end ;
     Version := VersionMajor + VersionMinor ;
     DMABufferSize := DMABufferHi ;
     DMABufferSize := DMABufferSize*$10000 + DMABufferLo ;
     Result := Error ;
     end ;


Function VDS_LockDMABuffer( var DDS : TDDS ;
                            Flags : Word ;
                            var BufPointer : Pointer ;
                            BufSize : LongInt ) : Word ;
{ ---------------------------------------------------------------
  Lock memory pages used for DMA buffer
  i.e. prevent this region of physical memory being paged to disk
  ---------------------------------------------------------------}
var
   Error : Byte ;
   DDSSegment,DDSOffset : Word ;
   Ptr1 : Pointer ;
label
     NoError ;
begin
     Error := 0 ;
     DDS.RegionSize := BufSize ;
     DDS.Segment := VDS_GetSegment( BufPointer ) ;
     DDS.Offset := VDS_GetOffset( BufPointer ) ;
     DDS.BufferID := 0 ;
     DDS.PhysicalAddress := 0 ;
     ptr1 := addr(DDS) ;
     { Get segment/offset of DDS memory block }
     DDSOffset := Ofs(DDS) ;
     DDSSegment := Seg(DDS) ;
     asm
        push ES ;                { ES,DI registers MUST be saved }
        push DI ;
        mov ah,$81 ;
        mov al,3 ;
        mov dx,Flags ;
        mov bx, DDSSegment ;    { Point ES:DI -> DDS block }
        mov ES,bx ;
        mov bx, DDSOffset ;
        mov DI,bx ;
        int $4b ;               { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:
        pop DI ;
        pop ES ;
        end ;
     Result := Error ;
     end ;

Function VDS_UnLockDMABuffer( var DDS : TDDS ) : Word ;
{ ----------------------------------------
  Unlock memory pages used for DMA buffer
  ---------------------------------------}
var
   Error : Byte ;
   DDSSegment,DDSOffset : Word ;
label
     NoError ;
begin
     Error := 0 ;
     { Get segment/offset of DDS memory block }
     DDSOffset := Ofs(DDS) ;
     DDSSegment := Seg(DDS) ;
     asm
        push ES ;           { ES,DI registers MUST be saved }
        push DI ;
        mov ah,$81 ;        { Set up call to VDS }
        mov al,4 ;
        mov dx,0 ;
        mov bx, DDSSegment ;
        mov ES,bx ;
        mov bx, DDSOffset ;
        mov DI,bx ;
        int $4b ;         { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:
        pop DI ;            { Restore registers }
        pop ES ;
        end ;
     Result := Error ;
     end ;


Function VDS_CopyToDMABuffer( var DDS : TDDS ;
                              BufPointer : Pointer ;
                              BufSize : LongInt ) : Word ;
{ -----------------------------------------
  Copy data into the Windows DMA buffer
  (previously requested by VDS_RequestDMABuffer)
  Enter with :
  DDS : Valid DMA Data Segment record (set by VDS_RequestDMABuffer)
  BufPointer : pointer to source data buffer
  BufSize : No. of bytes to be transferred
  Return :
  Err : 0 = OK
  -----------------------------------------}
var
   Error : Byte ;
   DDSSegment,DDSOffset : Word ;
label
     NoError ;
begin
     Error := 0 ;
     DDS.RegionSize := BufSize ;
     DDS.Segment := VDS_GetSegment( BufPointer ) ;
     DDS.Offset := VDS_GetOffset( BufPointer ) ;
     { Get segment/offset of DDS memory block }
     DDSOffset := Ofs(DDS) ;
     DDSSegment := Seg(DDS) ;
     asm
        push ES ;                { ES,DI registers MUST be saved }
        push DI ;
        mov ah,$81 ;
        mov al,9 ;
        mov dx,0 ;
        mov bx, DDSSegment ;    { Point ES:DI -> DDS block }
        mov ES,bx ;
        mov bx, DDSOffset ;
        mov DI,bx ;

        mov bx,0 ;              { bx:dx = starting offset in DMA buffer = 0 }
        mov cx,0 ;

        int $4b ;               { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:

        pop DI ;
        pop ES ;
        end ;
     Result := Error ;
     end ;


Function VDS_CopyFromDMABuffer( var DDS : TDDS ;
                                BufPointer : Pointer ;
                                BufSize : LongInt ) : Word ;
{ -----------------------------------------
  Copy data out of the Windows DMA buffer
  (previously requested by VDS_RequestDMABuffer)
  Enter with :
  DDS : Valid DMA Data Segment record (set by VDS_RequestDMABuffer)
  BufPointer : pointer to destination data buffer
  BufSize : No. of bytes to be transferred
  Return :
  Err : 0 = OK
  -----------------------------------------}
var
   Error : Byte ;
   DDSSegment,DDSOffset : Word ;
label
     NoError ;
begin
     Error := 0 ;
     DDS.RegionSize := BufSize ;
     DDS.Segment := VDS_GetSegment( BufPointer ) ;
     DDS.Offset := VDS_GetOffset( BufPointer ) ;
     { Get segment/offset of DDS memory block }
     DDSOffset := Ofs(DDS) ;
     DDSSegment := Seg(DDS) ;
     asm
        push ES ;                { ES,DI registers MUST be saved }
        push DI ;
        mov ah,$81 ;
        mov al,$a ;
        mov dx,0 ;
        mov bx, DDSSegment ;    { Point ES:DI -> DDS block }
        mov ES,bx ;
        mov bx, DDSOffset ;
        mov DI,bx ;

        mov bx,0 ;              { bx:dx = starting offset in DMA buffer = 0 }
        mov cx,0 ;

        int $4b ;               { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:

        pop DI ;
        pop ES ;
        end ;
     Result := Error ;
     end ;

Function VDS_RequestDMABuffer( var DDS : TDDS ;
                               Flags : Word ;
                               BufPointer : Pointer ;
                               BufSize : LongInt ) : Word ;
{ -----------------------------------------
  Request the use of the Windows DMA buffer
  Enter with :
  DDS : Empty DMA Data Segment record
  Flags : 1 = Copy data from BufPointer to Windows buffer
  BufPointer : pointer to D/A data buffer
  BufSize : No. of bytes to be transferred
  Return :
  Err : 0 = OK
  -----------------------------------------}
var
   Error : Byte ;
   DDSSegment,DDSOffset : Word ;
label
     NoError ;
begin
     Error := 0 ;
     DDS.RegionSize := BufSize ;
     DDS.Segment := VDS_GetSegment( BufPointer ) ;
     DDS.Offset := VDS_GetOffset( BufPointer ) ;
     DDS.BufferID := 0 ;
     DDS.PhysicalAddress := 0 ;
     { Get segment/offset of DDS memory block }
     DDSOffset := Ofs(DDS) ;
     DDSSegment := Seg(DDS) ;
     asm
        push ES ;                { ES,DI registers MUST be saved }
        push DI ;
        mov ah,$81 ;
        mov al,7 ;
        mov dx,Flags ;
        mov bx, DDSSegment ;    { Point ES:DI -> DDS block }
        mov ES,bx ;
        mov bx, DDSOffset ;
        mov DI,bx ;

        int $4b ;               { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:

        pop DI ;
        pop ES ;
        end ;
     Result := Error ;
     end ;
Function VDS_ReleaseDMABuffer( var DDS : TDDS ; Flags : Word ) : Word ;
{ -------------------------------------------------------------
  Release Windows DMA buffer
  Enter with :
  DDS : DMA Data Segment record (filled by VDS_RequestDMABuffer)
  Flags : 1=Copy data from buffer
  Returns :
  Error code : 0=OK
  --------------------------------------------------------------}
var
   Error : Byte ;
   DDSSegment,DDSOffset : Word ;
label
     NoError ;
begin
     Error := 0 ;
     { Get segment/offset of DDS memory block }
     DDSOffset := Ofs(DDS) ;
     DDSSegment := Seg(DDS) ;

     asm
        push ES ;           { ES,DI registers MUST be saved }
        push DI ;
        mov ah,$81 ;        { Set up call to VDS }
        mov al,8 ;
        mov dx,Flags ;
        mov bx, DDSSegment ;    { Point ES:DI -> DDS block }
        mov ES,bx ;
        mov di, DDSOffset ;

        int $4b ;         { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:

        pop DI ;            { Restore registers }
        pop ES ;
        end ;
     Result := Error ;
     end ;


Function VDS_DisableDMATranslation( DMAChannel : Word ) : Word ;
{ -----------------------------------------------------------
  Disable linear-->physical address translation done by VDS
  when address is written to virtualised DMA channel address port
  --------------------------------------------------------------}
var
   Error : Byte ;
label
     NoError ;
begin
     Error := 0 ;
     asm
        mov ah,$81 ;
        mov al,$B ;
        mov bx,DMAChannel ;
        mov dx,0 ;
        int $4b ; { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:
        end ;
     Result := Error ;
     end ;


Function VDS_EnableDMATranslation( DMAChannel : Word ) : Word ;
{ -----------------------------------------------------------
  Disable linear-->physical address translation done by VDS
  when address is written to virtualised DMA channel address port
  --------------------------------------------------------------}
var
   Error : Byte ;
label
     NoError ;
begin
     Error := 0 ;
     asm
        mov ah,$81 ;
        mov al,$C ;
        mov bx,DMAChannel ;
        mov dx,0 ;
        int $4b ; { VDS Interrupt }
        jnc NoError
        mov Error,al ;
        NoError:
        end ;
     Result := Error ;
     end ;


function VDS_GetSegment( Pntr : Pointer ) : Word ;
{ --------------------------------------------------------
  Return the segment part of the address stored in Pointer
  --------------------------------------------------------}
var
   pSeg,pOffset,Segment : Word ;
begin
     pSeg := seg(Pntr) ;
     pOffset := Ofs(Pntr) ;
     asm
        push ds ;
        mov ax, pSeg ;
        mov ds,ax ;
        mov si, pOffset ;
        mov ax,ds:[si]+2 ;    { Segment part of address (upper word) }
        mov Segment,ax ;
        pop ds ;
        end ;
     Result := Segment ;
     end ;


function VDS_GetOffset( Pntr : Pointer ) : Word ;
{ --------------------------------------------------------
  Return the offset part of the address stored in Pointer
  --------------------------------------------------------}
var
   pSeg,pOffset,OffsetWord : Word ;
begin
     pSeg := seg(Pntr) ;
     pOffset := Ofs(Pntr) ;
     asm
        push ds ;
        mov ax, pSeg ;
        mov ds,ax ;
        mov si, pOffset ;
        mov ax,ds:[si] ;    { offset part of address }
        mov OffsetWord,ax ;
        pop ds ;
        end ;
     Result := OffsetWord ;
     end ;


function InpB( Port : Word ) : Word ;
{ -----------------------------------------
  Read a byte from I/O port at address Port
  -----------------------------------------}
var
   Value : Word ;
begin
     asm
        mov ax,0 ;
        mov dx,Port
        In al,dx ;
        mov Value,ax ;
        end ;
     Result := Lo(Value) ;
     end ;


function InpW( Port : Word ) : Word ;
{ -----------------------------------------
  Read a word from I/O port at address Port
  -----------------------------------------}
var
   Value : Word ;
begin
     asm
        Mov dx,Port
        In ax,dx ;
        mov Value,ax ;
        end ;
     Result := Value ;
     end ;


procedure OutB( Port,Value : Word ) ;
{ -----------------------------------------
  Write a byte to I/O port at address Port
  -----------------------------------------}
begin
     asm
        mov dx,Port ;
        mov ax,Value ;
        Out dx,al ;
        end ;
     end ;


procedure OutW( Port,Value : Word ) ;
{ -----------------------------------------
  Write a word to I/O port at address Port
  -----------------------------------------}
begin
     asm
        mov dx,Port ;
        mov ax,Value ;
        Out dx,ax ;
        end ;
     end ;


procedure SetBits( var Dest : Word ; Bits : Word ) ;
{ ---------------------------------------------------
  Set the bits indicated in "Bits" in the word "Dest"
  ---------------------------------------------------}
begin
     Dest := Dest or Bits ;
     end ;


procedure ClearBits( var Dest : Word ; Bits : Word ) ;
{ ---------------------------------------------------
  Clear the bits indicated in "Bits" in the word "Dest"
  ---------------------------------------------------}
var
   NotBits : Word ;
begin
     NotBits := not Bits ;
     Dest := Dest and NotBits ;
     end ;


end.

