title 'F_DOS Character device I/O'
;    File              : $CDEVIO.A86$
;
;    Description       :
;
;    Original Author   : DIGITAL RESEARCH
;
;    Last Edited By    : $CALDERA$
;
;-----------------------------------------------------------------------;
;    Copyright Work of Caldera, Inc. All Rights Reserved.
;      
;    THIS WORK IS A COPYRIGHT WORK AND CONTAINS CONFIDENTIAL,
;    PROPRIETARY AND TRADE SECRET INFORMATION OF CALDERA, INC.
;    ACCESS TO THIS WORK IS RESTRICTED TO (I) CALDERA, INC. EMPLOYEES
;    WHO HAVE A NEED TO KNOW TO PERFORM TASKS WITHIN THE SCOPE OF
;    THEIR ASSIGNMENTS AND (II) ENTITIES OTHER THAN CALDERA, INC. WHO
;    HAVE ACCEPTED THE CALDERA OPENDOS SOURCE LICENSE OR OTHER CALDERA LICENSE
;    AGREEMENTS. EXCEPT UNDER THE EXPRESS TERMS OF THE CALDERA LICENSE
;    AGREEMENT NO PART OF THIS WORK MAY BE USED, PRACTICED, PERFORMED,
;    COPIED, DISTRIBUTED, REVISED, MODIFIED, TRANSLATED, ABRIDGED,
;    CONDENSED, EXPANDED, COLLECTED, COMPILED, LINKED, RECAST,
;    TRANSFORMED OR ADAPTED WITHOUT THE PRIOR WRITTEN CONSENT OF
;    CALDERA, INC. ANY USE OR EXPLOITATION OF THIS WORK WITHOUT
;    AUTHORIZATION COULD SUBJECT THE PERPETRATOR TO CRIMINAL AND
;    CIVIL LIABILITY.
;-----------------------------------------------------------------------;
;
;    *** Current Edit History ***
;    *** End of Current Edit History ***
;    $Log: $
;    CDEVIO.A86 1.18 94/12/21 11:16:29
;    Changed dev_dup to refuse from duplicating remote devices rather
;    than refuse from duplicating FCBs 
;    Made open_dev return IFN rather than XFN for FCB opens 
;    Made open_dev put the current time/date into DHNDL_TIME/DHNDL_DATE
;    CDEVIO.A86 1.16 94/10/07 09:00:18
;    Added patch 004 as source fix. Changed request header length for character
;    device to 16h. Used RH4_CDEV_LEN instead of RH4_LEN.
;    CDEVIO.A86 1.15 93/12/10 00:09:17
;    Move non-inherited bit to correct place in file handle
;    CDEVIO.A86 1.14 93/11/26 16:17:14
;    Update char_error so ES:SI -> device driver header itself;
;    ENDLOG

	eject ! include i:psp.def
	eject ! include i:modfunc.def
	eject ! include i:fdos.equ
	eject ! include i:mserror.equ
	eject ! include i:doshndl.def
	eject ! include i:driver.equ
	eject ! include rh.equ

BDOS_DATA	dseg

	extrn	fdos_buf:byte		; caveat: in PCMODE data segment
	extrn	fdos_pb:word
	extrn	fdos_ret:word

;
;	Critical Error responses from the default INT 24 handler and
;	the DO_INT24 routine.
;
ERR_IGNORE	equ	0		; Ignore Error
ERR_RETRY	equ	1		; Retry the Operation
ERR_ABORT	equ	2		; Terminate the Process
ERR_FAIL	equ	3		; Fail Function

BDOS_CODE	cseg

	public	open_dev
	public	close_dev
	public	dup_dev
	public	read_dev
	public	write_dev
	public	first_dev
	public	ioc6_dev
	public	ioc7_dev

	extrn	cooked_write:near	; write to cooked console
	extrn	read_line:near		; read edited line
	extrn	break_check:near	; look for CTRL C
	extrn	char_error:near		; generate Int 24

	extrn	current_dsk2al:near	; get default drive in AL
	extrn	device_driver:near
	extrn	alloc_dhndl:near	; find free DHNDL_ structure
	extrn	alloc_xfn:near
	extrn	les_di_dmaptr:near
	extrn	release_handle:near
	extrn	get_xftptr:near
	extrn	timestamp_dhndl:near


