	title	'CP/M V3.0 Loader'


;	Copyright (C) 1982
;	Digital Research
;	Box 579, Pacific Grove
;	California, 93950

;  Revised:
;    01 Nov 82  by Bruce Skidmore

base	equ	$
abase	equ	base-0100h

cr	equ	0dh
lf	equ	0ah

fcb	equ	abase+005ch	;default FCB address
buff	equ	abase+0080h	;default buffer address

;
;	System Equates
;
resetsys	equ	13	;reset disk system
printbuf	equ	09	;print string
open$func	equ	15	;open function
read$func	equ	20	;read sequential
setdma$func	equ	26	;set dma address
;
;	Loader Equates
;
comtop	equ	abase+80h
comlen	equ	abase+81h
bnktop	equ	abase+82h
bnklen	equ	abase+83h
osentry	equ	abase+84h

	cseg

	lxi	sp,stackbot

	call	bootf		;first call is to Cold Boot

	mvi	c,resetsys	;Initialize the System
	call	bdos

	mvi	c,printbuf	;print the sign on message
	lxi	d,signon
	call	bdos

	mvi	c,open$func	;open the CPM3.SYS file
	lxi	d,cpmfcb
	call	bdos
	cpi	0ffh
	lxi	d,openerr
	jz	error

	lxi	d,buff
	call	setdma$proc

	call	read$proc	;read the load record

	lxi	h,buff
	lxi	d,mem$top
	mvi	c,6
cloop:
	mov	a,m
	stax	d
	inx	d
	inx	h
	dcr	c
	jnz	cloop
	
	call	read$proc	;read display info

	mvi	c,printbuf	;print the info
	lxi	d,buff
	call	bdos
	
;
;	Main System Load
;

;
;	Load Common Portion of System
;
	lda	res$len
	mov	h,a
	lda	mem$top
	call	load
;
;	Load Banked Portion of System
;
	lda	bank$len
	ora	a
	jz	execute
	mov	h,a
	lda	bank$top
	call	load
;
;	Execute System
;
execute:
	lxi	h,fcb+1
	mov	a,m
	cpi	'$'
	jnz	execute$sys
	inx	h
	mov	a,m
	cpi	'B'
	cz	break
execute$sys:
	lxi	sp,osentry$adr
	ret

;
;	Load Routine
;
;	Input:   A = Page Address of load top
;		 H = Length in pages of module to read
;
load:
	ora	a	;clear carry
	mov	d,a
	mvi	e,0
	mov	a,h
	ral
	mov	h,a	;h = length in records of module
loop:
	xchg
	lxi	b,-128
	dad	b	;decrement dma address by 128
	xchg
	push	d
	push	h
	call	setdma$proc
	call	read$proc
	pop	h
	pop	d
	dcr	h
	jnz	loop
	ret

;
;	Set DMA Routine
;
setdma$proc:
	mvi	c,setdma$func
	call	bdos
	ret

;
;	Read Routine
;
read$proc:
	mvi	c,read$func	;Read the load record
	lxi	d,cpmfcb	;into address 80h
	call	bdos
	ora	a
	lxi	d,readerr
	rz
;
;	Error Routine
;
error:
	mvi	c,printbuf	;print error message
	call	bdos
	di
	hlt

break:
	db	0ffh
	ret

cpmfcb:
	db	0,'CPM3    SYS',0,0,0,0,0,0
	dw	0,0,0,0,0,0,0,0,0

openerr:
	db	cr,lf
	db	'CPMLDR error:  failed to open CPM3.SYS'
	db	cr,lf,'$'

readerr:
	db	cr,lf
	db	'CPMLDR error:  failed to read CPM3.SYS'
	db	cr,lf,'$'

signon:
	db	cr
	db	lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf
	db	lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf,lf
	db	'CP/M V3.0 Loader',cr,lf
	db	'Copyright (C) 1982, Digital Research'
	db	cr,lf,'$'

	db	'021182',0,0,0,0
stackbot:

mem$top:
	ds	1
res$len:
	ds	1
bank$top:
	ds	1
bank$len:
	ds	1
osentry$adr:
	ds	2

;	title	'CP/M 3.0 LDRBDOS Interface, Version 3.1 Nov, 1982'
;*****************************************************************
;*****************************************************************
;**                                                             **
;**   B a s i c    D i s k   O p e r a t i n g   S y s t e m    **
;**								**
;**            I n t e r f a c e   M o d u l e                  **
;**                                                             **
;*****************************************************************
;*****************************************************************
;
;	Copyright (c) 1978, 1979, 1980, 1981, 1982
;	Digital Research
;	Box 579, Pacific Grove
;	California
;
;       Nov 1982
;
;
;	equates for non graphic characters
;

rubout	equ	7fh	; char delete
tab	equ	09h	; tab char
cr	equ	0dh	; carriage return
lf	equ	0ah	; line feed
ctlh	equ	08h	; backspace


;
serial: db	0,0,0,0,0,0
;
;	Enter here from the user's program with function number in c,
;	and information address in d,e
;

bdos:
bdose:					; Arrive here from user programs
	xchg! shld info! xchg 		; info=de, de=info

	mov a,c! cpi 14! jc bdose2
	sta fx 				; Save disk function #
	xra a! sta dircnt 
	lda seldsk! sta olddsk 		; Save seldsk

