	entry	?iopen,?atach,?ioctl,ntabs,?bclof
	entry	?bread,?bwrit,?init,?ifcb,?endin
	entry	?rdbuf,?wrbuf
	extrn	?bdos,ldem,?boot


ntabs	equ	6	; number i/o entries in tables

@open	equ	15	; open file
@srch	equ	17	; search for file in directory
@del	equ	19	; delete file from mfd
@make	equ	22	; build file entry in mfd
@read	equ	20	; read disk
@write	equ	21	; write disk
@close	equ	16	; close file
@setdma	equ	26	; set dm address
tbuff	equ	80h	; temporary buffer
cr	equ	0dh	; carriage return

false	equ	0
true	equ	not false
testsys	equ	false
nsisys	equ	false

	common	/IOCTRL/
er:	db	0	; er - byte er; used by i/o subsystem
.ac:	db	0	; number of files on control statement
.iav:	ds	2*ntabs ; one word per file
	cseg
?iopen:	sta	op	; save operation (read or write)
	xra	a	; clear hanging trash in er byte
	sta	er	; so that it won't give false error
	mov	a,l	; iav the block number is used as a file number
	xchg		; put associated buffer in hl
	shld	bfr	; save open file buffer address
	call	sioctl	; search i/o control table for this file (a)
	jc	iopn1	; file not in table (not prepared)
	mvi	a,4	; look for prepared bit in table slot
	ana	m	; (bit=00000100) means association was made
	jnz	iopn2	; between filename and a unique block number
iopn0:	sta	er	; indicate error
	ret		; return to user
iopn1:	mvi	a,5	; er=5 *error* file not in table
	jmp	iopn0	; association not made
iopn2:	mvi	m,1	; set bit 1 for read operation
	lda	op	; see if we are really going to read
	cpi	'R'	; upper case R means read (BYTE VBL)
	jz	iopn3	; jump to read segment
	cpi	'W'	; test for write
	mvi	m,2	; set write bit
	jz	iopn4	; go to write segment
; heres the spot for random file i/o you write it
	mvi	a,6	; er=6 *error* operation not R or W
	jmp	iopn0	; return to user
iopn3:	call	iopn9	; store buffer address in table
	call	ldem	; gets fcb address in de
	mvi	c,@open	; open file
	push	h	; save buffer counter address
	call	?bdos	; thru cp/m
	pop	h	;retreive buffer counter address
	mvi	m,128	; set ctr to 128 indicate buffer empty
	inr	a	; a < 255?
	rnz		; yup, return, file open for read
	mvi	a,7	; er=7 *error* file not catalogued
	jmp	iopn0	; return error to user
iopn4:	call	iopn9	; set buffer address in table
	call	ldem	; obtain fcb address
	push	d	; keep de for future calls
	mvi	c,@srch	; search for file in directory
	call	?bdos
	pop	d	; get fcb address back
	inr	a	; if a=255 then no file with given name
	jz	iopn5	; don't bother to delete and remake
	push	d	; save fcb
	mvi	c,@del	; delete file
	call	?bdos	; from mfd
	pop	d
iopn5:	mvi	c,@make	; make file entry
	call	?bdos	; in mfd for this file
	ret		; return to user

iowait:	mvi	c,11
	call	?bdos
	ora	a
	rz
	mvi	c,1
	call	?bdos
	ret
;
; subroutine to store buffer address in io control table
;
; NOTE: THE FORMAT OF THIS TABLE IS THAT THERE ARE NTAB
; ENTRIES ALL 7 BYTES LONG.  NTAB CONTROLS THE REPT OPERATION
; AND IS EXTERNAL SO THAT YOU CAN'T GO WRONG HERE
;
;		__________________________________
; byte 0:	|    operation designator	 |
;		----------------------------------
; byte 1:	|	file number from iav	 |
;		----------------------------------
; byte 2 & 3:	|	buffer memory		 |
;		|		address		 |
;		----------------------------------
; byte 4 & 5:	| cp/m file control table adrs	 |
;		| set by associate command	 |
;		----------------------------------
; byte 6:	|	buffer counter		 |
;		----------------------------------
;
; iav =>the lower 7 bits must be unique for each file.  This will
; hold up PROBABLY if you use good sense.  It can't be =0.
;
;
iopn9:	inx	h	; point to buffer memory address
	inx	h	; (was pointing to byte 0) see above
	xchg		; get it de
	lhld	bfr	; get saved address
	mov	a,l	; tranfer it to memory
	stax	d
	inx	d
	mov	a,h
	stax	d
	inx	h
	inx	d
	xchg		; important make hl point to fcb area
	ret		; prior to return
