;       SCCSID = @(#)demdev.asm 6.19 92/05/13

        page    ,132
        title   demdev - DOS 3.x Box Device I/O support

NAME    demdev

;
;   Title:
;
;       demdev
;
;
;   Author:
;
;       Benjamin W. Slivka
;       (c) Microsoft Corporation
;       1986,1987
;
;
;   Description:
;
;       demdev contains routines to support I/O to devices for the
;       VDM component of OS/2
;
;
;   Modification History:
;
;       21 Jul 1986 - BWS
;               Initial version.
;
;       11 Aug 1986 - BWS
;               Use static request packet in DosGroup.  This eliminates
;               ES overrides for using packet, as well as calls to Alloc/
;               FreeReqPacket.
;
;       19 Aug 1986 - BWS
;               Check for zero length reads and writes.
;
;       29 Aug 1986 - BWS
;               When we run out of room in RMDevInfo tables, close file
;               and return error_no_more_files.  Test for bad file handle
;               on return from pJFNFromHandle.
;
;       29 Oct 1986 - KCD
;               Added support for interim characters.  When interim
;               support is on we put additional information in the
;               device driver packet.
;       31 Aug 1987 - BWS
;               Make RMDevCheck dual-mode.
;        8 Jan 1988 - PAL
;               Make RMDevCookedWrite send multibyte packets to
;               OS/2 devices.
;
;       31 Mar 1988 - Microsoft
;               IFS updates
;
;       14-Dec-1988 - danno
;               Modified for MVDM
;       23-Aug-1989 - avin
;               PTR B785010 - input redirection problem
;
;***********************************************************************;BD001;
;    03-01-91 IBM - John M. Hare                                        ;BN001;
;            DCR1310 - removed usage of wrong structure names in        ;BN001;
;                      RMDevGIOCTL routine.  To get cl - minorfunction  ;BN001;
;                      to work.                                         ;BN001;
;***********************************************************************;BD002;
; 5Mar91 - G.L.Fulk                                                     ;BD002;
; DCR 1202.   Dword alignment for performance boost.                    ;BD002;
;***********************************************************************;BN004;
; 28Feb91 - G.L.Fulk                                                    ;BN004;
; IOCtl calls to an OS/2 device driver fail.  The routine IOFunc is     ;BN004;
; being changed to detect the presents of an OS/2 driver as opposed     ;BN004;
; to a DOS driver.  The BX register needs to be preserved in this file. ;BN004;
; Files affected:   v86code\DemIoctl.asm                                ;BN004;
;                   v86code\DemDev.asm                                  ;BN004;
;                   mvdm\DemIoc2.asm                                    ;BN004;
;***********************************************************************;BN004;
;       04-01-91 IBM - John M. Hare                                     ;BN005;
;               B721247 - moved usage of DOS3.3 structure names from    ;BN005;
;                         RMDevGIOCTL to RMDevIOCTL routine.  To get    ;BN005;
;                         DOS3.3 and DOS4/5.0 generic device drivers    ;BN005;
;                         to work.                                      ;BN005;
;***********************************************************************;BN005;
;   B006  -  Mark Rogalski                                              ;BN006;
;            CP20 DCR 1360   Add DOS=HIGH support by removing check for ;BN006;
;                            -1 as segment/invalid handle indicator.    ;BN006;
;***********************************************************************;BN006;
;  30May91 - G.L.Fulk                                                   ;BN007;
;  Get information from the RMDevHandleInfo area (fake SFT) was not     ;BN007;
;  correctly documented.  The correct statement is:                     ;BN007;
;    IF DI=-1                                                           ;BN007;
;    THEN you are dealing with a file                                   ;BN007;
;    ELSE IF (WORD PTR ES:[DI])=-1                                      ;BN007;
;         THEN you are dealing with an OS/2 device                      ;BN007;
;         ELSE you are dealing with a DOS device                        ;BN007;
;  The mistake was the "WORD PTR ES:[DI] comparison was commented as    ;BN007;
;  "DWORD PTR ES:[DI]= -1:0".  The initial open state assigns a "0"     ;BN007;
;  to the second word, but subsequent DOS calls can alter that value.   ;BN007;
;  The check of just the first word is sufficient.  Since if it is a    ;BN007;
;  DOS device, then the double word at ES:DI is the address of the      ;BN007;
;  device header.  It's not possible for a device header to have a      ;BN007;
;  value of -1.                                                         ;BN007;
;                                                                       ;BN007;
;  Also, start using the structure macros.                              ;BN007;
;***********************************************************************;BN007;
;  25Sep91 - G.L.Fulk                                                   ;BN008;
;***********************************************************************;BN008;
;  93Feb92 - G.L.Fulk                                                   ;BN009;
;  Generic IOCTL packets must have the SI and DI information saved.     ;BN009;
;***********************************************************************;BN009;
;  05Mar92 - G.L.Fulk                                                   ;BN010;
;  Get IOFunc output status to a printer (ie. LPT1) with the spooler    ;BN010;
;  enabled will return CY.  Fake an always ready under this case.       ;BN010;
;***********************************************************************;BN010;
;  17Aug92 - G.L.Fulk                                                   ;BN011;
;  Preserve the AX register during a write operation to a file.         ;BN011;
;***********************************************************************;BN011;
;
;   Procedures:
;
;       RMDevCheckFCB
;       RMDevCheck
;       Get_IO_SFT
;       SwapBack
;       SwapCon
;       IOFunc
;       IOFuncToFile
;       FileInput
;       RMDevRead
;       RMDevReadConsole
;       RMDevRawRead
;       RMDevCookedRead
;       RMDevWrite
;       RMDevWriteConsole
;       RMDevRawWrite
;       RMDevCookedWrite
;       RMDevIOCTL
;       RMDevGIOCTL                                                     ;BN001;
;
;   Design Notes:
;
;       a. RMDevInfo contains device flags and pointers for devices opened
;          by the 3xBox.
;
;       b. The CPM 1-12 calls use Get_IO_SFT to get the information to do I/O;
;          Get_IO_SFT either returns ptr to an RMDevInfo entry (for a device)
;          or a phony ptr, seg = 0, offset = handle (for a file).  IOFunc
;          does the actual I/O.  If the pointer is phony, we mode switch
;          and call the file system.  Otherwise, we call DevIOCall2.
;
;       c. $Open/$Creat cause an entry to be made in RMDevList and RMDevInfo.
;          RMDevList contains the SFN number, and the corresponding entry in
;          RMDevInfo contains device flags and a pointer to the device header.
;
;       d. $Close causes an entry to be removed from RMDevList when the SFT
;          entry is freed.

.xlist
include v86seg.inc
.xcref
include basemaca.inc
include osmaca.inc
include oldmaca.inc
include demmac.inc
include dossymc.inc
include cpmfcb.inc
include devsym.inc
include dos4dd.inc                      ; dos 4.01 devsym.
include dos3dev.inc
include error.inc
include harderr.inc
include pmode.inc
include sft.inc
;include tcbdata.inc
include mi.inc
include intnat.inc
include iniequ.inc
include mvdmexp.inc
include chardd.inc
include demsvc.inc
include demdev.inc
include struc30.inc
.cref
.list

        CpuMode 386

;*  Global and Task References

        ReferV86    ConSwap,BYTE    ; !0 if handle I/O to console
        ReferV86    ConBuf,BYTE     ; Handle read console input buffer
        ReferV86    SaveCurFlg      ; !0 if writing interim character
        ReferV86    CurrentPDB,WORD ; Current PDB segment
        ReferV86    InterCon,BYTE   ; support interim charcters for console
        ReferV86    ContPos,WORD    ; Position to continue read from ConBuf
        ReferV86    ConSFT,DWORD    ; Address of Console SFT/RMDI
        ReferV86    ExtErr,WORD
        ReferV86    ExtErr_Locus,BYTE
        ReferV86    NAME1,BYTE
        ReferV86    NULDEV,BYTE
        ReferV86    CALLDEVAD,DWORD

        EXTRN   $Std_Con_String_Input:NEAR
        EXTRN   DskStatChk:NEAR
        EXTRN   Get_User_Stack:NEAR
        EXTRN   ECritDevice:NEAR
        EXTRN   LCritDevice:NEAR
        EXTRN   CharHard:NEAR
        EXTRN   Cons_String_Output:NEAR
        EXTRN   pJFNFromHandle:NEAR
        EXTRN   SFFromSFN:NEAR
        EXTRN   ECritDisk:NEAR
        EXTRN   LCritDisk:NEAR
        EXTRN   VSFNFromHandle:NEAR
        EXTRN   OUTT:NEAR
        EXTRN   SET_I24_EXTENDED_ERROR:NEAR


BREAK <Static Data>

V86DATA    SEGMENT

;** RMDevHandleInfo - Shadow "SFTs" for devices opened for handle in 3xBox
;** RMDevFCBInfo    - Shadow "SFTs" for devices opened by FCB in 3xBox
;
;   + There is one entry in this table for each 3xBox device SFT.
;   + Entries are added only on device opens.
;   + Entries are removed when the corresponding SFT is freed.

            PUBLIC  RMDevHandleInfo,RMDevFCBInfo
RMDevHandleInfo rmdi    MAX_RMDevHandleOpens DUP (<>)
RMDevFCBInfo    rmdi    MAX_RMDevFCBOpens DUP (<>)

InterimChar     db      0

;** RMDevReqPkt - static request packet for RMDev routines
;
;   Putting this static packet in DosGroup allows us to avoid ES overrides
;   and the (slight) overhead of calling Alloc/FreeReqPacket.
Public RMDevReqPkt
RMDevReqPkt     Packet  <>
                DB      ?               ; Padding to make it 32 bytes long
.ERRNZ  (SIZE Packet - 31)              ; Ensure packet size doesn't change

V86DATA    ENDS

V86CODE SEGMENT
        ASSUME  CS:V86GROUP

;***LP  RMDevOpCl - Open/Close the specified character device
;***LP  RMDevOpen - Open the specified character device
;***LP  RMDevClose - Close the specified character device
;
;       Call DevIOCall2 with an open or close packet
;
;       ENTRY
;           ES:DI
;               address of RMDI entry
;           RMDevOpCl: AL = CMDOpen or CMDClose
;
;       EXIT
;           SUCCESS
;               CF = 0
;           FAILURE
;               CF = 1
;               AL = error code
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;           adjust rmdi_refcnt (inc on open, dec on close)
;           if device supports open/close/Removable Media -->
;               create open/close packet
;               call DevIOCall2
;               if error -->
;                   HARD ERROR
;
;       NOTES
;           None
;
;       WARNINGS
;           None
;
;       CALLS
;           DevIOCall2
;
;       USES
;           AX,CX,DX,DS,SI,DI
;
        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

Procedure RMDevOpen,near
        mov     al,CMDOpen
        inc     es:[di].rmdi_refcnt
        call    RMDevOpCl
        .if     c                                                       ;BC007;
          dec     es:[di].rmdi_refcnt
        .endif                                                          ;BC007;
        ret
EndProc     RMDevOpen


Procedure RMDevClose,near                                               ;BN008;
        mov     al,CMDClose                                             ;BN008;
ifdef   DEMSTRICT                                                       ;BN008;
        .if     <<es:[di].rmdi_refcnt> eq 0>                            ;BN008;
          InternalError <DosKrnl: RMDevClose, refcnt>                   ;BN008;
        .endif                                                          ;BN008;
endif                                                                   ;BN008;
        dec     es:[di].rmdi_refcnt                                     ;BN008;
        align   4                                                       ;BN002;
if2                                    ;Issure that RMDevClose and      ;BD002;
        .errnz  RMDevOpCl-$            ;RMDevOpCl are next to each      ;BD002;
endif                                  ;other.                          ;BD002;
EndProc   RMDevClose

Procedure RMDevOpCl
        .if     <BIT <es:[di].rmdi_flags> and RMDI_30>  ; supports open/close?
          .repeat
            push    ax
            lds     si,es:[di].rmdi_DevPtr  ; get address of device header
            mov     bx,offset V86GROUP:RMDevReqPkt
            mov     es:[bx].PktLen,DOPCLHL
            mov     es:[bx].PktUnit,0
            mov     es:[bx].PktCmd,al
            mov     es:[bx].PktStatus,0
            xor     ax,ax
            mov     es:[bx].PktDOSLink.Segmt,ax
            mov     es:[bx].PktDOSLink.Offst,ax
            mov     es:[bx].PktDevLink.Segmt,ax
            mov     es:[bx].PktDevLink.Offst,ax
            call    DevIOCall2          ; do the open/close
            mov     di,es:[bx].PktStatus
            .leave  <BIT DI nand STERR> ; no error
            mov     ah,86h
            call    CharHard
            .leave  <AL ne 1>           ; IGNORE or FAIL
                                        ; note that FAIL is essentually IGNORED
            pop     ax                  ; get back Unit, Command
          .until
          clc
          pop     ax
        .endif
        ret
EndProc   RMDevOpCl

;***LP  RMDevCheckNameFCB - See if the specified device is a DOS Device
;
;       Compare the specified name to the DOS devices to find a match
;
;       ENTRY
;           DS:DX
;               Address of file specification
;
;       EXIT
;           SUCCESS
;               (File spec is a DOS Device)
;               CF = 0
;               ES:BX points to device header
;               DS:DX unchanged
;           FAILURE
;               (File spec is *not* a DOS device)
;               CF = 1
;               DS:DX unchanged
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;           p = NULDEV
;           while p != -1 -->
;               if filespec == p->devname
;                   return SUCCESS
;               p = p->pNext
;           return FAILURE
;
;       NOTES
;           None
;
;       WARNINGS
;           None
;
;       CALLS
;           None
;
;       USES
;           AX,BX,CX,SI,DI,ES
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevCheckNameFCB,NEAR
        mov     ax,cs
        mov     es,ax
        lea     di,NAME1                ; ES:DI = Temp. buffer
        mov     si,dx
        inc     si                      ; DS:SI = Actual FCB
        push    si
        push    di

        mov     cx,DEVNAMELEN           ; Base length
        .repeat
          mov     al,byte ptr ds:[si]   ; Convert lower case letters to
          .if     <AL ae "a"> and       ; uppercase and put in temp.
          .if     <AL be "z">           ; buffer,.
            sub     al,20h              ; To upper case
          .endif
          mov     byte ptr es:[di],al
          inc     si
          inc     di
        .loop

        pop     di
        pop     si

        jmp     short RMSearchDevName
EndProc   RMDevCheckNameFCB

;***LP  RMDevCheckName - See if the specified device is a DOS Device
;***LP  RMSearchDevName - entry for RMDevCheckNameFCB
;
;       Compare the specified name to the DOS devices to find a match
;
;       ENTRY
;           DS:DX
;               Address of file specification
;
;       EXIT
;           SUCCESS
;               (File spec is a DOS Device)
;               CF = 0
;               ES:BX points to device header
;               DS:DX unchanged
;           FAILURE
;               (File spec is *not* a DOS device)
;               CF = 1
;               DS:DX unchanged
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;           Call DemGetBaseName
;           if valid name ->
;               p = NULDEV
;               while p != -1 -->
;                   if filespec == p->devname
;                       return SUCCESS
;                   p = p->pNext
;           return FAILURE
;
;       NOTES
;           None
;
;       WARNINGS
;           None
;
;       CALLS
;           DemGetBaseName
;
;       USES
;           AX,BX,CX,SI,DI,ES
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevCheckName,NEAR
        mov     ax,cs
        mov     es,ax
        lea     di,NAME1                ; ES:DI = destination buffer
        SVC     SVC_DEMGETBASENAME      ; get the base name
        jc      short dcn40             ; exit if not a valid devspec

Entry   RMSearchDevName
        push    ds
        push    cs
        pop     ds
        lea     bx,V86GROUP:NULDEV      ; DS:BX = head of device chain
        mov     ax,di                   ; save DI
        .repeat
          lea     si,[bx].SDevName      ; DS:SI = device name
          mov     cx,DEVNAMELEN
          repe cmpsb                    ; compare with the name that we have
          mov     di,ax                 ; restore di
          je      short dcn20           ; exit if match
          lds     bx,ds:[bx].SDevNext   ; DS:BX = next device header
        .until  <BX eq -1>              ; loop for another
        stc
        jmp     short dcn30

dcn20:  mov     ax,ds
        mov     es,ax                   ; es:bx = header
        clc                             ; indicate SUCCESS
dcn30:  pop     ds
dcn40:  ret
EndProc   RMDevCheckName

;***LP  RMDevAllocDI - Allocate a DevInfo Entry
;
;       Look for an available DevInfo entry
;
;       ENTRY
;           ES:BX
;               DOS Device -> Address of device header
;               OS2 Device -> BX = -1, ES=fake SFN
;           AX
;               device attributes
;
;       EXIT
;           SUCCESS
;               CF = 0
;               AX = V86 SFN
;           FAILURE
;               CF = 1
;               AX = ERROR_TOO_MANY_OPEN_FILES
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;           For each RMDI entry -->
;               if DevPtr.Segmt == -1 -->
;                   Initialize RMDI entry
;                       return SUCCESS
;           Return FAILURE
;
;       NOTES
;           None
;
;       WARNINGS
;           None
;
;       CALLS
;
;
;       USES
;           AX,CX,DX,SI,DS
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevAllocDI,NEAR
        mov     dx,ax
        mov     ax,cs
        mov     ds,ax
        ASSUME  DS:V86GROUP
        xor     si,si
        mov     cx,MAX_RMDevHandleOpens

dad10:  cmp     RMDevHandleInfo[si].rmdi_refcnt,0 ; no references here?
        je      short dad20                       ; exit if found (carry clear)
        add     si,size rmdi
        loop    dad10
        mov     ax,ERROR_TOO_MANY_OPEN_FILES      ; set error code
        stc
        jmp     short dad30

dad20:  mov     RMDevHandleInfo[si].rmdi_devPtr.loword,bx
        mov     RMDevHandleInfo[si].rmdi_devPtr.hiword,es
        mov     RMDevHandleInfo[si].rmdi_flags,dx
        mov     RMDevHandleInfo[si].rmdi_refcnt,1
        mov     ax,cx
        not     al
        clc
dad30:  ret
EndProc   RMDevAllocDI

;***LP  RMDevCloseDI - Close a DevInfo Entry
;
;       For DOS devices, call RMDevClose; for OS/2 devices,
;       call DemClose.  Deallocate entry on the last close.
;
;       ENTRY
;           BX = VSFN
;
;       EXIT
;           SUCCESS
;               CF = 0
;           FAILURE
;               CF = 1
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;
;       NOTES
;           None
;
;       WARNINGS
;           None
;
;       CALLS
;           SFFromSFN, DemClose, RMDevClose
;
;       USES
;           AX,BX,CX,DX,SI,DI,DS,ES
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevCloseDI
        call    SFFromSFN               ; get the address of the rmdi entry
ifdef   DEMSTRICT
        .if     c or
        .if     <<es:[di].rmdi_refcnt> eq 0> ; panic if bad RMDI entry  ;BN008;
          InternalError    <DosKrnl: RMDevCloseDI>                      ;BN008;
        .endif                                                          ;BN008;
else
        jc      short dcd20
endif

;BN006 The following section used to check if the OS/2 handle was -1, but
;BN006   this should never occur. We need to remove this because if DOS is
;BN006   loaded High, a RM device driver can have a segment of 0xFFFF.

dcd10:  call    RMDevClose              ; close the RM device
dcd20:  ret

EndProc   RMDevCloseDI

;***LP  RMDevCheck - See if VSFN is for a device
;
;       Return indication if the VSFN is for a device and, if so,
;       if the device is a real-mode device.
;
;       ENTRY
;           BX = VSFN
;
;       EXIT
;           HANDLE IS FOR A DEVICE
;               CF = 0
;               (ES:DI) = RMDI entry
;
;               DEVICE IS A REAL MODE DEVICE
;                   ZF = 0
;               DEVICE IS A PROTECT-MODE DEVICE
;                   ZF = 1
;                   BX = honest OS/2 handle
;           HANDLE IS FOR A FILE
;               CF = 1
;               ZF = 1
;               BX = honest OS/2 handle
;           IN OTHER WORDS
;               CF  --> DEVICE (V86 or OS/2)
;               ZF  --> OS/2   (FILE or DEVICE)
;               !ZF --> CF     (V86 --> DEVICE) (no such thing as a V86 file)
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;           if device -->
;               CF = 0
;               if OS/2 device -->
;                   ZF = 1
;               else
;                   ZF = 0
;           else
;               CF = 1
;               ZF = 1
;
;       NOTES
;           None
;
;       WARNINGS
;           None
;
;       CALLS
;           SFFromSFN
;
;       USES
;           if a device --> ES:DI
;           if an OS/2 device --> BX
;
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevCheck,NEAR
ifdef   DEMSTRICT
        .if     <NONZERO BH>            ; indicate OS/2 file if bad VSFN;BN008;
          InternalError    <DosKrnl: RMDevCheck, bad VSFN>              ;BN008;
        .endif                                                          ;BN008;
endif
        cmp     bl,0ffh
        je      short dck20
        cmp     bl,VSFN_DEV
        jb      short dck20             ; exit if not a device
        call    SFFromSFN
ifdef   DEMSTRICT
        .if     c                       ; should never get an error here;BN008;
          InternalError    <DosKrnl: RMDevCheck, SFFromSFN>             ;BN008;
        .endif                                                          ;BN008;
endif
        .if     <<es:[di].rmdi_DevPtr.Offst> eq -1>  ; OS/2 device?
          mov     bx,es:[di].rmdi_DevPtr.Segmt       ; get OS/2 handle
        .endif
        clc                             ; a device (CF=0)
        ret

dck20:  cmp     al,al                   ; an OS/2 file (ZF=1/CF=1)
        stc
        ret

EndProc   RMDevCheck

;***LP  RMDevXFlags - Get rmdi_flags from device flags
;
;       Get the device attributes and convert them to rmdi_flags
;
;       ENTRY
;           AX = device attribute word
;
;       EXIT
;           AX = flags for the RMDI entry
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;
;       NOTES
;           None
;
;       WARNINGS
;           None
;
;       CALLS
;           None
;
;       USES
;           AX
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

;       The following bits come from the device header

RMDI_FROMDEV    EQU     DEV_SPEC OR DEV_CLOCK OR DEV_NULL OR DEV_COUT OR DEV_CIN OR DEV_30
.errnz  DEV_SPEC  - RMDI_SPECIAL
.errnz  DEV_CLOCK - RMDI_CLOCK
.errnz  DEV_NULL  - RMDI_NULL
.errnz  DEV_COUT  - RMDI_CON_OUT
.errnz  DEV_CIN   - RMDI_CON_IN
.errnz  DEV_30    - RMDI_30

Procedure RMDevXFlags,NEAR
        and     ax,RMDI_FROMDEV                 ; get interesting bits
        or      al,RMDI_DEVICE OR RMDI_EOF      ; not at EOF (?)
        ret
EndProc   RMDevXFlags

;***LP  RMDevCheckFCB - See if FCB is for a device
;
;       Return indication if the FCB is for a device and, if so,
;       if the device is a real-mode device.
;
;       ENTRY
;           DS:SI
;               Address of the FCB
;
;       EXIT
;           FCB IS FOR A LOCAL DEVICE
;               CF = 0
;               (ES:DI) = RMDI entry
;           FCB IS FOR A OS/2 DEVICE
;               CF = 1
;               Zf = 0
;               BX = Real SFN
;           FCB IS FOR A FILE
;               CF = 1
;               zf = 1
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;           Get VSFN
;           Call RMDevCheck
;
;       NOTES
;           None
;
;       USES
;           if a local device --> ES:DI
;           FLAGS

        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevCheckFCB,NEAR
        xor     bh,bh
        mov     bl,ds:[si].fcb_sfn      ; (AL) = FCB SFN
        jmp     RMDevCheck
EndProc   RMDevCheckFCB

BREAK <Get_IO_SFT - Get pointer for functions 1-12 I/O>

;***    Get_IO_SFT - Get pointer for functions 1-12 I/O
;
;       The old name of "Get_IO_SFT" is retained here to minimize
;       impact on code imported from MS-DOS 4.x.
;
;       Convert fake JFN number in BX to an RMDevInfo pointer.
;
;       If the handle does not refer to a character device, we return a
;       "phony" SFT pointer, (handle:-1).  We cannot return a real SFT pointer
;       because SFTs are linked in protect-mode (and may be in high memory).
;       IOFunc recognizes this phony pointer, and call the Prot-mode routine.
;
;       If ConSwap is TRUE and the handle is 0 or 1 (stdin or stdout), then
;       we get the offset of the RMDevInfo entry out of ConSFT.  Otherwise,
;       we use RMDevCheck to determine if the handle is for a character device.
;
;
;       ENTRY:  (BX) = fake JFN desired (handle)
;
;       EXIT:   Carry Clear
;                   (ES:DI) = pointer to RMDevInfo entry
;                             if es:di == 0:-1 then it's an OS/2 driver ;BN004;
;                           = (handle:-1) if NOT for character device
;               Carry Set
;                   (AX) = error code
;
;       USES:   AX,BX,DI,ES

        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure Get_IO_SFT,NEAR
        push    ds
        push    ss
        pop     ds
        assume  ds:V86GROUP
        .if     <[ConSwap] ne 0> and    ; console is not swapped
        .if     <BX be 1>               ; stdin or stdout?
          les     di,ConSFT
          jmp     short gii40
        .endif
        call    VSFNFromHandle
        jc      short gii20             ; exit if bad handle
        cmp     bl,VSFN_DEV
        jb      short gii30             ; skip if a file
        call    SFFromSFN               ; get the RDMI entry
gii20:  pop     ds
        ret

gii30:  push    bx                      ; handle is for file
        pop     es                      ; ES = handle
        mov     di,-1                   ; DI = -1

gii40:  clc                             ; indicate success
        jmp     gii20

EndProc   Get_IO_SFT

BREAK <SwapBack - Turn off Console Swap>
;***    SwapBack - Turn off Console Swap
;
;       Indicate that there is no longer I/O occurring through another SFT
;       outside of handles 0 and 1.
;
;       ENTRY:  (DS) = V86GROUP
;
;       EXIT:   ConSwap is set to false.
;
;       USES:   none
;
        ASSUME  DS:V86GROUP,ES:NOTHING,SS:NOTHING

Procedure SwapBack,NEAR
        mov     ConSwap,0               ; turn off ConSwap
        ret
EndProc   SwapBack

BREAK <SwapCon - Temporarily override JFNs 0 and 1>
;***    SwapCon - Temporarily override JFNs 0 and 1
;
;       Set ConSFT for use by the 1-12 primitives.
;       This allows $Read and $Write to use the routines in cpmio.asm
;       to do I/O to the console device.
;
;       ENTRY:  (ES) = V86GROUP
;               (DI) = RMDevInfo offset for console device
;
;       EXIT:   ConSwap is set.
;               ConSFT = (SI)
;
;       USES:   none
;
        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

Procedure SwapCon,NEAR,LOCAL
        mov     ConSwap,-1              ; ConSwap = TRUE;
        mov     ConSFT.Offst,di         ; ConSFT = address of SFT/RMDI
        mov     ConSFT.Segmt,es
        ret
EndProc   SwapCon

BREAK <Device Request Packet Tables>
V86TABLE   SEGMENT

;*  Length of packets
;
LenTab  DB      DRDWRHL                 ; 0 input
        DB      DRDNDHL                 ; 1 input status
        DB      DRDWRHL                 ; 2 output
        DB      DSTATHL                 ; 3 output status
        DB      DFLSHL                  ; 4 input flush
        DB      DRDWRHL                 ; 5 output string

;*  Error/Function table
;
;               Error   Function
CmdTab  DB      86h,    CMDInput        ; 0 input
        DB      86h,    CMDNDR          ; 1 input status
        DB      87h,    CMDOutput       ; 2 output
        DB      87h,    CMDOutputS      ; 3 output status
        DB      86h,    CMDInputF       ; 4 input flush
        DB      87h,    CMDOutput       ; 5 output string

V86TABLE   ENDS

BREAK <IOFunc - Do Function 1-12 I/O>
;***    IOFunc - Do Function 1-12 I/O
;
;       One of several single-character I/O functions is performed on a
;       device or file.  This function is used by the CP/M compatible I/O
;       functions, the IOCTL function and the ^C checking routines.
;
;       ENTRY:  (ES:DI) = Points to RMDevInfo entry
;                         = (handle:-1) if I/O is to a file
;                         = (V86GROUP:offset) if I/O is to a device
;                         = if es:di==0:-1 then it's an OS/2 device     ;BN004;
;               (AH)    = function code                                 ;BN004;
;                         = 0 Input           AL=?                      ;BN004;
;                         = 1 Input Status    AL=6                      ;BN004;
;                         = 2 Output          AL=?                      ;BN004;
;                         = 3 Output Status   AL=7                      ;BN004;
;                         = 4 Flush           AL=?                      ;BN004;
;                         = 5 Output String   AL=?                      ;BN004;
;               If single character output,
;                   (AL) = character if output character
;               If string output,
;                   (DS:DX) = string
;                      (CX) = length
;
;       EXIT:   Carry set, error
;               Carry clear, operation successful
;                   Input function
;                       (AL) = character
;                   Status request
;                       zero set, not ready
;                       zero reset, ready
;                           (AL) = character for input status
;                   File request
;                       Input Status
;                           Gets character but restores position
;                           Zero set on EOF
;                       Input
;                           Gets character advances position
;                           Returns ^Z on EOF
;                       Output Status
;                           Always ready
;
;       USES:   AX
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

public ftf_output_char,ftf_input_char,ftf_input
ftf_output_char DB      0
ftf_input_char  DB      0
ftf_handle      DW      0
ftf_input:
        SaveReg <bx,cx,dx,ds>
        mov     bx,cs:[ftf_handle]
        mov     cx,1                      ; (CX) = count of chars for I/O
        push    cs
        pop     ds
        assume  ds:V86GROUP
        mov     ds:[ftf_output_char],0                               ;PB736446;
        lea     dx,ds:[ftf_output_char]   ; (DS:DX) = I/O buffer
        call    FileInput                 ; input
        RestoreReg <ds,dx,cx,bx>
        assume  ds:nothing
        mov     al,cs:[ftf_output_char]
        or      al,al
        ret

public IOFuncOS2
IOFuncOS2:
        assume  es:nothing
        ;------ OS/2 device

        .select
        .when   <ZERO AH> ;input
          xor     al,al
          xchg    al,cs:[ftf_input_char]
          .if     <ZERO AL>
            call    ftf_input
          .endif

        .when   <AH eq 1> ;in status
          mov     al,cs:[ftf_input_char]
          .if     <ZERO AL>
            call    ftf_input
            mov     cs:[ftf_input_char],al
          .endif

        .when   <AH eq 2> or ;output
        .when   <AH eq 5>    ;string output
          SaveReg <bx,cx,dx,ds>
          .if     <AH eq 2>
            mov     cx,1                    ; (CX) = count of chars for I/O
            push    cs
            pop     ds
            assume  ds:V86GROUP
            mov     ds:[ftf_output_char],al
            lea     dx,[ftf_output_char]    ; (DS:DX) = I/O buffer
          .endif
          mov     bx,cs:[ftf_handle]
          SVC     SVC_DEMWRITE            ; output
          jc      ftfie
          RestoreReg <ds,dx,cx,bx>

        .when   <AH eq 3> ;output status
          push    bx
          mov     al,7
          mov     bx,cs:[ftf_handle]
          SVC     SVC_DEMIOCTL
          .if     c
            ;BD010; jmp     ftfie
            mov     al,-1                                               ;BN010;
          .endif
          or      al,al
          pop     bx

        .when   <AH eq 4> ;flush input
          mov     cs:[ftf_input_char],0
          or      ah,ah
          .ERRE   IO_INPUT_FLUSH
          ;NZ... always ready

        .endselect
        ret



Procedure IOFunc,NEAR
        .if     <DI eq -1>
          mov     cs:[ftf_handle],es

          push    ax
          push    bx
          mov     bx,es
          mov     al,0
          SVC     SVC_DEMIOCTL
          test    al,80h
          pop     bx
          pop     ax

          jnz     IOFuncOS2
          jmp     IOFuncToFile
        .endif

        .if     <<es:[di].rmdi_DevPtr.Offst> eq -1>
          push    di
          mov     di,es:[di].rmdi_DevPtr.Segmt
          mov     cs:[ftf_handle],di
          pop     di
          jmp     IOFuncOS2
        .endif
        assume  es:V86GROUP
        assume  ds:nothing

LocalVar  DevIOBuf,WORD                 ; buffer for single-character I/O

        EnterProc

;*  I/O is to a local device
;
;   Retry after error from device jumps here.

iof5:   PUSHA
        PUSH    DS
        MOV     BX,OFFSET V86GROUP:RMDevReqPkt ; (ES:BX) = request packet
        .if     <AH eq 5>               ; output string call?
          ;*  String output
          MOV     ES:[BX].IOpData.Segmt,DS
          MOV     ES:[BX].IOpData.Offst,DX
          MOV     ES:[BX].IOcount,CX
        .else
          MOV     ES:[BX].IOpData.Segmt,SS
          LEA     CX,DevIOBuf
          MOV     ES:[BX].IOpData.Offst,CX
          MOV     ES:[BX].IOcount,1
          MOV     byte ptr DevIObuf,AL
        .endif

;*  (ES:BX) = tiled request packet

        XOR     CX,CX
        MOV     ES:[BX].PktStatus,CX    ; clear status
        MOV     ES:[BX].IOmedia,CL      ; clear media byte

        push    bx
        MOV     BL,AH
        XOR     BH,BH                   ; (BX) = IOFUNC function
        MOV     AX,CS
        MOV     DS,AX
        ASSUME  DS:V86GROUP
        MOV     AH,LenTab[BX]           ; (AH) = packet length
        SHL     BX,1                    ; (BX) = word offset in CmdTab table
        MOV     CX,WORD PTR CmdTab[BX]  ; (CH) = ReqPkt func, (CL) = error
        pop     bx

        MOV     ES:[BX].PktLen,AH       ; set packet length
        MOV     ES:[BX].PktCmd,CH       ; set function code

;       interim character support.  Set the data byte to proper interim mode

        CMP     [InterCon],InterimMode  ; interim character support on?
        JNE     short iof30             ; no, skip interim code
        SaveReg <CX>
        MOV     CL,InterimMode
        CMP     CH,CMDInput
        JE      short iof25
        CMP     CH,CMDNDR
        JE      short iof25
        MOV     CL,[SaveCurFlg]
        CMP     CH,CMDOutput
        JNE     short iof20

;       test for output of interim character to non console

        TEST    byte ptr ES:[DI.rmdi_flags], RMDI_CON_OUT ; going to console?
        JNZ     short iof25             ; yes, jump down
        CMP     CL,NoCursorAdv          ; printing interim
        JNZ     short iof25             ; no, jump down
        POP     CX                      ; no, don't do I/O
        JMP     short iof50

iof20:  MOV     CL,NonInterimMode       ; use default value
iof25:  MOV     ES:[BX].IOmedia,CL      ; set byte for interim support
        RestoreReg <CX>
iof30:  PUSH    DS
        LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
        ASSUME  DS:NOTHING
        CALL    DevIOCall2              ; call device
        POP     DS
        ASSUME  DS:V86GROUP
        MOV     AX,ES:[BX].PktStatus    ; (AX) = return status
        TEST    AX,STERR
        JNZ     short iof55             ; device had error

;*  I/O successful
;
;       Set the InterimChar flag

iof35:
        mov     [InterimChar], FALSE    ; assume false
        .if     <[InterCon] eq InterimMode> and
                                        ; not interim mode, continue below
        .if     <BIT AX and STINTER>    ; did device driver return interim?
          mov     [InterimChar], TRUE   ; yes
        .endif

        .if     <CH eq CMDNDR>          ; not non-destructive read command
          mov     al,es:[bx].NDRbyte
          mov     DevIObuf,ax
        .endif

;*  Set flags to return status
        mov     ah,BYTE PTR ES:[BX].PktStatus+1 ; (AH) = low byte of status
        not     ah                      ; zero = busy, not zero = ready
        and     ah,STBUI shr 8          ; carry cleared

iof50:  POP     DS
        ASSUME  DS:NOTHING
        POPA
        MOV     AX,DevIObuf             ; (AX) = return value
        LeaveProc
        RET

;*  Error from device
;
iof55:
        LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
        PUSH    DI
        MOV     DI,AX                   ; (DI) = device error code
        MOV     AH,CL                   ; (AH) = allowed actions
        CALL    CharHard
        POP     DI
        CMP     AL,1                    ; retry?
        JA      short iof50             ; abort or fail; return error
        JNE     short iof60             ; no, must be ignore

        POP     DS
        ASSUME  DS:NOTHING
        POPA
        JMP     iof5                    ; retry operation

;*  Know user must have wanted ignore.  Make sure device shows ready
;   so that DOS doesn't get caught in a status loop when user simply wants
;   to ignore the error.

iof60:  AND     BYTE PTR ES:[BX].PktStatus+1, NOT (STBUI SHR 8)
        JMP     iof35

EndProc IOFunc

BREAK <IOFuncToFile - Do Function 1-12 I/O to a file>
;***    IOFuncToFile - Do Function 1-12 I/O to a file
;
;       One of several single-character I/O functions is performed on a
;       device or file.  This function is used by the CP/M compatible I/O
;       functions, the IOCTL function and the ^C checking routines.
;
;       ENTRY:  (ES) = fake handle
;               (AH) = function code
;                      = 0 Input
;                      = 1 Input Status
;                      = 2 Output
;                      = 3 Output Status
;                      = 4 Flush
;                      = 5 Output String
;               If single character output,
;                   (AL) = character if output character
;               If string output,
;                   (DS:DX) = string
;                      (CX) = length
;
;       EXIT:   Carry set, error
;               Carry clear, operation successful
;                   Input function
;                       (AL) = character
;                   Status request
;                       zero set, not ready
;                       zero reset, ready
;                           (AL) = character for input status
;                   File request
;                       Input Status
;                           Gets character but restores position
;                           Zero set on EOF
;                       Input
;                           Gets character advances position
;                           Returns ^Z on EOF
;                       Output Status
;                           Always ready
;
;       USES:   AX

        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure IOFuncToFile,NEAR,LOCAL

LocalVar  ftf_Buffer,WORD               ; file I/O buffer
LocalVar  ulFilePos,DWORD               ; saved File position

        EnterProc <ax>
        .if     <ah eq IO_INPUT_FLUSH> or       ;=4
        .if     <ah eq IO_OUTPUT_STATUS>        ;=3
          or      ah,ah
          .ERRE   IO_INPUT_FLUSH
          .ERRE   IO_OUTPUT_STATUS
          ;NZ... always ready
          jmp     short ftfx
        .endif

        SaveReg <bx,cx,dx,ds>
        mov     bx,es                   ; (BX) = handle
        .if     <ah ne IO_OUTPUT_STRING>        ;=5

          mov     cx,1                    ; (CX) = count of chars for I/O
          push    ss
          pop     ds
          lea     dx,[ftf_Buffer]         ; (DS:DX) = I/O buffer

          .if     <ah ne IO_OUTPUT>             ;=2

            .if     <ah eq IO_INPUT>            ;=0
              call    FileInput             ; input
              jmp     short ftf30
            .endif

                                                ;=1
            ; This is an input_status request.  Read the byte without
            ; changing the current file position.

            SaveReg <cx,dx>
            xor     cx,cx
            xor     dx,dx
            mov     al,1                    ; (AL) = relative to current position
            SVC     SVC_DEMCHGFILEPTR       ; determine current position
            jc      short ftfie
            mov     ulFilePos.hiword,dx
            mov     ulFilePos.loword,ax
            RestoreReg <dx,cx>
            call    FileInput
            pushf                           ; save Zero flag, it's the RC
            push    ax
            mov     cx,ulFilePos.hiword
            mov     dx,ulFilePos.loword
            mov     al,0                    ; absolute position
            SVC     SVC_DEMCHGFILEPTR       ; restore file position
            jc      short ftfie
            pop     ax
            popf                            ; restore Zero flag
            jmp     short ftf30
          .endif
        .endif

        push    ax                                                      ;BN011;
        SVC     SVC_DEMWRITE            ; output
        pop     ax                                                      ;BN011;
        jc      short ftfie
ftf30:
        RestoreReg <ds,dx,cx,bx>
ftfx:
        LeaveProc
        ret

ftfie:
        InternalError <DosKrnl: IOFuncToFile>
EndProc   IOFuncToFile

BREAK <FileInput - Do read one byte from file>
;***    FileInput - Do read one byte from file
;
;       ENTRY:  (BX)    = real file handle
;               (CX)    = 1
;               (DS:DX) = input buffer (one byte in size)
;
;       EXIT:   Zero set, at end of file
;                   (AL) = ^Z
;               Zero clear, not at end of file
;                   (AL) = byte from file
;
;       USES:   AX
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure FileInput,near,local
        SVC     SVC_DEMREAD
        .if     nc
          or      ax,ax                   ; check EOF
          mov     ax,[ftf_Buffer]         ; get byte from trans addr
          .if     z
            mov     al,1Ah                ; ^Z if no bytes
          .endif
          ret
        .endif
        InternalError <DosKrnl: FileInput>
EndProc   FileInput

BREAK <RMDevRead - Read from device in Real Mode>
;***    RMDevRead - Read from device in Real Mode>
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = input buffer to fill
;               (CX)    = length of input buffer
;
;       EXIT:   Carry Set, read failed
;                   (AX) = error code
;               Carry Clear, read succeeded
;                   (AX) = number of bytes read
;
;       USES:   AX,BX,CX,DX,SI,DI,ES,DS

        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

Procedure RMDevRead,near
        MOV     BX,ES:[DI].rmdi_flags   ; (BX) = device flags
.erre   RMDI_EOF LE 0FFh
        TEST    BL,RMDI_EOF             ; NOTE: bit is 0 if at EOF!
        JZ      short drd10             ; at EOF
.erre   RMDI_NULL LE 0FFh
        TEST    BL,RMDI_NULL
        JNZ     short drd10             ; NUL device
        JCXZ    short drd10             ; no bytes to read
        SaveReg <BP,DI,ES>
.erre   RMDI_RAW LE 0FFh
        TEST    BL,RMDI_RAW
        JZ      short drd20             ; not in raw mode

        CALL    RMDevRawRead            ; read device in raw mode
        JMP     SHORT drd40

drd10:  XOR     AX,AX                   ; (AX) = number of bytes read
        JMP     short drdx

drd20:  TEST    BL,RMDI_CON_IN
        JZ      short drd30             ; not the console

        CALL    RMDevReadConsole        ; read console in cooked mode
        JMP     SHORT drd40

drd30:  CALL    RMDevCookedRead         ; read device in cooked mode
drd40:  RestoreReg <ES,DI,BP>
        OR      AX,AX
        JNZ     short drdx              ; we are not at EOF of device

; Flag that we have reached EOF for the device
; NOTE that DEVID_DEVICE_EOF flag bit is reset if we are at EOF

        AND     BYTE PTR ES:[DI].rmdi_flags,NOT RMDI_EOF
drdx:   RET
EndProc   RMDevRead

BREAK <RMDevReadConsole - Read from console in Real Mode>
;***    RMDevReadConsole - Read from console in Real Mode>
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = input buffer
;               (CX)    = number of bytes to read
;
;       EXIT:   Carry Set, read failed
;                   (AX) = error code
;               Carry Clear, read succeeded
;                   (AX) = number of bytes read
;
;       USES:   All

        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

Procedure RMDevReadConsole,near
        SaveReg <DX>                    ; save initial buffer offset
        CALL    SwapCon
        MOV     DI,DX
        MOV     AX,DS
        MOV     ES,AX                   ; (ES:DI) = user input buffer
        push    ss
        pop     ds
        assume  ds:V86GROUP
        MOV     SI,ContPos              ; (SI) = ptr to next char in ConBuf
        OR      SI,SI
        JNZ     short drc30             ; Get character from buffer
        .if     <<byte ptr [ConBuf]> ne 80h>    ; Need to read another line
          mov     word ptr [ConBuf],0FF80H; Set up 128-byte buf with no template
        .endif
        SaveReg <CX,DI,ES,DS>
        mov     dx,offset V86GROUP:ConBuf
        call    $Std_Con_String_Input   ; Get input buffer
        RestoreReg <DS,ES,DI,CX>
        mov     si,offset V86GROUP:ConBuf+2
        cmp     byte ptr [SI],1Ah       ; Check for Ctrl-Z in first character
        JNZ     short drc30             ; Not at end of file

        MOV     AL,1AH
        STOSB                           ; Store character
        DEC     DI
        MOV     AL,c_LF
        CALL    OUTT                    ; Send linefeed
        XOR     SI,SI                   ; Set zero flag -- at end of file

drc20:  CALL    SwapBack
        MOV     ContPos,SI              ; Save updated buffer position
        JMP     short drcx

drc30:  LODSB                           ; Copy character to user's buffer
        STOSB
        CMP     AL,c_CR                 ; Check for carriage return
        JNZ     short drc40
        MOV     BYTE PTR [SI],c_LF      ; Put LineFeed in input buffer

drc40:  CMP     AL,c_LF
        LOOPNE  drc30                   ; Buffer not full, and not LF char
        JNE     short drc20             ; Not at end of line, but buffer full

        XOR     SI,SI                   ; Cause a new buffer to be read
        CALL    OUTT                    ; Transmit linefeed
        OR      AL,1                    ; Clear zero flag -- not end of file
        JMP     short drc20

drcx:   RestoreReg <DX>                 ; (DX) = initial buffer offset
        SUB     DI,DX                   ; (DI) = number of bytes read
        MOV     AX,DI                   ; (AX) = number of bytes read
        RET
EndProc   RMDevReadConsole

BREAK <RMDevRawRead - Read in raw mode from device in Real Mode>
;***    RMDevRawRead - Read in raw mode from device in Real Mode
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = input buffer
;               (CX)    = number of bytes to read
;
;       EXIT:   Carry Set, read failed
;                   (AX) = error code
;               Carry Clear, read succeeded
;                   (AX) = number of bytes read
;
;       USES:   All

        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevRawRead,near

LocalVar  drr_pBuffer,DWORD             ; transfer address
LocalVar  drr_Count,WORD                ; current number of bytes to read
LocalVar  drr_Total,WORD                ; total number of bytes to read

        EnterProc <ds,dx,cx,cx>         ; pBuffer, Count
        MOV     BX,OFFSET V86GROUP:RMDevReqPkt ; (ES:BX) = request packet

drr10:  MOV     ES:[BX].PktCmd,CMDINPUT
        MOV     ES:[BX].PktLen,DRDWRHL
        MOV     ES:[BX].IOpData.Offst,DX
        MOV     ES:[BX].IOpData.Segmt,DS
        MOV     ES:[BX].IOcount,CX
        MOV     ES:[BX].IOmedia,0
        MOV     ES:[BX].PktStatus,0
        LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
        CALL    DevIOCall2

        MOV     SI,ES:[BX].IOcount      ; (SI) = number transferred
        ADD     drr_pBufferL,SI         ; (drr_pBufferL) = addr of next input
        SUB     drr_Count,SI            ; (drr_Count) = number remaining

        MOV     AX,ES:[BX].ReqStat
        TEST    AX,STERR
        JZ      short drr30             ; no errors (CF clear)

        LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
        PUSH    DI
        MOV     DI,AX                   ; (DI) = device error
        MOV     AH,86H                  ; read error
        CALL    CharHard
        POP     DI
        CMP     AL,1
        JA      short drr20             ; abort or fail, return error
        JNE     short drr30             ; ignore

        LDS     DX,drr_pBuffer          ; restore parms for retry
        MOV     CX,drr_Count
        JMP     drr10                   ; retry

;*  We have encountered a device-driver error.  We have informed the user of
;   it and he has said for us to fail the system call.

drr20:  MOV     AX,ERROR_ACCESS_DENIED
        STC
        JMP     SHORT drrx

drr30:  MOV     AX,drr_Total
        SUB     AX,drr_Count            ; (AX) = amount transferred

drrx:   LeaveProc
        RET
EndProc   RMDevRawRead

BREAK <RMDevCookedRead - Read in cooked mode from device in Real Mode>
;***    RMDevCookedRead - Read in cooked mode from device in Real Mode
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = input buffer
;               (CX)    = number of bytes to read
;
;       EXIT:   Carry Set, read failed
;                   (AX) = error code
;               Carry Clear, read succeeded
;                   (AX) = number of bytes read
;
;       USES:   All
;
;       NOTES:  We are going to do a cooked read on some character device.
;               There is a problem here, what does the data look like?  Is it
;               a terminal device, line CR line CR line CR, or is it file
;               data, line CR LF line CR LF?  Does it have a ^Z at the end
;               which is data, or is the ^Z not data?
;
;               In any event we're going to do this:  Read in pieces up to
;               CR (CRs included in data) or ^Z (^Z included in data).  This
;               "simulates" the way CON works in cooked mode reading one line
;               at a time.  With file data, however, the lines will look
;               like, LF line CR. This is a little weird.

        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

Procedure RMDevCookedRead,near

LocalVar  rmdcr_Total,WORD              ; Total number of bytes to write

        EnterProc <CX>
        MOV     BX,OFFSET V86GROUP:RMDevReqPkt ; (ES:BX) = request packet
        SaveReg <DX>                    ; save offset for count computation

        MOV     ES:[BX].PktCmd,CMDINPUT
        MOV     ES:[BX].PktLen,DRDWRHL
        MOV     ES:[BX].IOpData.Offst,DX
        MOV     ES:[BX].IOpData.Segmt,DS
        MOV     ES:[BX].IOmedia,0

dcr10:  MOV     ES:[BX].IOcount,1       ; read one character at a time
        MOV     ES:[BX].PktStatus,0

        CALL    DskStatChk
        LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
        CALL    DevIOCall2
        MOV     AX,ES:[BX].ReqStat      ; (AX) = status of device call
        TEST    AX,STERR
        JZ      short dcr40             ; no error (CF clear)

dcr20:  LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
        PUSH    DI
        MOV     DI,AX                   ; (DI) = device error
        MOV     AH,86H                  ; read error
        CALL    CharHard
        POP     DI
        CMP     AL,1
        JA      short dcr30             ; abort or Fail, return error
        JE      short dcr10             ; retry
        XOR     AL,AL                   ; pick some random character
        JMP     SHORT dcr50             ; ignore

;*  We have encountered a device-driver error.  We have informed the user of
;   it and he has said for us to fail the system call.

dcr30:  MOV     AX,ERROR_ACCESS_DENIED
        STC
        JMP     SHORT dcr60

dcr40:  CMP     ES:[BX].IOCount,1
        CLC                             ; indicate success
        JNE     short dcr60             ; didn't get a character (at EOF?)

        LDS     SI,DS:[BX].IOpData      ; (DS:SI) = ptr to character just read
        LODSB                           ; (AL) = character we just read

dcr50:  INC     ES:[BX].IOpData.offst   ; point to next byte in user buffer
        CMP     AL,1AH                  ; was character a ^Z?
        JE      short dcr60             ; yes, done
        CMP     AL,c_CR                 ; CR?
        LOOPNE  dcr10                   ; loop if no, else done (Carry clear)

dcr60:  RestoreReg <DX>                 ; (DX) = initial buffer offset
        JC      short dcrx              ; error

        SUB     DX,ES:[BX].IOpData.Offst; (DX) = -(count of bytes read)
        MOV     AX,DX                   ; (AX) = -(count of bytes read)
        NEG     AX                      ; (AX) = count of bytes read
        CLC                             ; indicate success
dcrx:   LeaveProc
        RET
EndProc   RMDevCookedRead

BREAK <RMDevWrite - Write to device in Real Mode>
;***    RMDevWrite - Write to device in Real Mode>
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = buffer to write
;               (CX)    = length of buffer
;
;       EXIT:   Carry Set, Write failed
;                   (AX) = error code
;               Carry Clear, Write succeeded
;                   (AX) = number of bytes written
;
;       USES:   All but BP

        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevWrite,near
        push    bp

;*  Mark device not at end of file (Note negated logic)

        or      byte ptr es:[di].rmdi_flags,RMDI_EOF
        mov     bx,es:[di].rmdi_Flags   ; (BX) = device flags

        .if     <BIT BL nand RMDI_NULL> and  ; NUL device
        .if     ncxz                         ; No bytes to write
          TEST    BL,DEVID_DEVICE_RAW
          JZ      SHORT dwr20             ; Not in raw mode

          call    RMDevRawWrite           ; Write device in raw mode
          jmp     short dwrx
        .endif

        mov     ax,cx                   ; (AX) = number of bytes written
        jmp     short dwrx
dwr20:
        .if     <BIT BL and DEVID_DEVICE_CON_OUT>  ; Not the console
          call    RMDevWriteConsole       ; Write console in cooked mode
          jmp     short dwrx
        .endif
        call    RMDevCookedWrite        ; Write device in cooked mode
dwrx:
        pop     bp
        ret
EndProc   RMDevWrite

BREAK <RMDevWriteConsole - Write to console in Real Mode>
;***    RMDevWriteConsole - Write to console in Real Mode>
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = buffer
;               (CX)    = number of bytes to write
;
;       EXIT:   Carry Set, Write failed
;                   (AX) = error code
;               Carry Clear, Write succeeded
;                   (AX) = number of bytes written
;
;       USES:   AX,BX,CX,DX,SI,DS

        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

Procedure RMDevWriteConsole,near
        call    SwapCon
        mov     ax,ds
        mov     es,ax                   ; (ES:DX) = pointer to string
        assume  es:nothing

;*  Find possible CTRL-Z in the user's string

        mov     di,dx                   ; (ES:DI) = pointer to string
        mov     bx,cx                   ; save passed byte count
        mov     al,'Z'-'@'              ; (AL) = CTRL-Z
        repne   scasb
        .if     eq                      ; no CTRL-Z in string, CX = 0
          inc     cx                    ; adjust for having passed CTRL-Z
        .endif
        neg     cx                      ; (CX) = -(count remaining)
        add     cx,bx                   ; (CX) = original - (count remaining)
        push    cx                      ; (CX) = amount to actually write
        call    Cons_String_Output      ; write the string
        pop     ax                      ; (AX) = bytes written
        push    ss
        pop     ds
        assume  ds:V86GROUP
        call    SwapBack
        clc                             ; Indicate success
        ret
EndProc   RMDevWriteConsole

BREAK <RMDevRawWrite - Write in raw mode from device in Real Mode>
;***    RMDevRawWrite - Write in raw mode from device in Real Mode
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = input buffer
;               (CX)    = number of bytes to Write
;
;       EXIT:   Carry Set, Write failed
;                   (AX) = error code
;               Carry Clear, Write succeeded
;                   (AX) = number of bytes Write
;
;       USES:   All

        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

Procedure RMDevRawWrite,near

LocalVar  drw_pBuffer,DWORD             ; transfer address
LocalVar  drw_Count,WORD                ; current number of bytes to write
LocalVar  drw_Total,WORD                ; total number of bytes to write

        EnterProc <DS,DX,CX,CX>         ; pBuffer, Count
        MOV     BX,OFFSET V86GROUP:RMDevReqPkt ; (ES:BX) = request packet
        .repeat
          MOV     ES:[BX].PktCmd,CMDOUTPUT
          MOV     ES:[BX].PktLen,DRDWRHL
          MOV     ES:[BX].IOpData.Offst,DX
          MOV     ES:[BX].IOpData.Segmt,DS
          MOV     ES:[BX].IOcount,CX
          MOV     ES:[BX].IOmedia,0
          MOV     ES:[BX].PktStatus,0
          LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
          CALL    DevIOCall2

          MOV     SI,ES:[BX].IOcount    ; (SI) = number transferred
          ADD     drw_pBufferL,SI       ; (drw_pBufferL) = addr of next output
          SUB     drw_Count,SI          ; (drw_Count) = number remaining

          mov     ax,ES:[BX].ReqStat
          test    AX,STERR
          jz      short drw30           ; no errors (CF clear)

          LDS     SI,ES:[DI].rmdi_devptr  ; (DS:SI) = device pointer
          push    di
          mov     di,ax                 ; (DI) = device error
          mov     ah,87h                ; write error
          call    CharHard
          pop     di
          .leave  <AL a 1>              ; abort or fail, return error
          JNE     short drw30           ; ignore

          LDS     DX,drw_pBuffer          ; restore parms for retry
          mov     cx,drw_Count
        .until                          ; retry

;*  We have encountered a device-driver error.  We have informed the user of
;   it and he has said for us to fail the system call.

        MOV     AX,ERROR_ACCESS_DENIED
        STC
        JMP     SHORT drwx

drw30:  MOV     AX,drw_Total
        SUB     AX,drw_Count            ; (AX) = amount transferred

drwx:   LeaveProc
        ret
EndProc   RMDevRawWrite

BREAK <RMDevCookedWrite - Write in cooked mode to device in Real Mode>
;***    RMDevCookedWrite - Write in cooked mode to device in Real Mode
;
;       PSEUDO CODE
;
;       truncate buffer at EOF.
;       if (Dev_FuncLev < v1.0 OS/2)
;           cbSend = 1
;       else
;           cbSend = DCW_DEFAULT_CBSEND
;       while (still characters to send) {
;           BytesToSend = MIN (bytes left, cbSend)
;           rc = Deviocall2(buffer,BytesToSend)
;           if (rc != NO_ERROR)
;               if (rc == ERROR_I24_CHAR_CALL_INTERRUPTED)
;                   return(error)
;               else
;                   rc = RMHardChar()
;                   if (rc == IGNORE)
;                       pretend the buffer was successfully sent
;                   else
;                       if (rc != RETRY)
;                           return(error)
;           adjust the count remaining and the buffer position for
;           the number of bytes successfully sent.
;       }
;       return ( number of bytes sent )
;
;       ENTRY:  (ES:DI) = RMDevInfo entry
;               (DS:DX) = string
;               (CX)    = number of bytes to write
;
;       EXIT:   Carry Set, write failed
;                   (AX) = error code
;               Carry Clear, write succeeded
;                   (AX) = number of bytes written
;
;       USES:   All

        ASSUME  DS:NOTHING,ES:V86GROUP,SS:NOTHING

DCW_DEFAULT_CBSEND      EQU 128         ; default number of bytes to send

Procedure RMDevCookedWrite,near

LocalVar  dcw_Offset,WORD               ; original offset of the send buffer
LocalVar  dcw_cbSend,WORD               ; number of bytes to send each iteration
LocalVar  dcw_Count,WORD                ; number of bytes left to write
LocalVar  dcw_BytesToSend,WORD          ; bytes to send this iteration

        EnterProc <dx,DCW_DEFAULT_CBSEND>
        mov     bx,offset V86GROUP:RMDevReqPkt ; (ES:BX) = request packet
        mov     es:[bx].PktCmd,CMDOUTPUT
        mov     es:[bx].PktLen,16h
        mov     es:[bx].IOpData.Offst,dx
        mov     es:[bx].IOpData.Segmt,ds
        mov     es:[bx].IOmedia,0
;
; DEVLEV_1 was introduced for OS/2 1.x bimodal device drivers, and I cannot
; find a discussion anywhere of how this relates to the ability to process
; multi-byte packets.  The two seem unrelated, unless it was just a way to call
; DskStatChk more frequently.  Furthermore, a DEVLEV_1 driver would be loaded
; in protected-mode space, not V86 space, so I don't see how this test would
; ever succeed in current architecture.  I have replaced it with a test for
; whether the driver is part of our code segment, since we know our drivers can
; support multi-byte packets (JeffPar 21-Aug-90)
;
       IFDEF BOGUS
        SaveReg <bx>
        lds     si,es:[di].rmdi_devPtr  ; get device header
        mov     bx,ds:[si].SDevAtt      ; get device attributes
        Dev_FuncLev DEVLEV_1,bx         ; 'C' if less than DevLev_1
        RestoreReg  <bx>
        jnc     short dcw10             ; level >= DEVLEV_1, can send mult bytes
       ELSE
        mov     ax,cs                   ; HACK: is it our driver?
        cmp     ax,es:[di].rmdi_devPtr.Segmt
        je      short dcw10             ; yes, so assume we can send mult bytes
       ENDIF
        mov     dcw_cbSend,1            ; old device, send 1 char at a time

dcw10:  or      cx,cx                   ; Is the count to transfer = 0?
        jz      short dcw90             ;  YES! leave
        mov     dcw_Count,cx            ; save the count to transfer

        SaveReg <es,di>
        les     di,es:[bx].IOpData
        mov     al,eof                  ; AX = EOF = CTRL-Z
        repne   scasb                   ; is EOF in this data packet?
        RestoreReg  <di,es>
        jnz     short dcw20             ;  NO! do not truncate the buffer
        inc     cx
        sub     dcw_Count,cx            ; truncate the buffer to the EOF

dcw20:  mov     cx,dcw_cbSend           ; assume the default number to send
        cmp     cx,dcw_Count            ; are there fewer bytes in the buffer?
        jbe     short dcw30             ;   No, send only the default number.
        mov     cx,dcw_Count            ; write the entire buffer

dcw30:  jcxz    dcw90                   ; jump if the buffer is empty

        mov     dcw_BytesToSend,cx
        mov     es:[bx].IOcount,cx
        mov     es:[bx].PktStatus,0
        call    DskStatChk
        lds     si,es:[di].rmdi_devptr  ; (DS:SI) = device pointer
        call    DevIOCall2
        mov     ax,es:[bx].ReqStat      ; (AX) = status of device call
        .if     <BIT AX and STERR>
          push    di
          mov     di,ax                 ; (DI) = device error
          mov     ah,87h                ; write error
          call    CharHard
          pop     di
          cmp     al,1
          ja      short dcw80           ; fail or abort
          .if     ne                    ; retry
            mov     ax,dcw_BytesToSend  ; ignore: simulate a successful send
            mov     es:[bx].IOCount,ax
          .endif
        .endif
        mov     ax,es:[bx].IOCount      ; get bytes written             PTM3333
        or      ax,ax                   ;                               PTM3333
        jz      short dcw90             ; exit if none written          PTM3333
        sub     dcw_Count,ax            ; adjust for bytes written      PTM3333
        add     ax,es:[bx].IOpData.Offst
        mov     es:[bx].IOpData.Offst,ax
        jmp     short dcw20             ; send the rest of the buffer

dcw80:  mov     ax,ERROR_ACCESS_DENIED
        stc
        jmp     short dcwx

dcw90:  mov     dx,dcw_Offset           ; (dx) = original offset of buffer
        sub     dx,es:[bx].IOpData.Offst; (DX) = -(count of bytes written)
        mov     ax,dx                   ; (AX) = -(count of bytes written)
        neg     ax                      ; (AX) = count of bytes written
        clc                             ; indicate success
dcwx:   LeaveProc
        ret
EndProc   RMDevCookedWrite


Break <RMDevGIOCTL - Send a generic IOCTL packet to a device>

;***LP  RMDevGIOCTL - Send a generic IOCTL packet to a device>
;
;       Call DevIOCall2 with a Generic IOCTL packet
;
;       ENTRY
;           CH
;               category
;           CL
;               function within category
;           DS:SI
;               Device header
;           ES:DX
;               Data packet
;           AX
;               Client's SI
;           DI
;               Client's DI
;
;       EXIT
;           SUCCESS
;               CF = 0
;           FAILURE
;               CF = 1
;               AX = error code
;
;       CONTEXT
;           Runs in V86 mode
;
;       STEPS
;
;       NOTES
;           None
;
;       WARNINGS
;           A bug in DOS 3.21 through 4.0 trashes SI with the contents
;           of DS on entry to $IOCTL, so we have to do the same here.
;
;       CALLS
;           DevIOCall2
;
;       USES
;           AX
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure RMDevGIOCTL
        test    ds:[si].SDevAtt,DEV_GIOCTL
        jz      short gioctl_invalid_function

        mov     bx,offset V86GROUP:RMDevReqPkt
        mov     cs:[bx].PktLen,LENGenIOCTL2+PktHeadSize ; fill in request packet =1Dh
        mov     cs:[bx].PktCmd,GENIOCTL                ;=13h    +02h
        mov     cs:[bx].GIOCategory,ch           ;B6   ;BN001;  +0Dh
        mov     cs:[bx].GIOFunction,cl                 ;BN001;  +0Eh


; Emulate bug in 3.2 to 4.0: the Reg_SI field is set with the user's DS
        mov     cs:[bx].Reg_SI,ax                      ;        +0Fh
        mov     cs:[bx].Reg_DI,di                      ;        +11h
;BD009; mov     cs:[bx].GIOParaPack.Offst,dx           ;BN001;  +0Fh
;BD009; mov     cs:[bx].GIOParaPack.Segmt,es           ;BN001;  +11h
        mov     cs:[bx].GIODataPack.Offst,dx           ;BN005;  +13h
        mov     cs:[bx].GIODataPack.Segmt,es           ;BN005;  +15h

;What about ???
;DW+17h  System file number
;DW+19h  Parameter buffer length
;DW+1Bh  Data buffer length

Entry   gioctl_do_io
        push    cs
        pop     es
        xor     ax,ax
        mov     es:[bx].PktUnit,al
        mov     es:[bx].PktStatus,ax
        mov     es:[bx].PktDOSLink.Segmt,ax
        mov     es:[bx].PktDOSLink.Offst,ax
        mov     es:[bx].PktDevLink.Segmt,ax
        mov     es:[bx].PktDevLink.Offst,ax

        call    DevIOCall2              ; do the IOCTL
        .if     <BIT <es:[bx].PktStatus> nand STERR>
          mov     ax,es:[bx].IOcount      ; (AX) = actual bytes transferred
          clc
          ret
        .endif
        mov     di,es:[bx].PktStatus
        and     di,STECODE
        mov     ax,di
        invoke  SET_I24_EXTENDED_ERROR
        mov     ax,cs:[ExtErr]
        stc
        ret

Entry   gioctl_invalid_function
        mov     ax,ERROR_INVALID_FUNCTION
        stc
        ret
EndProc   RMDevGIOCTL

Break <DEVIOCALL, DEVIOCALL2 - CALL A DEVICE>

; Inputs:
;       DS:SI Points to DevInfo Header
;       ES:BX Points to request data
; Function:
;       Call the device
; Outputs:
;       DS:SI -> Device driver
; DS:SI,AX destroyed, others preserved
;
        ASSUME  DS:NOTHING,ES:NOTHING,SS:NOTHING

Procedure DEVIOCALL,NEAR
        lds     si,[si.rmdi_devPtr]
Entry   DEVIOCALL2
        EnterCrit critDevice
        mov     ax,[si.SDevStrat]
        mov     word ptr CALLDEVAD,ax
        mov     word ptr CALLDEVAD+2,ds
        call    CALLDEVAD
        mov     ax,[si.SDevInt]
        mov     word ptr CALLDEVAD,ax
        call    CALLDEVAD
        LeaveCrit critDevice
        ret
EndProc  DEVIOCALL

V86CODE ENDS

        END