bdose2:
	mov a,e! sta linfo 		; linfo = low(info) - don't equ
	lxi h,0! shld aret 		; Return value defaults to 0000
	shld resel 			; resel = 0
				; Save user's stack pointer, set to local stack
	dad sp! shld entsp 		; entsp = stackptr

	lxi sp,lstack 			; local stack setup

	lxi h,goback 			; Return here after all functions
	push h 				; jmp goback equivalent to ret
	mov a,c! cpi nfuncs! jnc high$fxs ; Skip if invalid #
	mov c,e 			; possible output character to c
	lxi h,functab! jmp bdos$jmp

	; look for functions 100 ->
high$fxs:
	sbi 100! jc lret$eq$ff 		; Skip if function < 100

bdos$jmp:

	mov e,a! mvi d,0 		; de=func, hl=.ciotab
	dad d! dad d! mov e,m! inx h! mov d,m ; de=functab(func)
	lhld info 			; info in de for later xchg	
	xchg! pchl 			; dispatched


;	dispatch table for functions

functab:
	dw	func$ret, func1, func2, func3
	dw	func$ret, func$ret, func6, func$ret
	dw	func$ret, func9, func10, func11
diskf	equ	($-functab)/2	; disk funcs
	dw	func12,func13,func14,func15
	dw	func16,func17,func18,func19
	dw	func20,func21,func22,func23
	dw	func24,func25,func26,func27
	dw	func28,func29,func30,func31
	dw	func32,func33,func34,func35
	dw	func36,func37,func38,func39
	dw	func40,func42,func43
	dw	func44,func45,func46,func47
	dw	func48,func49,func50
nfuncs	equ	($-functab)/2


entsp:	ds	2	; entry stack pointer

	;	40 level stack

	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
	dw	0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h,0c7c7h
lstack:


page
	title	'CP/M 3.0 LDRBDOS Interface, Version 3.1 July, 1982'
;*****************************************************************
;*****************************************************************
;**                                                             **
;**   B a s i c    D i s k   O p e r a t i n g   S y s t e m    **
;**								**
;**               C o n s o l e   P o r t i o n                 **
;**                                                             **
;*****************************************************************
;*****************************************************************
;
;       July, 1982
;
;
;	console handlers

conout:
			;compute character position/write console char from C
			;compcol = true if computing column position
	lda compcol! ora a! jnz compout
			;write the character, then compute the column
			;write console character from C
	push b 				;recall/save character
	call conoutf 			;externally, to console
	pop b 				;recall the character
  compout:
		mov a,c 		;recall the character
					;and compute column position
		lxi h,column 		;A = char, HL = .column
		cpi rubout! rz 		;no column change if nulls
		inr m 			;column = column + 1
		cpi ' '! rnc 		;return if graphic
					;not graphic, reset column position
		dcr m 			;column = column - 1
		mov a,m! ora a! rz 	;return if at zero
					;not at zero, may be backspace or eol
		mov a,c 		;character back to A
		cpi ctlh! jnz notbacksp
					;backspace character
		dcr m 			;column = column - 1
		ret

  notbacksp:
					;not a backspace character, eol?
		cpi lf! rnz 		;return if not
					;end of line, column = 0
		mvi m,0 		;column = 0
		ret
;
;
tabout:
					;expand tabs to console
	mov a,c! cpi tab! jnz conout 	;direct to conout if not
					;tab encountered, move to next tab pos
  tab0:
	mvi c,' '! call conout 		;another blank
	lda column! ani 111b 		;column mod 8 = 0 ?
	jnz tab0 			;back for another if not
	ret
;
print:
					;print message until M(BC) = '$'
	LXI H,OUTDELIM
	ldax b! CMP M! rz 		;stop on $
					;more to print
	inx b! push b! mov c,a 		;char to C
	call tabout 			;another character printed
	pop b! jmp print
;
;
func2:	equ	tabout
			;write console character with tab expansion
;
func9:
					;write line until $ encountered
	xchg				;was lhld info	
	mov c,l! mov b,h 		;BC=string address
	jmp print 			;out to console	
;
sta$ret:
					;store the A register to aret
	sta aret
func$ret:
	ret 			;jmp goback (pop stack for non cp/m functions)
;
setlret1:
					;set lret = 1
	mvi a,1! jmp sta$ret
;
func1:	equ 	func$ret
;
func3:	equ 	func$ret
;
func6:	equ 	func$ret
;
func10:	equ	func$ret
func11:	equ	func$ret
;
;	data areas
;


compcol:db	0	;true if computing column position
;	end of BDOS Console module

;**********************************************************************
;*****************************************************************
;
;	Error Messages

md	equ	24h

err$msg:	db	cr,lf,'BDOS ERR: ',md
err$select:	db	'Select',md
err$phys:	db	'Perm.',md

;*****************************************************************
;*****************************************************************
;
;	common values shared between bdosi and bdos


aret:	ds	2	; address value to return
lret	equ	aret	; low(aret)

;*****************************************************************
;*****************************************************************
;**                                                             **
;**   b a s i c    d i s k   o p e r a t i n g   s y s t e m    **
;**                                                             **
;*****************************************************************
;*****************************************************************

;	literal constants

true	equ	0ffh	; constant true
false	equ	000h	; constant false
enddir	equ	0ffffh	; end of directory
byte	equ	1	; number of bytes for "byte" type
word	equ	2	; number of bytes for "word" type

;	fixed addresses in low memory

tbuff	equ	0080h	; default buffer location

;	error message handlers

sel$error:
				; report select error
	lxi b,err$msg
	call print
	lxi b,err$select
	jmp goerr1

goerr:
	lxi b,err$msg
	call print
	lxi b,err$phys
goerr1:
	call print
	di ! hlt