;
; search i/o control table for file number in a
; exit with address in hl of start of 7 word entry when found.
; 	carry will be =0
; if not found hl=>garbages carry set
;
sioctl:	lxi	h,?ioctl+1 ; point to file number part of table
	mvi	c,ntabs	; number of table entries
	lxi	d,iosize ; entry length (now 7 bytes)
sio0:	cmp	m	; check for = file number
	jz	sio1	; found
	dad	d	; step table
	dcr	c	; next entry?
	jnz	sio0	; loop
	stc		; set carry, not found
	ret		; return to user code
sio1:	dcx	h	; point to byte 0 of table entry (not 1)
	ora	a	; clear carry flag
	ret		; return to user
;
; general subroutines used by read/write and close as well as others
;
;
; SETDMA ADDRESS
;
SETDMA:	lhld	bfr
	xchg
	mvi	c,@setdma
	CALL	?bdos
	RET		;RETURN TO USER CODE

;
; XDEHL - tests buffer address in memory against DE
;
xdehl:	lda	bfr
	xra	e
	rnz		; not equal
	lda	bfr+1
	xra	d	;
	ret		; ?
;
; Preset registers for iocontrol table search
; - assumes bfr address is in de
;
preset:	xchg
	shld	bfr
setup:	lxi	b,iosize ; entry length
	mvi	d,ntabs	; number of entries
	lxi	h,?ioctl ; table base address
	ret
;
; Get buffer address from control table
;
gbfa:	shld	tempoh	; retain this
	xchg
	shld	tempod	; and this in case compare blows up
	xchg		; get registers back to first state
	inx	h
	inx	h	; skip over first two bytes
	jmp	ldem	; loads de from (hl) bumps hl by +2
			; it advances hl by 2 ( uses its ret to get back)
;
; write block to selected file
;
wrblk:	mvi	c,@write
	call	?bdos
	ora	a
	ret
;
; read byte to pidgin
;
; INVOKED BY: "READ k FROM b"
;
?bread:	xra	a	; set er byte to zero
	sta	er	; don't let casual use bomb program
	if	testsys
	mvi	a,'r'	; reading
	sta	which
	endif
	shld	destad	; destination address for byte
	call	preset	; load registers
	mvi	a,1	; look for read operation in table
br0:	cmp	m
	jz	br2	; found read operation
	dad	b	; next entry
	dcr	d
	jnz	br0
	mvi	a,2	; er=2 *error* no read files open
br1:	sta	er
	if	testsys	; if running tests, print errors on console
	ori	'0'	; add ascii bits

	lxi	d,hunh+1
	stax	d
	dcx	d
	lda	which
	stax	d
	mvi	c,9
	call	?bdos
	endif
	ret		; return to user
br2:	call	gbfa	; go get buffer address in de
	call	xdehl	; does de = current buffer address?
	jnz	br4	; fails
; here, we have correct buffer and read operation
; get buffer count here and return character to user
;
	call	ldem	; get fcb address
	mov	a,m	; get buffer count
	cpi	128	; is buffer full?
	jnz	br3	; no
	push	d	; save fcb
	push	h	; save address of buffer count
	call	setdma	; prepare to read
	pop	h	; get  buffer count address back
	pop	d	; get back fcb address
	push	h	; resave buffer count ( xchg two entries on stack )
	mvi	c,@read	; read block
	call	?bdos	; from disk
	pop	h	; get back buffer count address
	ora	a	; get status of read
	sta	er	; save it
	jnz	br5	; bad read return from bdos
	mov	m,a	; zero buffer counter