;			   Character device functions
;			   ==========================

open_dev:
;--------
;	Entry:	ES:BX -> character device header

;	Note:	We own the MXdisk here, fdos_pb -> parameter data

	push es ! push bx		; save device driver address
	call	alloc_xfn		; DI = XFN
	push	di			; save XFN
	call	alloc_dhndl		; ES:BX -> DHNDL, AX = IFN
	pop	dx			; DX = XFN
	xchg	ax,si			; SI = IFN
	xor	ax,ax
	lea	di,DHNDL_DATRB[bx]	; point at start of area to fill
	mov	cx,DHNDL_UID-DHNDL_DATRB
	rep	stosb
	mov	es:DHNDL_SHARE[bx],ax	; also zap share word	
	pop	es:DHNDL_DEVOFF[bx]
	pop	es:DHNDL_DEVSEG[bx]	; save device driver address

	push si ! push dx		; save IFN/XFN

	mov	es:DHNDL_COUNT[bx],1
	push	ds
	mov	ax,fdos_pb+6		; AX = open mode
	mov	cx,DHAT_DEV+DHAT_CLEAN+DHAT_TIMEOK
	lds	si,es:DHNDL_DEVPTR[bx]	; DS:SI -> device driver
	or	cl,ds:byte ptr DH_ATTRIB[si]
					; get attrib from device driver
	test	al,DHM_LOCAL		; is it private ?
	 jz	open_dev10
	and	al,not DHM_LOCAL	; clear inherit bit
	or	ch,DHAT_LOCAL/256	; rememmber it's local
open_dev10:
	mov	es:DHNDL_MODE[bx],ax
	mov	es:DHNDL_WATTR[bx],cx
	lea	si,DH_NAME[si]		; copy the device name
	lea	di,DHNDL_NAME[bx]	;  from the device header
	mov	cx,8/WORD
	rep	movsw
	mov	al,' '
	mov	cl,3			; space the file extension
	rep	stosb
	pop	ds
	
	pop dx ! pop ax			; AX = IFN, DX = XFN

	push	es
	call	get_xftptr		; ES:DI -> PSP_XFTPTR for current_psp
	 jc	open_dev20		; no PSP, skip updating XFN
	add	di,dx			; ES:DI -> entry in PSP
	stosb				; update entry in PSP
	xchg	ax,dx
open_dev20:
	pop	es

	mov	fdos_ret,ax		; save XFN (IFN for FCB open) to return
	call	timestamp_dhndl
	or	es:DHNDL_ATTR[bx],DHAT_READY
;	jmp	open_dup_dev

open_dup_dev:
;------------
; On Entry:
;	ES:BX = DHNDL_
; On Exit:
;	None
;
	mov	al,CMD_DEVICE_OPEN
;	jmp	open_close_dev		; call device open routine

open_close_dev:
;--------------
; entry:	ES:BX = DHNDL_
;		AL = cmd_type (CMD_DEVICE_OPEN/CMD_DEVICE_CLOSE)
;
	push ds ! push es
	push bx ! push si
	lds	si,es:DHNDL_DEVPTR[bx]	; DS:SI -> device driver
	test	ds:DH_ATTRIB[si],DA_REMOVE
	 jz	ocdev1			 ; does the device support OPEN/CLOSE/RM
	sub	sp,RH13_LEN-2*word	; make space on stack for RH_
	push	ax			; RH_CMD = AL
	mov	ax,RH13_LEN
	push	ax			; RH_LEN = RH13_LEN
	push ss ! pop es
	mov	bx,sp			; ES:BX -> RH_
	call	device_driver		; call the device driver
	add	sp,RH13_LEN		; recover stack space
ocdev1:
	pop si ! pop bx
	pop es ! pop ds
	ret

dup_dev:
;-------
; On Entry:
;	ES:BX -> DHNDL_
; On Exit:
;	None
;	AX trashed
;
	mov	ax,es:DHNDL_WATTR[bx]
	test	al,DHAT_DEV
	 jz	dup_dev10		; skip if disk file
	test	ax,DHAT_REMOTE		; or remote
	 jz	open_dup_dev
dup_dev10:
	ret