bde$e$bde$m$hl:
	mov a,e! sub l! mov e,a
	mov a,d! sbb h! mov d,a
	rnc! dcr b! ret

bde$e$bde$p$hl:
	mov a,e! add l! mov e,a
	mov a,d! adc h! mov d,a
	rnc! inr b! ret

shl3bv:
	inr c
shl3bv1:
	dcr c! rz
	dad h! adc a! jmp shl3bv1

compare:
	ldax d! cmp m! rnz
	inx h! inx d! dcr c! rz
	jmp compare

;
;	local subroutines for bios interface
;

move:
	; Move data length of length c from source de to
	; destination given by hl
	inr c ; in case it is zero
	move0:
		dcr c! rz ; more to move
		ldax d! mov m,a ; one byte moved
		inx d! inx h ; to next byte
		jmp move0

selectdisk:
			; Select the disk drive given by register D, and fill
			; the base addresses curtrka - alloca, then fill
			; the values of the disk parameter block
	mov c,d 			; current disk# to c
					; lsb of e = 0 if not yet logged - in
	call seldskf 			; hl filled by call
				; hl = 0000 if error, otherwise disk headers
	mov a,h! ora l! rz 	; Return with C flag reset if select error
					; Disk header block address in hl
	mov e,m! inx h! mov d,m! inx h ; de=.tran
	inx h ! inx h
	shld curtrka! inx h! inx h ; hl=.currec
	shld curreca! inx h! inx h ; hl=.buffa
	inx h! inx h
	inx h! inx h
					; de still contains .tran
	xchg! shld tranv 		; .tran vector
	lxi h,dpbaddr 			; de= source for move, hl=dest
	mvi c,addlist! call move 	; addlist filled
					; Now fill the disk parameter block
	lhld dpbaddr! xchg 		; de is source
	lxi h,sectpt 			; hl is destination
	mvi c,dpblist! call move 	; data filled
					; Now set single/double map mode
	lhld maxall 			; largest allocation number
	mov a,h 			; 00 indicates < 255
	lxi h,single! mvi m,true 	; Assume a=00
	ora a! jz retselect
				; high order of maxall not zero, use double dm
	mvi m,false
  retselect:
				; C flag set indicates successful select
		stc
		ret

home:
			; Move to home position, then offset to start of dir
	call homef
	xra a 				; constant zero to accumulator
	lhld curtrka! mov m,a! inx h! mov m,a ; curtrk=0000
	lhld curreca! mov m,a! inx h! mov m,a ; currec=0000
	inx h! mov m,a 			; currec high byte=00

	ret

pass$arecord:
	lxi h,arecord
	mov e,m! inx h! mov d,m! inx h! mov b,m
	ret

rdbuff:
			; Read buffer and check condition
	call pass$arecord
	call readf 			; current drive, track, sector, dma


diocomp: 		; Check for disk errors
	ora a! rz
	mov c,a
	cpi 3! jc goerr
	mvi c,1! jmp goerr

seekdir:
			; Seek the record containing the current dir entry

	lhld dcnt 			; directory counter to hl
	mvi c,dskshf! call hlrotr 	; value to hl

	mvi b,0! xchg

	lxi h,arecord
	mov m,e! inx h! mov m,d! inx h! mov m,b
	ret

seek:
			; Seek the track given by arecord (actual record)

	lhld curtrka! mov c,m! inx h! mov b,m 	; bc = curtrk
	push b 					; s0 = curtrk 
	lhld curreca! mov e,m! inx h! mov d,m
	inx h! mov b,m 				; bde = currec
	lhld arecord! lda arecord+2! mov c,a 	; chl = arecord
seek0:
	mov a,l! sub e! mov a,h! sbb d! mov a,c! sbb b
	push h 					; Save low(arecord)
	jnc seek1 			; if arecord >= currec then go to seek1
	lhld sectpt! call bde$e$bde$m$hl 	; currec = currec - sectpt
	pop h! xthl! dcx h! xthl 		; curtrk = curtrk - 1
	jmp seek0
seek1:
	lhld sectpt! call bde$e$bde$p$hl 	; currec = currec + sectpt
	pop h 					; Restore low(arecord)
	mov a,l! sub e! mov a,h! sbb d! mov a,c! sbb b
	jc seek2 			; if arecord < currec then go to seek2
	xthl! inx h! xthl 			; curtrk = curtrk + 1
	push h 					; save low (arecord)
	jmp seek1
seek2:
	xthl! push h 			; hl,s0 = curtrk, s1 = low(arecord)
	lhld sectpt! call bde$e$bde$m$hl 	; currec = currec - sectpt
	pop h! push d! push b! push h 		; hl,s0 = curtrk, 
			; s1 = high(arecord,currec), s2 = low(currec), 
			; s3 = low(arecord)
	xchg! lhld offset! dad d
	mov b,h! mov c,l! shld track
	call settrkf 				; call bios settrk routine
						; Store curtrk
	pop d! lhld curtrka! mov m,e! inx h! mov m,d
						; Store currec
	pop b! pop d!
	lhld curreca! mov m,e! inx h! mov m,d
	inx h! mov m,b 				; currec = bde
	pop b 				; bc = low(arecord), de = low(currec)
	mov a,c! sub e! mov l,a 		; hl = bc - de
	mov a,b! sbb d! mov h,a
	call shr$physhf
	mov b,h! mov c,l

	lhld tranv! xchg 			; bc=sector#, de=.tran
	call sectran 				; hl = tran(sector)
	mov c,l! mov b,h 			; bc = tran(sector)
	shld sector
	call setsecf 				; sector selected
	lhld curdma! mov c,l! mov b,h! jmp setdmaf