br3:	mvi	b,0
	mov	c,a	; bc=words in buffer
	inr	m	; advance buffer counter
	lhld	bfr
	dad	b	; calculate byte address in buffer
	xchg		; caculated bfr adr
	lhld	destad	; address of byte in pidgin program
	ldax	d	; get the byte
	mov	m,a	; give it to pidgin
	call	iowait
	ret
br4:	lhld	tempod
	xchg
	lhld	tempoh
	dad	b
	dcr	d
	jnz	br0-2	; try next one
	mvi	a,4	; er=4 *error* improper read buffer
	jmp	br1
br5:	lhld	destad	; end of file on input file
	mvi	m,26
	mvi	a,1
	jmp	br1	; exit for eof
;
; end of pidgin read
;
;------------------------------------------------------------
;
; subroutine ?bwrit / pidgin write routine
;
; INVOKED BY:
;
; WRITE b  INTO m
;
?bwrit:	sta	op	; save char temporarily in op
	if	testsys
	mvi	a,'w'
	sta	which
	endif
	call	preset	; preset registers for search
	mvi	a,2	; look for active write bits
bw0:	cmp	m	; is this entry a write entry
	jz	bw1	; found
	dad	b
	dcr	d	; more?
	jnz	bw0	; yup!
	jmp	br1	; er=2 *error* no active write on this buffer
bw1:	call	gbfa	; go load table's buffer address into de
	call	xdehl	; check it against buffer address in call
	jnz	bw3	; not same buffer, look some more
	call	ldem	; get users fcb
	push	d	; save it for further use
	mov	a,m	; get count of characters in buffer
	cpi	128	; is buffer full?
	jz	bw4	; yes, write it out
bw2:	mov	c,a	; form address of corrent character
	mvi	b,0
	inr	m	; step buffer counter
	lhld	bfr	; get buffer address
	dad	b
	lda	op	; get the character back
	mov	m,a	; put it in buffer
	pop	d	; pop fcb address off stack
	call	iowait
	ret		; return to user code
bw3:	lhld	tempod
	xchg
	lhld	tempoh
	dad	b
	dcr	d	; more?
	jnz	bw0-2	; yerse
	mvi	a,2	; er=2 *error* write buffer not located
	jmp	br1	; in io control tables
bw4:	push	h	; save address of buffer counter
	call	setdma	; set output buffer address
	pop	h
	pop	d	; retrieve fcb address
	push	h
	call	wrblk	; write a block
	jnz	bw5	; jump to error
	pop	h
	mvi	m,1	; reset buffer counter
	lhld	bfr	; get buffer address
	lda	op	; get character
	mov	m,a	; stuff it in buffer
	call	iowait
	ret		; return to user
bw5:	mvi	a,2	; er=2 *error* write operation failed
	pop	h
	jmp	br1	; error exit
;
; end of pidgin write subroutine
;
;-------------------------------------------------------------
;
; close pidgin file
;
; INVOKED BY:
;
; CLOSE b
;
; this routine closes any file attached to the write buffer
;
; and any read buffer files as well.  write buffer is flushed at close
;
;
?bclof:	shld	bfr	; keep buffer address
	call	setup	; set up registers only
clo0:	mvi	a,3	; set for read or write
			; note !!!! random files won't allow this test
	ana	m	; sense bit
	jz	clo1
	call	gbfa	; get buffer address from table
	call	xdehl	; is it same as one supplied?
	lhld	tempod
	xchg
	lhld	tempoh
	jz	clo3	; yes, close that file
clo1:	dad	b
	dcr	d
	jnz	clo0	; loop looking for another file
clo2:	mvi	a,2	; er=2 *error* file doesn't exist
	sta	er
	ret
clo3:	lxi	b,4	; set to get fcb from io control table
	mov	a,m	; get byte 0 of ioctl (operation)
	ani	2	; check for write
	jnz	clo4	; write operation
	mov	m,a	; zeroize it now
	inx	h
	mov	m,a	; clear file number
	dcx	h	; (both byte 0 & 1 are now =0)
	dad	b	; meaning file is logically closed
	call	ldem	; get fcb address in de now
	mvi	m,0	; clear buffer counter
	mvi	c,@close
	call	?bdos
	xra	a
	jmp	clo2+2