close_dev:	; close character device handle
;---------
;	entry:	FDOS_PB+2 = user file handle
;		ES:BX = file handle
;	NOTE:	This is called with the MXdisk owned

	mov	al,CMD_DEVICE_CLOSE
	call	open_close_dev		; call device close routine
	call	release_handle		; release the XFN
	dec	es:DHNDL_COUNT[bx]	; one less XFN refers to this IFN
	ret


write_dev:	; write to character device handle
;---------
;	entry:	ES:BX -> DHNDL_ structure
;

	mov	cl,CMD_OUTPUT		; OUTPUT driver function
	or	es:DHNDL_ATTR[bx],DHAT_READY
	mov	al,es:DHNDL_ATTR[bx]	; get file info
	and	al,DHAT_BIN+DHAT_NUL+DHAT_CIN+DHAT_COT
	cmp	al,DHAT_CIN+DHAT_COT	; is it cooked console?
	 jne	inst_io			; no, device driver i/o
	mov	si,2[bp]		; SI -> parameter block
	mov	cx,ds:8[si]		; CX = string length
	 jcxz	write_dev20		; exit if nothing to write
	les	di,ds:4[si]		; ES:DI -> string to print
	mov	al,'Z'-40h		; we have to stop at ^Z
	repne	scasb			; scan for ^Z character
	 jne	write_dev10		; skip if ^Z not found
	inc	cx			; include ^Z in count of chars to skip
	sub	ds:8[si],cx		; subtract from total count
write_dev10:
	mov	bx,ds:2[si]		; BX = handle number
	mov	cx,ds:8[si]		; CX = string length
	mov	si,ds:4[si]		; ES:SI -> string to print
	call	cooked_write		; write w/ tab expansion & ^C checking
write_dev20:
	sub	bx,bx			; no errors
	ret				; return the result



read_dev:	; read to character device handle
;--------
;	entry:	ES:BX -> DHNDL_ structure

	mov	al,es:DHNDL_ATTR[bx]	; get the file info
	mov	ah,al			; save ioctl info
	and	al,DHAT_BIN+DHAT_CIN+DHAT_COT
	cmp	al,DHAT_CIN+DHAT_COT	; is it cooked console?
	 jne	rddev1			; skip if binary or not console
	jmp	read_con		; read from console handle
					; return the result
rddev1:
	test	ah,DHAT_READY		; previous EOF ?
	 jnz	rddev2			; yes we return now
	mov	di,2[bp]		; DI -> parameter block
	mov	ds:word ptr 8[di],0	; zero bytes xfered
	ret
rddev2:
	mov	cl,CMD_INPUT
inst_io:
; ES:BX = DHNDL_, CL = command
	sub	sp,RH4_CDEV_LEN		; make RH_ on stack
	mov	di,bx			; save address DHNDL_ in DI
	mov	bx,sp			; SS:BX -> request header

	mov	ds:RH_CMD[bx],cl
	mov	ds:RH_LEN[bx],RH4_CDEV_LEN
	mov	ds:RH_STATUS[bx],0	; status OK in case of zero chars
	mov	si,2[bp]		; DS:SI -> parameter block
	lea	si,4[si]		; point at buffer offset
	lodsw				; get buffer offset
; Normalising the address has been unnecessary so far
;	push	ax
;	and	ax,15			; normalise the address
;	pop	cx
;	shr	cx,1 ! shr cx,1
;	shr	cx,1 ! shr cx,1
	mov	ds:RH4_BUFOFF[bx],ax	; set buffer offset in request header
	lodsw				; get buffer segment
;	add	ax,cx			; add in normalised offset/16
	mov	ds:RH4_BUFSEG[bx],ax	; get buffer segment in request header
	lodsw				; get byte count
	xchg	ax,cx			; byte count in CX

; Parameter block created on stack at SS:BX and initialised for xfer
; ES:DI -> DHNDL_, CX = total number of bytes to xfer

inst_io20:
	mov	ds:RH4_COUNT[bx],cx	; try and do this many
	test	es:DHNDL_ATTR[di],DHAT_BIN+DHAT_NUL
					; are we in binary mode ?
	 jcxz	inst_io30		; return on zero length xfer
	 jnz	inst_io25		; binary, skip calling PCMODE
    mov ds:RH4_COUNT[bx],1  ; do one char at a time
	call	break_check		; call the break check routine
	cmp	ds:RH_CMD[bx],CMD_OUTPUT ; which way are we going 
	 jne	inst_io25
	call	inst_io_getchar		; AL = 1st char in the buffer
	cmp	al,1Ah			; EOF - don't send it or anything after
	 je	inst_io30		;  and exit without xfering any