shr$physhf:
	lda physhf! mov c,a! jmp hlrotr


;	file control block (fcb) constants

empty	equ	0e5h	; empty directory entry
recsiz	equ	128	; record size
fcblen	equ	32	; file control block size
dirrec	equ	recsiz/fcblen	; directory fcbs / record
dskshf	equ	2	; log2(dirrec)
dskmsk	equ	dirrec-1
fcbshf	equ	5	; log2(fcblen)

extnum	equ	12	; extent number field
maxext	equ	31	; largest extent number
ubytes	equ	13	; unfilled bytes field

namlen	equ	15	; name length
reccnt	equ	15	; record count field
dskmap	equ	16	; disk map field
nxtrec	equ	fcblen

;	utility functions for file access

dm$position:
	; Compute disk map position for vrecord to hl
	lxi h,blkshf! mov c,m ; shift count to c
	lda vrecord ; current virtual record to a
	dmpos0:
		ora a! rar! dcr c! jnz dmpos0
	; a = shr(vrecord,blkshf) = vrecord/2**(sect/block)
	mov b,a ; Save it for later addition
	mvi a,8! sub m ; 8-blkshf to accumulator
	mov c,a ; extent shift count in register c
	lda extval ; extent value ani extmsk
	dmpos1:
		; blkshf = 3,4,5,6,7, c=5,4,3,2,1
		; shift is 4,3,2,1,0
		dcr c! jz dmpos2
		ora a! ral! jmp dmpos1
	dmpos2:
	; Arrive here with a = shl(ext and extmsk,7-blkshf)
	add b ; Add the previous shr(vrecord,blkshf) value
	; a is one of the following values, depending upon alloc
	; bks blkshf
	; 1k   3     v/8 + extval * 16
	; 2k   4     v/16+ extval * 8
	; 4k   5     v/32+ extval * 4
	; 8k   6     v/64+ extval * 2
	; 16k  7     v/128+extval * 1
	ret ; with dm$position in a

getdma:
	lhld info! lxi d,dskmap! dad d! ret

getdm:
	; Return disk map value from position given by bc
	call getdma
	dad b ; Index by a single byte value
	lda single ; single byte/map entry?
	ora a! jz getdmd ; Get disk map single byte
		mov l,m! mov h,b! ret ; with hl=00bb
	getdmd:
		dad b ; hl=.fcb(dm+i*2)
		; double precision value returned
		mov a,m! inx h! mov h,m! mov l,a! ret

index:
	; Compute disk block number from current fcb
	call dm$position ; 0...15 in register a
	sta dminx
	mov c,a! mvi b,0! call getdm ; value to hl
	shld arecord! mov a,l! ora h! ret

atran:
	; Compute actual record address, assuming index called

;	arecord = shl(arecord,blkshf)

	lda blkshf! mov c,a
	lhld arecord! xra a! call shl3bv
	shld arecord! sta arecord+2

	shld arecord1 ; Save low(arecord)

;	arecord = arecord or (vrecord and blkmsk)

	lda blkmsk! mov c,a! lda vrecord! ana c
	mov b,a ; Save vrecord & blkmsk in reg b & blk$off
	sta blk$off
	lxi h,arecord! ora m! mov m,a! ret


getexta:
	; Get current extent field address to hl
	lhld info! lxi d,extnum! dad d ; hl=.fcb(extnum)
	ret

getrcnta:
	; Get reccnt address to hl
	lhld info! lxi d,reccnt! dad d! ret

getfcba:
	; Compute reccnt and nxtrec addresses for get/setfcb
	call getrcnta! xchg ; de=.fcb(reccnt)
	lxi h,(nxtrec-reccnt)! dad d ; hl=.fcb(nxtrec) 
	ret

getfcb:
	; Set variables from currently addressed fcb
	call getfcba ; addresses in de, hl
	mov a,m! sta vrecord ; vrecord=fcb(nxtrec)
	xchg! mov a,m! sta rcount ; rcount=fcb(reccnt)
	call getexta ; hl=.fcb(extnum)
	lda extmsk ; extent mask to a
	ana m ; fcb(extnum) and extmsk
	sta extval
	ret

setfcb:
					; Place values back into current fcb
	call getfcba 			; addresses to de, hl
	mvi c,1

	lda vrecord! add c! mov m,a 	; fcb(nxtrec)=vrecord+seqio
	xchg! lda rcount! mov m,a 	; fcb(reccnt)=rcount
	ret

hlrotr:
					; hl rotate right by amount c
	inr c 				; in case zero
	hlrotr0: dcr c! rz 		; return when zero

	mov a,h! ora a! rar! mov h,a 	; high byte
	mov a,l! rar! mov l,a 		; low byte
	jmp hlrotr0

hlrotl:
				 	; Rotate the mask in hl by amount in c
 	inr c 				; may be zero
 	hlrotl0: dcr c! rz 		; return if zero

	dad h! jmp hlrotl0

set$cdisk:
				; Set a "1" value in curdsk position of bc
	lda seldsk
	push b 				; Save input parameter
	mov c,a 			; Ready parameter for shift
	lxi h,1 			; number to shift
	call hlrotl 			; hl = mask to integrate
	pop b 				; original mask
	mov a,c! ora l! mov l,a
	mov a,b! ora h! mov h,a 	; hl = mask or rol(1,curdsk)
	ret