; close write buffer
clo4:	mvi	m,0	; zero buffer counter
	dad	b	; calculate fcb address
	call	ldem	; get fcb in de
	push	d	; stack it
	mov	c,m	; get buffer count
	mvi	a,128	; buffer size
	sub 	c	; 128-current
	jnz	clo5
	mvi	a,128	; if buffer is full, write new block
clo5:	lhld	bfr	; of all 1ah
	push	b	; save bfr count
	call	setdma	; set output buffer address
	pop	b	; restore counter
	lhld	bfr	; get buffer address back
	mvi	b,0
	dad	b	; point to last used loc in buffer
	mov	c,a	; count of unused portion of buffer
clo6:	mvi	m,01ah	; write eof into buffer
	inx	h
	dcr	c
	jnz	clo6	; loop
	pop	d
	push	d
	call	wrblk	; write block
	pop	d
	mvi	c,@close
	call	?bdos
	xra	a
	sta	er
	ret
;
; end of pidgin close
;
?rdbuf: ;/ buffer read
;
?wrbuf: ;/buffer write
	ret
;
;---------------------------------------------------------------
;
;
; attach routine - peculiar to cp/m pidgin - attachs user fcb
; to buffer / file
;
;---------------------------------------------------------------
;
?atach:	push	d	; save exec fcb
	push	h
af0:	xra	a
	sta	er
	call	sioctl	; search table looking for zero slot
	jnc	af1	; found, continue
	mvi	a,1	; er=1 *error* table full
	pop	b
	pop	b	; reset stack
	sta	er
	ret		; return
af1:	pop	d	; get adr of file id
	ldax	d	; get file id
	push	h	; save zero slot in table
	call	sioctl	; search for this particular file
	pop	h	; get back zero slot
	jc	af2	; goo, file not in table
	pop	d	; unstack fcb
	mvi	a,2	; er=2 *error* file already in use
	sta	er
	ret
af2:	mvi	m,4	; set operation to prepared
	inx	h	; point to file id
	mov	m,a	; set file # in table
	inx	h
	inx	h
	inx	h
	pop	d	; get fcb
	mov	m,e
	inx	h
	mov	m,d
	inx	h
	mvi	m,0	; set bfr count to zero
	ret
;
;
; ?ENDING / close all files and terminate pidgin
; program correctly
;
;
?endin:	
	call	setup
end1:	mvi	a,1	; set to look for read operation
	ana	m	; read op?
	cnz	rclose	; close read file
	mvi	a,2	; write operation
	ana	m	; is this write?
	cnz	wclose	; close write file
	dad	b	; next entry
	dcr	d
	jnz	end1	; loop till all files closed
	ret
rclose:
	push	h
	push	d
	push	b
	xra	a
	mov	m,a	; set operation to zero
	dad	b	; set hl=>next entry
	dcx	h	; hl=> buffer counter
	mov	m,a	; zero buffer count
	dcx	h	; ho byte of fcb address
	mov	d,m	; load de from (hl) [fcb address]
	dcx	h	;
	mov	e,m	; de=>address of read file's fcb
	mvi	c,@close ; close file function
	call	?bdos	; do it
	pop	b
	pop	d
	pop	h
	ret		; note: remainder of ctl tbl not zeroed out!
;
; wclose / close write file.  This must first flush the write buffer.
;	then, go to cp/m to write directory table entry into track 2.
;
wclose:
	push	h
	push	d
	push	b
	xra	a
	mov	m,a
	inx	h
	mov	m,a
	inx	h
	call	ldem	; load de with buffer address
	xchg		; de=>hl (store de in bfr)
	shld	bfr	; save buffer address
	xchg		; get it back
	call	ldem	; get fcb address
	xchg
	shld	tempod	; save fcb address for use later
	xchg
	mov	c,m	; get buffer count
	mvi	a,128	; buffer size
	sub	c	; how much buffer is left
	jz	$+4	; if none, write a buffer of all 1ah
	lxi	d,803eh	; becomes mvi a,128
	mov	c,a	; remaining buffer locations
	mvi	b,0
	lhld	bfr	; base address of buffer
	dad	b	; address to store 1ah's at
	mvi	a,1ah