inst_io25:
	push ds ! push es ! push di ! push cx
	lds	si,es:DHNDL_DEVPTR[di]	; DS:SI -> device driver
	push ss ! pop es		; ES:BX -> RH_
	call	device_driver		; execute the command
	pop cx ! pop di ! pop es ! pop ds
	 jns	inst_io_continue	; if no errors carry on
	push	es
	les	si,es:DHNDL_DEVPTR[di]	; ES:SI -> device driver
	call	char_error		; this will handle the Int 24
	pop	es
	cmp	al,ERR_RETRY		; what should we do ?
	 je	inst_io20		; retry the operation
	 ja	inst_io30		; fail - return error
	mov	ds:RH_STATUS[bx],RHS_DONE
	jmps	inst_io_ignore		; ignore - fiddle status and
inst_io_continue:			;  say we did it all
	mov	dx,ds:RH4_COUNT[bx]	; how many did we xfer ?
	test	dx,dx			;  if we haven't done any
	 jz	inst_io30		;  we are stuck so exit now
inst_io_ignore:
	call	inst_io_getchar		; AL = 1st char in the buffer
	add	ds:RH4_BUFOFF[bx],dx	; it may not enough so adjust offset
	sub	cx,dx			;  and number still to do
	cmp	ds:RH_CMD[bx],CMD_INPUT	; which way are we going - if input
	 jne	inst_io20		;  we need to check for CR/EOF
	test	es:DHNDL_ATTR[di],DHAT_BIN+DHAT_NUL
	 jnz	inst_io30		; if BIN then exit now
	cmp	al,13			; is it a CR character ?
	 je	inst_io30		;  yes, we stop now
	cmp	al,1Ah			; is it the EOF character ?
	 jne	inst_io20		;  yes, we aren't ready
	and	es:DHNDL_ATTR[di],not DHAT_READY
inst_io30:
	mov	di,2[bp]		; DI -> parameter block
	sub	ds:8[di],cx		; subtract # not xfered from byte count

	mov	ax,ds:RH_STATUS[bx]	; get result for later
	sub	bx,bx			; assume no errors
	test	ax,ax			; test error bit (8000h)
	 jns	rddev_no_err		; skip if ERROR set
	mov	bl,al			; AL is error code
	neg	bx			; make it negative code
	add	bx,ED_PROTECT		; normalize for extended errors
rddev_no_err:
	add	sp,RH4_CDEV_LEN		; free up RH_ on stack
	ret				; return BX

inst_io_getchar:
	push	ds
	lds	si,ds:RH4_BUFFER[bx]	; point to the users buffer
	lodsb				; get 1st char in the buffer
	pop	ds
	ret


read_con:	; handle read from cooked console
;--------
;	entry:	AH = DHNDL_ATTR
;		ES:BX -> DHNDL_
;		2[BP] -> F_DOS parameter block
;	exit:	BX = return value

	xor	cx,cx			; assume we've already had EOF
	test	ah,DHAT_READY		; now see if we have
	 jnz	con_dev_not_eof
	jmp	con_dev_exit		; yes, just return zero chars read
con_dev_not_eof:
	push	es
	push	bx			; save DHNDL_
con_dev_loop:
	mov	bx,word ptr fdos_buf	; get # of bytes already used
	xor	ax,ax
	xchg	al,bh			; get # bytes in the buffer
	inc ax ! inc ax			; also count the CR/LF
	sub	ax,bx			; have we any bytes left in the buffer?
	 ja	con_dev_cont		; yes, return them
	 				; no, we need a fresh input line
	mov	fdos_buf,128		; read up to 128 characters
	mov	si,2[bp]		; SI -> parameter block
	mov	bx,ds:2[si]		; BX = input handle
	push	ds ! pop es
	mov	dx,offset fdos_buf	; ES:DX -> console buffer
	mov	cx,bx			; output to same handle as input
	push	bx
	push	bp
	call	read_line		; read edited line
	pop	bp
	mov	bl,fdos_buf+1		; # byte we have read
	mov	bh,0			; BX = # of characters read
	mov	word ptr fdos_buf+2[bx],0A0Dh; append carriage return/line feed
	mov	fdos_buf,0		; start reading at beginning
	lea	si,fdos_buf+3[bx]	; Echo the LF character to the 
	pop	bx			; Same Output handle
	mov	cx,1			; Only One Character
	call	cooked_write
	jmps	con_dev_loop