test$vector:
	lda seldsk
	mov c,a! call hlrotr
	mov a,l! ani 1b! ret 		; non zero if curdsk bit on

getdptra:
			; Compute the address of a directory element at
			; positon dptr in the buffer

	lhld buffa! lda dptr
					; hl = hl + a
	add l! mov l,a! rnc
					; overflow to h
	inr h! ret

clr$ext:
			; fcb ext = fcb ext & 1fh

	call getexta! mov a,m! ani 0001$1111b! mov m,a!
	ret


subdh:
			; Compute hl = de - hl
	mov a,e! sub l! mov l,a! mov a,d! sbb h! mov h,a
	ret

get$buffa:
	push d! lxi d,10! dad d
	mov e,m! inx h! mov d,m
	xchg! pop d! ret


rddir:
			; Read a directory entry into the directory buffer
	call seek$dir
	lda phymsk! ora a! jz rddir1
	mvi a,3
	call deblock$dir! jmp setdata

rddir1:
	call setdir 				; directory dma
	shld buffa! call seek
	call rdbuff 				; directory record loaded

setdata:
			; Set data dma address
	lhld dmaad! jmp setdma 			; to complete the call

setdir:
			; Set directory dma address

	lhld dirbcba
	call get$buffa

setdma:
			; hl=.dma address to set (i.e., buffa or dmaad)
	shld curdma! ret

end$of$dir:
			; Return zero flag if at end of directory, non zero
			; if not at end (end of dir if dcnt = 0ffffh)
	lxi h,dcnt
	mov a,m 			; may be 0ffh
	inx h! cmp m 			; low(dcnt) = high(dcnt)?
	rnz 				; non zero returned if different
					; high and low the same, = 0ffh?
	inr a 				; 0ffh becomes 00 if so
	ret

set$end$dir:
			; Set dcnt to the end of the directory
	lxi h,enddir! shld dcnt! ret


read$dir:
		; Read next directory entry, with c=true if initializing

	lhld dirmax! xchg 		; in preparation for subtract
	lhld dcnt! inx h! shld dcnt 	; dcnt=dcnt+1

					; while(dirmax >= dcnt)
	call subdh 			; de-hl
	jc set$end$dir
				; not at end of directory, seek next element
				; initialization flag is in c

		lda dcnt! ani dskmsk 	; low(dcnt) and dskmsk
		mvi b,fcbshf 		; to multiply by fcb size

	read$dir1:
		add a! dcr b! jnz read$dir1
					; a = (low(dcnt) and dskmsk) shl fcbshf
		sta dptr 		; ready for next dir operation
		ora a! rnz 		; Return if not a new record

		push b 			; Save initialization flag c
		call rd$dir 		; Read the directory record
		pop b 			; Recall initialization flag
		ret
compext:
			; Compare extent# in a with that in c, return nonzero
			; if they do not match
	push b 				; Save c's original value
	push psw! lda extmsk! cma! mov b,a
					; b has negated form of extent mask
	mov a,c! ana b! mov c,a 	; low bits removed from c
	pop psw! ana b 			; low bits removed from a
	sub c! ani maxext 		; Set flags
	pop b 				; Restore original values
	ret

get$dir$ext:
			; Compute directory extent from fcb
			; Scan fcb disk map backwards
	call getfcba 	; hl = .fcb(vrecord)
	mvi c,16! mov b,c! inr c! push b
			; b=dskmap pos (rel to 0)
get$de0:
	pop b
	dcr c
	xra a 				; Compare to zero
get$de1:
	dcx h! dcr b			; Decr dskmap position
	cmp m! jnz get$de2 		; fcb(dskmap(b)) ~= 0
	dcr c! jnz get$de1
				; c = 0 -> all blocks = 0 in fcb disk map
get$de2:
	mov a,c! sta dminx
	lda single! ora a! mov a,b
	jnz get$de3
	rar 				; not single, divide blk idx by 2
get$de3:
	push b! push h 			; Save dskmap position & count
	mov l,a! mvi h,0 		; hl = non-zero blk idx
					; Compute ext offset from last non-zero
					; block index by shifting blk idx right
					; 7 - blkshf
	lda blkshf! mov d,a! mvi a,7! sub d
	mov c,a! call hlrotr! mov b,l
					; b = ext offset
	lda extmsk! cmp b! pop h! jc get$de0
				; Verify computed extent offset <= extmsk
	call getexta! mov c,m
	cma! ani maxext! ana c! ora b
		; dir ext = (fcb ext & (~ extmsk) & maxext) | ext offset
	pop b 				; Restore stack
	ret 				; a = directory extent


search:
			; Search for directory element of length c at info
	lhld info! shld searcha 	; searcha = info
	mov a,c! sta searchl 		; searchl = c

	call set$end$dir 		; dcnt = enddir
	call home 			; to start at the beginning