wcl1:	mov	m,a	; loop to fill buffer with eof's
	inx	h	;
	dcr	c	;
	jnz	wcl1	;
	call	setdma	; set address of write transfer
	lhld	tempod
	xchg		; get fcb into de
	call	wrblk	; write block to disk
	lhld	tempod
	xchg		; get fcb back
	mvi	c,@close
	call	?bdos	; close write file
	pop	b
	pop	d
	pop	h
	ret		; return
;------------------------------------------------------;
;
; This is the initialization segment for all PIDGIN
; programs.  It is entered by the statement "BEGINMAIN"
; and its functions are:
; 1: construct a series of up to ntabs FCB's by scanning
;    the processor call line.
; 2: store the number of files on the call line in .AC
; 3: move a series of integers from 1 to 6 in each of
;    six words, .IAV(0) thru .IAV(5)
; 4: do any thing else that the user may want done prior
;    to execution such as set the clock etc...
;    NOTE: USER CODE SHOULD BE INSERTED AT INIT2: OR BEYOND
;
;;
;-----------------------------------------------------;
;

?init:	lxi	h,0	; clear hl
	dad	sp	; get stack pointer
	shld	destad	; save stack pointer
	lxi	b,ntabs
	lxi	sp,.iav+((ntabs-1)*2)-1	; iav=integer operating system array
	push	b	; store values into .iav
	dcr	c
	jnz	$-2
	lhld	destad
	sphl
	lxi	h,fcb1
	shld	nxtfcb	; save first fcb location
	xchg
	lxi	h,tbuff
	xra	a	; set ac to zero
	sta	.ac	; indicates no files
	ora	m	; or in bits from buffer count
	jz	init2	; tbuffer empty, just skip all of this
	push	h
	lxi	b,0
	mov	c,m
	dad	b	; form ending address of string
	inx	h	; adjust it
	mvi	m,cr	; put a cr at end of string for 2.x and mp/m
	pop	h
	inx	h	; point to tbuff+1
init1:	call	mtfcb
	jc	init3	; if error ask for reboot
	shld	destad	; use temporary storage
	lxi	b,fcbsiz
	xchg
	lhld	nxtfcb
	dad	b
	shld	nxtfcb
	lxi	h,.ac
	inr	m
	cpi	0dh	; was character a c/r?
	jz	init2	; yes, done statement scan
	mvi	a,ntabs
	cmp	m
	jz	init2
	lhld	nxtfcb
	xchg
	lhld	destad
	jmp	init1
init2:
	if	nsisys
	mvi	c,9
	lxi	d,exmsg	; on console
	call	?bdos
	endif
	ret
init3:	mvi	c,9
	lda	.ac	; get file counter
	inr	a
	ori	'0'
	sta	flno
	lxi	d,fnbad
	call	?bdos
	jmp	?boot
;++++++++++++++++++++++++++++++++++++++++++++++
;
; MAKE CP/M FILE CONTROL BLOCK
;
; MAKEFCB.LIB  -  Version 0.2  -  28 OCT 77
;
; JEFFREY W. SHOOK
; P.O. BOX 185
; ROCKY POINT, NEW YORK 11778
; (516) 744 7133
;
;++++++++++++++++++++++++++++++++++++++++++++++


; Create a CP/M file control block  from
; a command string at the address in HL
; and place it at the address in DE.  Return
; with the carry set if an error occurs.


; DEFINITIONS

fcbsiz	EQU	36	; length of FCB in cp/m 2.2 **alb 10/81**
fnmlen	EQU	11	; File name length


MTFCB:	PUSH	H	; Save cmd string ptr
	PUSH	D	; Save FCB address
	
	LXI	B,FCBSIZ; Clear entire FCB area
	xra	a	; set a=0	**alb 10/81**
	CALL	FILLB	;

	POP	D	; Fill file name with spaces
	PUSH	D	;
	INX	D	;
	LXI	B,FNMLEN;
	MVI	A,' '	;
	CALL	FILLB	;

	POP	D	; Restore pointers
	POP	H	;

	CALL	SKIPS	; Skip leading spaces

	INX	H	; Check for disk code
	MOV	A,M	;
	DCX	H	;
	CPI	':'	;
	JNZ	MTFCB1	; Jump on no code

	MOV	A,M	; Test if disk code good
	INX	H	;
	INX	H	;
	SBI	'@'	;
	RC		; Make error return if bad
	CPI	'Z'+1	;
	CMC		;
	RC		;

	STAX	D	; Store disk code at FCB + 0