con_dev_cont:				; BX = buffer offset
	mov	di,2[bp]		; DI -> parameter block
	mov	cx,ds:8[di]		; CX = # of bytes to read
	cmp	cx,ax			; reading more than available?
	 jbe	con_dev_ok		; no, read as much as you want
	mov	cx,ax			; else take all that there is
con_dev_ok:
	add	fdos_buf,cl		; update buffer index for next time
	les	di,ds:4[di]		; ES:DI -> buffer to read into
	lea	si,fdos_buf+2[bx]	; DS:SI -> function 10 buffer
	push	cx			; save count
	rep	movsb			; read all the data
	pop	cx			; restore count
	mov	al,1Ah			; now we look for EOF mark...
	push ds ! pop es
	lea	di,fdos_buf+2[bx]	; ES:DI -> function 10 buffer
	mov	si,cx			; keep count safe
	repne	scasb
	xchg	cx,si			; restore count
	pop	bx			; recover DHNDL_
	pop	es
	 jne	con_dev_exit		; if no EOF, skip to exit
	sub	cx,si			; subtract any after EOF mark
	dec	cx			; and the EOF mark itself
	and	es:DHNDL_ATTR[bx],not DHAT_READY
con_dev_exit:
	mov	di,2[bp]		; DI -> parameter block
	mov	ds:8[di],cx		; set # of characters read
	sub	bx,bx			; good return code
	ret




eject

first_dev:	; F_DOS FIRST call on device performed
;---------	; Called with MXDisk
; On Entry:
;	ES:BX -> device header
; On Exit:
;	dma_buffer initialised with device name
;
	mov	dx,es			; DX:BX -> device header
	call	les_di_dmaptr		; ES:DI -> DMA buffer
	mov	al,0FFh			; invalidate search state for NEXT
	mov	cx,21
	rep	stosb
	mov	al,40h			; mark it as a device
	stosb
	sub	ax,ax
	mov	cx,4
	rep	stosw			; zero time, date, file size

	lea	si,10[bx]
	push	ds
	mov	ds,dx			; DS:SI -> name in device header
	mov	cx,4
	rep	movsw			; copy device name across
	pop	ds

	mov	cx,8
frstdev1:				; scan off trailing spaces
	cmp	es:byte ptr 0-1[di],' '
	 jne	frstdev2
	dec	di
	loop	frstdev1
frstdev2:
	mov	al,0
	stosb				; add a trailing NUL
	ret


eject

ioc6_dev:	; IOCTL(6) - input status for device
;--------
;	entry:	ES:BX -> DHNDL_

	mov	al,CMD_INPUT_NOWAIT
	jmps	ioc67d			; call the device driver


ioc7_dev:	; IOCTL(7) - output status for device
;--------
;	entry:	ES:BX -> DHNDL_

	mov	al,CMD_OUTPUT_STATUS	; OUTPUT STATUS
ioc67d:					; common code for I/O STATUS
	push	ds
	sub	sp,RH5_LEN-2*word	; allocate request header on stack
	push	ax			; RH_CMD = AL
	mov	ax,RH5_LEN
	push	ax			; RH_LEN = RH5_LEN
	lds	si,es:DHNDL_DEVPTR[bx]	; DS:SI -> device driver
	push ss ! pop es
	mov	bx,sp			; ES:BX -> RH_
	mov	es:RH5_CHAR[bx],0	; zero the char
	call	device_driver		; do the CALLF's to the device driver
	xor	dl,dl			; assume not ready
	mov	dh,es:RH5_CHAR[bx]	; possible peeked character
	add	sp,RH5_LEN		; recover stack space
	pop	ds
	test	ax,RHS_ERROR+RHS_BUSY	; test if BUSY bit set in status
	 jnz	ioc67d_ret		; device not ready if error or busy
	dec	dl			; return ready DL = FF
ioc67d_ret:
	mov	si,2[bp]		; SI -> user's parameter block
	mov	ds:6[si],dx		; update returned status
	sub	bx,bx			; no error occurred
	ret				;	for now

	end				; of CDEVIO.A86