searchn:
			; Search for the next directory element, assuming
			; a previous call on search which sets searcha and
			; searchl

	mvi c,false! call read$dir 	; Read next dir element
	call end$of$dir! jz lret$eq$ff
					; not end of directory, scan for match
	lhld searcha! xchg 		; de=beginning of user fcb

		call getdptra 		; hl = buffa+dptr
		lda searchl! mov c,a 	; length of search to c
		mvi b,0 		; b counts up, c counts down

		mov a,m! cpi empty! jz searchn

  searchloop:
			mov a,c! ora a! jz endsearch
					; Scan next character if not ubytes
			mov a,b! cpi ubytes! jz searchok
					; not the ubytes field, extent field?
			cpi extnum 	; may be extent field
			jz searchext 	; Skip to search extent
			ldax d
			sub m! ani 7fh 	; Mask-out flags/extent modulus
			jnz searchn 	; Skip if not matched
			jmp searchok 	; matched character
		searchext:
			ldax d
					; Attempt an extent # match
			push b 		; Save counters
			mov c,m 	; directory character to c
			call compext 	; Compare user/dir char
			pop b 		; Recall counters
			ora a 		; Set flag
			jnz searchn 	; Skip if no match
		searchok:
					; current character matches
			inx d! inx h! inr b! dcr c
			jmp searchloop
		endsearch:
				; entire name matches, return dir position
			xra a
			sta lret 	; lret = 0
					; successful search -
					; return with zero flag reset
			mov b,a! inr b
			ret
		lret$eq$ff:
					; unsuccessful search -
					; return with zero flag set
					; lret,low(aret) = 0ffh
			mvi a,255 ! mov b,a ! inr b ! jmp sta$ret

open:
			; Search for the directory entry, copy to fcb
	mvi c,namlen! call search
	rz 				; Return with lret=255 if end

			; not end of directory, copy fcb information
open$copy:
	call getexta ! mov a,m ! push a	; save extent to check for extent
					; folding - move moves entire dir FCB
	call getdptra! xchg 		; hl = .buff(dptr)
	lhld info 			; hl=.fcb(0)
	mvi c,nxtrec 			; length of move operation
	call move 			; from .buff(dptr) to .fcb(0)

			; Note that entire fcb is copied, including indicators

	call get$dir$ext! mov c,a
	pop a ! mov m,a			; restore extent

		; hl = .user extent#, c = dir extent#
		; above move set fcb(reccnt) to dir(reccnt)
		; if fcb ext < dir ext then fcb(reccnt) = fcb(reccnt) | 128
		; if fcb ext = dir ext then fcb(reccnt) = fcb(reccnt)
		; if fcb ext > dir ext then fcb(reccnt) = 0

set$rc: 				; hl=.fcb(ext), c=dirext
	mvi b,0
	xchg! lxi h,(reccnt-extnum)! dad d
	ldax d! sub c! jz set$rc2
	mov a,b! jnc set$rc1
	mvi a,128! mov b,m

  set$rc1:
		mov m,a! mov a,b! sta actual$rc! ret 
  set$rc2:
		sta actual$rc
		mov a,m! ora a! rnz 	; ret if rc ~= 0
		lda dminx! ora a! rz 	; ret if no blks in fcb
		lda fx! cpi 15! rz 	; ret if fx = 15
		mvi m,128 		; rc = 128
		ret

restore$rc:
			; hl = .fcb(extnum)
			; if actual$rc ~= 0 then rcount = actual$rc
	push h
	lda actual$rc! ora a! jz restore$rc1
	lxi d,(reccnt-extnum)! dad d
	mov m,a! xra a! sta actual$rc

restore$rc1:
	pop h! ret

open$reel:
		; Close the current extent, and open the next one
		; if possible.

	call getexta
	mov a,m! mov c,a
	inr c! call compext
	jz open$reel3

	mvi a,maxext! ana c! mov m,a 		; Incr extent field
	mvi c,namlen! call search 		; Next extent found?
						; not end of file, open
	call open$copy

  open$reel2:
		call getfcb 			; Set parameters
		xra a! sta vrecord! jmp sta$ret ; lret = 0
  open$reel3:
		inr m 				; fcb(ex) = fcb(ex) + 1
		call get$dir$ext! mov c,a
						; Is new extent beyond dir$ext?
		cmp m! jnc open$reel4 		; no
		dcr m 				; fcb(ex) = fcb(ex) - 1
		jmp set$lret1
  open$reel4:
		call restore$rc
		call set$rc! jmp open$reel2

seqdiskread:
			; Sequential disk read operation
			; Read the next record from the current fcb

	call getfcb 				; sets parameters for the read

	lda vrecord! lxi h,rcount! cmp m 	; vrecord-rcount
						; Skip if rcount > vrecord
	jc recordok

				; not enough records in the extent
				; record count must be 128 to continue
		cpi 128 			; vrecord = 128?
		jnz setlret1 			; Skip if vrecord<>128
		call open$reel 			; Go to next extent if so
						; Check for open ok
		lda lret! ora a! jnz setlret1 	; Stop at eof

  recordok:
			; Arrive with fcb addressing a record to read

		call index 			; Z flag set if arecord = 0

		jz setlret1 			; Reading unwritten data

						; Record has been allocated
		call atran 			; arecord now a disk address

		lda phymsk! ora a		; if not 128 byte sectors
		jnz read$deblock		; go to deblock

		call setdata			; Set curdma = dmaad
		call seek			; Set up for read
		call rdbuff			; Read into (curdma)
		jmp setfcb			; Update FCB

curselect:
	lda seldsk! inr a! jz sel$error
	dcr a! lxi h,curdsk! cmp m! rz

				; Skip if seldsk = curdsk, fall into select
select:
			; Select disk info for subsequent input or output ops
	mov m,a 				; curdsk = seldsk

	mov d,a 		; Save seldsk in register D for selectdisk call
	lhld dlog! call test$vector 	; test$vector does not modify DE
	mov e,a! push d 		; Send to seldsk, save for test below
	call selectdisk! pop h 		; Recall dlog vector
	jnc sel$error			; returns with C flag set if select ok
					; Is the disk logged in?
	dcr l 				; reg l = 1 if so
	rz 				; yes - drive previously logged in

	lhld dlog! mov c,l! mov b,h 	; call ready
	call set$cdisk! shld dlog 	; dlog=set$cdisk(dlog)
	ret