MTFCB1:	INX	D	;

	MVI	C,8	; Process file name field
	CALL	GETNAM	;

	MOV	A,M	; Test for file type separator
	INX	H	;
	CPI	'.'	;
	JNZ	MTFCB2	;

	MVI	C,3	; Process file type field
	CALL	GETNAM	;
	MOV	A,M	;
	INX	H	;

MTFCB2:	CALL	TERMT	; Test for corect terminator

	RET


; PROCESS NAME FIELD

GETNAM:	MOV	A,M	; Get char from cmd str
	INX	H	;

	CPI	'?'	; Allow ambig reference char
	JZ	GETNA1	;

	CPI	'*'	; Fill rest with ?
	JZ	GETNA2	;

	CALL	VALCHR	; Test for allowed char in name
	JC	GETNA3	;

GETNA1:	STAX	D	; Store char in TFCB
	INX	D	;

	DCR	C	; Check name size
	JNZ	GETNAM	;
	RET		;


GETNA2:	MVI	A,'?'	; Fill rest of field with ?
	MVI	B,0	;
	JMP	FILLB	;

GETNA3:	INX	D	; Move FCB ptr to end of field
	DCR	C	;
	JNZ	GETNA3	;
	DCX	H	;
	RET		;


; TEST FOR VALID CHAR IN  NAME FIELD
; Return with carry set if invalid.

VALCHR:	CPI	'*'
	CMC
	RZ

	CPI	','
	CMC
	RZ

	CPI	'.'
	CMC
	RZ		; **alb 10/81**

	CPI	' '
	RC

	CPI	'^'+1
	CMC
	RC

	CPI	':'
	CMC
	RNC

	CPI	'@'
	RET


; TEST FOR VALID FILENAME TERMINATOR CHAR
; Return with zarry set if invalid.

TERMT:	CPI	' '
	RZ

	CPI	','
	RZ

	CPI	CR
	RZ

	CPI	';'
	RZ

	ORA	A	; CHECK FOR NULL TERMINATION
	RZ		; YOU GOT IT

	STC
	RET


; SKIP SPACES IN CMD STRING

SKIPS:	MVI	A,' '
SKIPS1:	CMP	M	; **alb 10/81**
	RNZ
	INX	H
	JMP	SKIPS1


; FILL BLOCK WITH VALUE

; Enter with:
; A  = value for fill
; DE = start of block
; BC = length of block

CLRB:	MVI	A,0

FILLB:	INR	B
	DCR	B
	JNZ	FILLB1
	INR	C
	DCR	C
	RZ

FILLB1:	STAX	D

	INX	D
	DCX	B

	JMP	FILLB
	if	nsisys
exmsg:	db	27,'H',27,'J'
	db	'PIDGIN EXECUTION (1.0 (,m))'
	db	0dh,0ah,'$'
	endif
	dseg
fnbad:	db	0dh,0ah
	db	'Cannot interpret character correctly'
	db	' in file number: '
flno:	db	'0',0dh,0ah,'$'
bfr:	dw	0
nxtfcb:	dw	fcb1	; fcb chain
destad:	db	0	; destad=destad^op bytes
op:	db	0
tempoh:	dw	0	; temp storage for control of search loop
tempod:	dw	0
?ioctl:	db	0	; file operation
	db	0	; file id
	dw	0	; buffer address
	dw	0	; fcb address
	db	0	; buffer counter
iosize	equ	$-?ioctl
	rept	ntabs-1
	rept	iosize
	db	0
	endm
	endm
?ifcb:
fcb1:	ds	ntabs*fcbsiz	; fcb running hardware table
	if	testsys
which:	db	0
hunh:	db	'  ',0dh,0ah,'$'
	endif
	end