set$seldsk:
	lda linfo! sta seldsk! ret

reselectx:
	xra a! sta high$ext! jmp reselect1
reselect:
			; Check current fcb to see if reselection necessary
	mvi a,80h! mov b,a! dcr a! mov c,a 	; b = 80h, c = 7fh
	lhld info! lxi d,7! xchg! dad d
	mov a,m! ana b
						; fcb(7) = fcb(7) & 7fh
	mov a,m! ana c! mov m,a
						; high$ext = 80h & fcb(8)
	inx h! mov a,m! ana b! sta high$ext
						; fcb(8) = fcb(8) & 7fh
	mov a,m! ana c! mov m,a
						; fcb(ext) = fcb(ext) & 1fh
	call clr$ext

	; if fcb(rc) & 80h 
	;    then fcb(rc) = 80h, actual$rc = fcb(rc) & 7fh
	;    else actual$rc = 0

	call getrcnta! mov a,m! ana b! jz reselect1
	mov a,m! ana c! mov m,b

reselect1:
	sta actual$rc

	lxi h,0
	shld fcbdsk 				; fcbdsk = 0
	mvi a,true! sta resel 			; Mark possible reselect
	lhld info! mov a,m 			; drive select code
	ani 1$1111b 				; non zero is auto drive select
	dcr a 			; Drive code normalized to 0..30, or 255
	sta linfo 				; Save drive code
	cpi 0ffh! jz noselect
				; auto select function, seldsk saved above
	mov a,m! sta fcbdsk 			; Save drive code
	call set$seldsk

  noselect:
		call curselect
		mvi a,0 ! lhld info ! mov m,a
		ret

;
;	individual function handlers
;

func12	equ func$ret

func13:

			; Reset disk system - initialize to disk 0
	lxi h,0! shld dlog

	xra a! sta seldsk
	dcr a! sta curdsk

	lxi h,tbuff! shld dmaad 		; dmaad = tbuff
        jmp setdata 				; to data dma address

func14:	
			; Select disk info
	call set$seldsk 			; seldsk = linfo
	jmp curselect

func15:
			; Open file
	call reselectx
	call open! call openx 		; returns if unsuccessful, a = 0
	ret

openx:
	call end$of$dir! rz
	call getfcba! mov a,m! inr a! jnz openxa
	dcx d! dcx d! ldax d! mov m,a
openxa:
						; open successful
	pop h 					; Discard return address
	mvi c,0100$0000b
	ret

func16	equ func$ret

func17	equ func$ret

func18	equ func$ret

func19	equ func$ret

func20:
				; Read a file
	call reselect
	jmp seqdiskread

func21	equ func$ret

func22	equ func$ret

func23	equ func$ret

func24	equ func$ret

func25: lda seldsk ! jmp sta$ret

func26:	xchg ! shld dmaad
	jmp setdata

func27	equ func$ret

func28:	equ func$ret

func29	equ func$ret

func30	equ func$ret

func31	equ func$ret

func32	equ func$ret

func33	equ func$ret

func34	equ func$ret

func35	equ func$ret

func36	equ func$ret

func37	equ func$ret

func38	equ func$ret

func39	equ func$ret

func40  equ func$ret

func42	equ func$ret

func43	equ func$ret

func44	equ func$ret

func45	equ func$ret

func46	equ func$ret

func47	equ	func$ret

func48	equ func$ret

func49	equ	func$ret

func50	equ	func$ret

func100	equ func$ret	

func101	equ func$ret

func102	equ func$ret

func103	equ func$ret

func104	equ func$ret

func105	equ func$ret

func106	equ func$ret

func107	equ func$ret

func108	equ func$ret

func109	equ func$ret


goback:
			; Arrive here at end of processing to return to user
	lda fx! cpi 15! jc retmon
	lda olddsk! sta seldsk 			; Restore seldsk
	lda resel! ora a! jz retmon

	lhld info! mvi m,0 ; fcb(0)=0
	lda fcbdsk! ora a! jz goback1
						; Restore fcb(0)
	mov m,a 				; fcb(0)=fcbdsk
  goback1:
						; fcb(8) = fcb(8) | high$ext
	inx h! lda high$ext! ora m! mov m,a
						; fcb(rc) = fcb(rc) | actual$rc
	call getrcnta! lda actual$rc! ora m! mov m,a
						; return from the disk monitor
retmon:
	lhld entsp! sphl
	lhld aret! mov a,l! mov b,h
	ret
;
;	data areas
;
dlog:	dw	0	; logged-in disks
curdma	ds	word	; current dma address
buffa:	ds	word	; pointer to directory dma address

;
;	curtrka - alloca are set upon disk select
;	(data must be adjacent, do not insert variables)
;	(address of translate vector, not used)
cdrmaxa:ds	word	; pointer to cur dir max value (2 bytes)
curtrka:ds	word	; current track address (2)
curreca:ds	word	; current record address (3)
drvlbla:ds	word	; current drive label byte address (1)
lsn$add:ds	word	; login sequence # address (1)
			; +1 -> bios media change flag (1)
dpbaddr:ds	word	; current disk parameter block address
checka:	ds	word	; current checksum vector address
alloca:	ds	word	; current allocation vector address
dirbcba:ds	word	; dir bcb list head
dtabcba:ds	word	; data bcb list head
hash$tbla:
	ds	word
	ds	byte

addlist	equ	$-dpbaddr	; address list size

;
; 	       buffer control block format
;
; bcb format : drv(1) || rec(3) || pend(1) || sequence(1) ||
;	       0         1         4          5
;
;	       track(2) || sector(2) || buffer$add(2) ||
;	       6           8            10
;
;	       link(2)
;	       12
;

;	sectpt - offset obtained from disk parm block at dpbaddr
;	(data must be adjacent, do not insert variables)
sectpt:	ds	word	; sectors per track
blkshf:	ds	byte	; block shift factor
blkmsk:	ds	byte	; block mask
extmsk:	ds	byte	; extent mask
maxall:	ds	word	; maximum allocation number
dirmax:	ds	word	; largest directory number
dirblk:	ds	word	; reserved allocation bits for directory
chksiz:	ds	word	; size of checksum vector
offset:	ds	word	; offset tracks at beginning
physhf:	ds	byte	; physical record shift
phymsk:	ds	byte	; physical record mask
dpblist	equ	$-sectpt	; size of area
;
;	local variables
;
blk$off:	ds	byte	; record offset within block
dir$cnt:	ds	byte	; direct i/o count

tranv:	ds	word	; address of translate vector
linfo:	ds	byte	; low(info)
dminx:	ds	byte	; local for diskwrite

actual$rc:
	ds	byte	; directory ext record count

single:	ds	byte	; set true if single byte allocation map


olddsk:	ds	byte	; disk on entry to bdos
rcount:	ds	byte	; record count in current fcb
extval:	ds	byte	; extent number and extmsk

vrecord:ds	byte	; current virtual record

curdsk:

adrive: db	0ffh	; current disk
arecord:ds	word	; current actual record
	ds	byte

arecord1:	ds	word	; current actual block# * blkmsk

;******** following variable order critical *****************

high$ext:	ds	byte	; fcb high ext bits
;xfcb$read$only:	ds	byte

;	local variables for directory access
dptr:	ds	byte	; directory pointer 0,1,2,3

;
;	local variables initialized by bdos at entry
;
fcbdsk:		ds	byte	; disk named in fcb

phy$off:	ds	byte
curbcba:	ds	word

track:		ds	word
sector:		ds	word

read$deblock:
	mvi a,1! call deblock$dta
	jmp setfcb

column		db 	0
outdelim:	db	'$'

dmaad:		dw	0080h
seldsk:		db	0
info:		dw	0
resel:		db	0
fx:		db	0
dcnt:		dw	0
searcha:	dw	0
searchl:	db	0


; 	**************************
; 	Blocking/Deblocking Module
;	**************************

deblock$dir:

	lhld dirbcba

	jmp deblock

deblock$dta:
	lhld dtabcba

deblock:

	; BDOS Blocking/Deblocking routine
	; a = 1 -> read command
	; a = 2 -> write command
	; a = 3 -> locate command
	; a = 4 -> flush command
	; a = 5 -> directory update

	push a 				; Save z flag and deblock fx

					; phy$off = low(arecord) & phymsk
					; low(arecord) = low(arecord) & ~phymsk
	call deblock8
	lda arecord! mov e,a! ana b! sta phy$off
	mov a,e! ana c! sta arecord

	shld curbcba! call getbuffa! shld curdma

	call deblock9
					; Is command flush?
	pop a! push a! cpi 4
	jnc deblock1 			; yes
					; Is referenced physical record 
					;already in buffer?
	call compare! jz deblock45 	; yes
	xra a
deblock1:
	call deblock10
					; Read physical record buffer
	mvi a,2! call deblock$io

	call deblock9 			; phypfx = adrive || arecord
	call move! mvi m,0 		; zero pending flag

deblock45:
					; recadd = phybuffa + phy$off*80h
	lda phy$off! inr a! lxi d,80h! lxi h,0ff80h
deblock5:
	dad d! dcr a! jnz deblock5
	xchg! lhld curdma! dad d
					; If deblock command = locate
					; then buffa = recadd; return
	pop a! cpi 3! jnz deblock6
	shld buffa! ret
deblock6:
	xchg! lhld dmaad! lxi b,80h
					; If deblock command = read
	jmp move$tpa 			; then move to dma

deblock8:
	lda phymsk! mov b,a! cma! mov c,a! ret

deblock9:
	lhld curbcba! lxi d,adrive! mvi c,4! ret

deblock10:
	lxi d,4
deblock11:
	lhld curbcba! dad d! ret

deblock$io:
					; a = 0 -> seek only
					; a = 1 -> write
					; a = 2 -> read
	push a! call seek
	pop a! dcr a
	cp rdbuff
					; Move track & sector to bcb
	call deblock10! inx h! inx h
	lxi d,track! mvi c,4! jmp move

	org	base+((($-base)+255) and 0ff00h)-1
	db	0

; Bios equates

bios$pg		equ	$

bootf		equ	bios$pg+00	; 00. cold boot
conoutf		equ	bios$pg+12	; 04. console output function
homef		equ	bios$pg+24	; 08. disk home function
seldskf		equ	bios$pg+27	; 09. select disk function
settrkf		equ	bios$pg+30	; 10. set track function
setsecf		equ	bios$pg+33	; 11. set sector function
setdmaf		equ	bios$pg+36	; 12. set dma function
sectran		equ	bios$pg+48	; 16. sector translate
movef		equ	bios$pg+75	; 25. memory move function
readf		equ	bios$pg+39	; 13. read disk function
move$out	equ	movef
move$tpa	equ	movef

		end
