; Altirra AcidOS test suite
; Copyright (C) 2010 Avery Lee, All Rights Reserved.
;
; Permission is hereby granted, free of charge, to any person obtaining a copy
; of this software and associated documentation files (the "Software"), to deal
; in the Software without restriction, including without limitation the rights
; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
; copies of the Software, and to permit persons to whom the Software is
; furnished to do so, subject to the following conditions:
;
; The above copyright notice and this permission notice shall be included in
; all copies or substantial portions of the Software.
;
; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
; SOFTWARE. 

		opt		h-o-
		org		$0043
zbufp	dta		a(0)		;Buffer pointer
zdrva	dta		a(0)		;Drive pointer
zsba	dta		a(0)		;Sector buffer pointer
errno	dta		0			;Error number

		icl		'hardware.inc'
		icl		'kerneldb.inc'
		icl		'cio.inc'
		icl		'sio.inc'
		
runad	equ		$02e0
initad	equ		$02e2
dskinv	equ		$e453
ciov	equ		$e456

		opt		o+
		org		$0700
		
base:
		dta		0			;flags
		dta		3
		dta		a($0700)
		dta		a(DOSInit)
		jmp		boot					;$0706 ($0714)
bcb_maxfiles	dta		3				;$0709 ($03)
bcb_drivebits	dta		1				;$070A ($03)
bcb_allocdirc	dta		0				;$070B ($00) (unused)
bcb_secbuf		dta		a(dos_sectorbuffers)	;$070C ($1A7C)
bcb_bootflag	dta		$01				;$070E ($01)
bcb_firstsec	dta		a(4)			;$070F ($0004) first sector of DOS.SYS
bcb_linkoffset	dta		125				;$0711 ($7D)
bcb_loadaddr	dta		a($0880)		;$0712 ($07CB)
		
boot:
		;replace serial vectors
		ldx		#3
		mva:rpl	newvecs,x vserin,x-
		
		;read sectors
		mwa		bcb_loadaddr dbuflo
		mva		#'R' dcomnd

		lda		bcb_firstsec
		ldx		bcb_firstsec+1
next_sector:
		sta		daux1
		stx		daux2
		jsr		dskinv
		bmi		error
		
		lda		dbuflo
		sta		zsba
		clc
		adc		bcb_linkoffset
		sta		dbuflo
		lda		dbufhi
		sta		zsba+1
		adc		#0
		sta		dbufhi
		
		ldy		bcb_linkoffset
		lda		(zsba),y
		and		#$03
		tax
		iny
		lda		(zsba),y
		bne		next_sector
		cpx		#0
		bne		next_sector
		clc
		rts
error:
		sec
		rts
		
newvecs:
		dta		a(SIOInputReadyHandler)
		dta		a(SIOOutputReadyHandler)

;==============================================================================
; SIO serial input routine
;
; DOS 2.0S replaces (VSERIN), so it's critical that this routine follow the
; rules compatible with DOS. The rules are as follows:
;
;	BUFRLO/BUFRHI:	Points to next byte to read. Note that this is different
;					from (VSEROR)!
;	BFENLO/BFENHI:	Points one after last byte in buffer.
;	BUFRFL:			Set when all data bytes have been read.
;	NOCKSM:			Set if no checksum byte is expected. Cleared after checked.
;	RECVDN:			Set when receive is complete, including any checksum.	
;
.proc SIOInputReadyHandler
	lda		bufrfl
	bne		receiveChecksum

	;receive data byte
	tya
	pha
	lda		serin
	ldy		#$00
	sta		(bufrlo),y
	clc
	adc		chksum
	adc		#$00
	sta		chksum
	
	;bump buffer pointer
	inw		bufrlo
	
	;check for EOB
	lda		bufrlo
	cmp		bfenlo
	beq		possiblyEnd
xit:
	pla
	tay
	pla
	rti
	
receiveChecksum:
	;read and compare checksum
	lda		serin
	cmp		chksum
	beq		checksumOK
	
	mva		#SIOErrorChecksum	status
checksumOK:
	
	;set receive done flag
	mva		#$ff	recvdn

	;exit
	pla
	rti
	
possiblyEnd:	
	lda		bufrhi
	cmp		bfenhi
	bne		xit

	mva		#$ff	bufrfl
	
	;should there be a checksum?
	lda		nocksm
	bne		skipChecksum
	jmp		xit

skipChecksum:
	;set receive done flag
	sta		recvdn
	
	;clear no checksum flag
	lda		#0
	sta		nocksm
	jmp		xit
.endp

;==============================================================================
; SIO serial output ready routine
;
; DOS 2.0S replaces (VSEROR), so it's critical that this routine follow the
; rules compatible with DOS. The rules are as follows:
;
;	BUFRLO/BUFRHI:	On entry, points to one LESS than the next byte to write.
;	BFENLO/BFENHI:	Points to byte immediately after buffer.
;	CHKSUM:			Holds running checksum as bytes are output.
;	CHKSNT:			$00 if checksum not yet sent, $FF if checksum sent.
;	POKMSK:			Used to enable the serial output complete IRQ after sending
;					checksum.
;
.proc SIOOutputReadyHandler
		;increment buffer pointer
		inc		bufrlo
		bne		addrcc
		inc		bufrhi
addrcc:

		;compare against buffer end
		lda		bufrlo
		cmp		bfenlo
		lda		bufrhi
		sbc		bfenhi			;set flags according to (dst - end)
		bcs		doChecksum

		;save Y
		tya
		pha

		;send out next byte
		ldy		#0
		lda		(bufrlo),y
		sta		serout
		
		;update checksum
		adc		chksum
		adc		#0
		sta		chksum

		;restore registers and exit
		pla
		tay
		pla
		rti
		
doChecksum:
		;send checksum
		lda		chksum
		sta		serout
		
		;set checksum sent flag
		mva		#$ff	chksnt
		
		;enable output complete IRQ and disable serial output IRQ
		lda		pokmsk
		ora		#$08
		and		#$ef
		sta		pokmsk
		sta		irqen
		
		pla
		rti
.endp

;==========================================================================
.proc DOSInit
		ldx		#0
search_loop:
		lda		hatabs,x
		beq		found_slot
		inx
		inx
		inx
		cpx		#33
		bne		search_loop
		rts
		
found_slot:
		mva		#'D' hatabs,x
		mwa		#DOSHandlerTable hatabs+1,x
		
		;set MEMLO
		ldx		bcb_maxfiles
		inx
		txa
		asl
		tay
		lda		#0
		ror
		adc		#<dos_sectorbuffers
		sta		memlo
		tya
		adc		#>dos_sectorbuffers
		sta		memlo+1
		
		;attempt to load AUTORUN.SYS with IOCB #1
		mva		#CIOCmdOpen iccmd+$10
		mwa		#autorunsys_fname icbal+$10
		mva		#$04 icax1+$10
		ldx		#$10
		jsr		ciov
		bmi		load_failed
		
		;load the executable
		jsr		DOSLoadExecutable
		bcs		load_failed
		
		jsr		DOSRun.close_iocb
		jmp		(runad)
		
load_failed:
		;close IOCB #1
		jsr		DOSRun.close_iocb
		
		clc
		rts
		
autorunsys_fname:
		dta		'D:AUTORUN.SYS',$9b
.endp

;==========================================================================
.proc DOSRun
		;attempt to load DUP.SYS with IOCB #1
		mva		#CIOCmdOpen iccmd+$10
		mwa		#dupsys_fname icbal+$10
		mva		#$04 icax1+$10
		ldx		#$10
		jsr		ciov
		bmi		load_failed
		
		;load the executable
		jsr		DOSLoadExecutable
		bcs		load_failed
		
		jsr		close_iocb
		jmp		(runad)
		
load_failed:
		;close IOCB #1
		jsr		close_iocb
		clc
		rts
		
dupsys_fname:
		dta		'D:DUP.SYS',$9b

close_iocb:
		mva		#CIOCmdClose iccmd+$10
		ldx		#$10
		jmp		ciov
.endp

;==========================================================================
.proc DOSLoadExecutable
		mwa		#default_run runad
segment_loop:
		mwa		#default_run initad
		
		mwa		#dos_segadrs icbal+$10
		mwa		#2 icbll+$10
		mva		#CIOCmdGetChars iccmd+$10
		jsr		ciov
		bpl		startaddr_ok
		cpy		#CIOStatEndOfFile
		bne		load_error
		clc
		rts

startaddr_ok:
		;check for $FFFF marker
		lda		dos_segadrs
		and		dos_segadrs+1
		cmp		#$ff
		beq		segment_loop

		mwa		#dos_segadrs+2 icbal+$10
		jsr		ciov
		bmi		load_error
		
		sbw		dos_segadrs+2 dos_segadrs icbll+$10
		inw		icbll+$10
		mwa		dos_segadrs icbal+$10
		jsr		ciov
		bmi		load_error
		
		jsr		do_init
		jmp		segment_loop
		
default_run:
load_error:
		sec
		rts
		
do_init:
		jmp		(initad)
.endp

;==========================================================================
.struct FCB
secbuf	.byte
offset	.byte
sector	.word
pad1	.dword
pad2	.dword
pad3	.dword
.ends

;==========================================================================
dos_fcb_table dta FCB [8] (255,0,0,0,0,0)

dos_secbuf_seclo:
		:8 dta 0

dos_secbuf_sechi:
		:8 dta 0
		
dos_secbuf_fcbidx:
		:8 dta $ff
		
dos_filename:
		:11 dta 0
		
dos_iocb	dta		0
dos_dsidx	dta		0			;dirent scan: directory sector index
dos_secbidx	dta		0
dos_seclo	dta		0
dos_sechi	dta		0
dos_segadrs	dta		a(0), a(0)

;==========================================================================
.proc DOSHandlerTable
		dta		a(DOSOpen-1)
		dta		a(DOSClose-1)
		dta		a(DOSGetByte-1)
		dta		a(DOSPutByte-1)
		dta		a(DOSGetStatus-1)
		dta		a(DOSSpecial-1)
.endp

;==========================================================================
.proc DOSOpen
		;check if FCB is already open
		lda		dos_fcb_table[0].secbuf
		bmi		fcb_closed
		ldy		#CIOStatIOCBInUse
		rts

too_many_open:
		ldy		#CIOStatTooManyFiles
		rts

fnerr:
		ldy		#CIOStatFileNameErr
		rts

fcb_closed:
		;try to find an available sector buffer
		jsr		DOSFindOpenBuffer
		beq		too_many_open

		ldy		#0
		lda		(icbalz),y
		cmp		#'D'
		beq		drvlet_ok
		ldy		#CIOStatUnkDevice
drvlet_ok:
		iny
		
		;validate optional digit
		lda		(icbalz),y
		cmp		#'1'
		bcc		not_digit
		cmp		#'9'+1
		bcs		not_digit
		iny
		lda		(icbalz),y
not_digit:

		;validate colon
		cmp		#':'
		bne		fnerr
		iny
		
		;parse filename
		stx		dos_iocb
		ldx		#0

		lda		(icbalz),y
		cmp		#'*'
		bne		filename_notstar
filename_starfill:
		lda		#'?'
		bne		filename_fill_loop
filename_notstar:
		jsr		DOSIsupperWild
filename_1stok:
		sta		dos_filename,x
		inx
		iny
		
filename_loop:
		lda		(icbalz),y
		cmp		#'*'
		beq		filename_starfill
		jsr		DOSIsalnumWild
		bcs		filename_done
		sta		dos_filename,x
		iny
		inx
		cpx		#8
		bne		filename_loop
		
filename_done:
		;pad filename to spaces
		lda		#' '
filename_fill_loop:
		cpx		#8
		beq		filename_full
		sta		dos_filename,x
		inx
		bne		filename_fill_loop
filename_full:

		lda		(icbalz),y
		cmp		#'.'
		bne		ext_done
		iny

		;parse extension
ext_loop:
		lda		(icbalz),y
		cmp		#'*'
		bne		ext_notstar
		lda		#'?'
		bne		ext_fill_loop
ext_notstar:
		jsr		DOSIsalnumWild
		bcs		ext_done
		sta		dos_filename,x
		iny
		inx
		cpx		#11
		bne		ext_loop
		
ext_done:
		;pad filename to spaces
		lda		#' '
ext_fill_loop:
		cpx		#11
		beq		ext_full
		sta		dos_filename,x
		inx
		bne		ext_fill_loop
ext_full:

		;search directory
		jsr		DOSSearchFilename
		bpl		found
		rts
		
found:
		;restore IOCB#
		ldx		dos_iocb
		
		;set up FCB
		clc
		adc		#3
		tay
		lda		(zsba),y
		sta		dos_fcb_table[0].sector,x
		iny
		lda		(zsba),y
		sta		dos_fcb_table[0].sector+1,x
		
		lda		#0
		sta		dos_fcb_table[0].offset,x

		;find open sector buffer (we confirmed there was one earlier)
		jsr		DOSFindOpenBuffer
		sta		dos_fcb_table[0].secbuf,x
		txa
		sta		dos_secbuf_fcbidx,y
		
		;read first sector
		ldy		dos_fcb_table[0].sector+1,x
		lda		dos_fcb_table[0].sector,x
		pha
		lda		dos_fcb_table[0].secbuf,x
		tax
		pla
		jsr		DOSReadSector

		rts
.endp

;==========================================================================
.proc DOSFindOpenBuffer
		ldy		bcb_maxfiles
search_loop:
		lda		dos_secbuf_fcbidx,y
		bmi		found_unused
		dey
		bne		search_loop
found_unused:
		tya
		rts
.endp

;==========================================================================
; Result:
;	C=0		Alphanumeric or ?
;	C=1		Not alphanumeric or ?
;
DOSIsupperWild = DOSIsalnumWild.isupperwild_entry
.proc DOSIsalnumWild
		cmp		#'0'
		bcc		not_digit
		cmp		#'9'+1
		bcc		done
not_digit:
		and		#$df
isupperwild_entry:
		cmp		#'A'
		bcc		fail
		cmp		#'Z'+1
		bcc		done
		cmp		#'?'
		beq		done
fail:
		sec
done:
		rts
.endp

;==========================================================================
.proc DOSClose
		ldy		#CIOStatNotOpen
		lda		dos_fcb_table[0].secbuf,x
		bmi		done
		tay
		lda		#$ff
		sta		dos_secbuf_fcbidx,y
		sta		dos_fcb_table[0].secbuf,x
done:
		ldy		#1
		rts
.endp

;==========================================================================
.proc DOSGetByte
		;check if the FCB is open
		ldy		#CIOStatNotOpen
		lda		dos_fcb_table[0].secbuf,x
		bpl		is_open
		rts
is_open:
		;stash IOCB#
		stx		dos_iocb
		
		;set sector pointer
		lda		dos_fcb_table[0].secbuf,x
		tax
		jsr		DOSSetSectorPointer
		
		;get byte offset
		ldx		dos_iocb
		lda		dos_fcb_table[0].offset,x
		
		;check if we're at the end of the data area
		ldy		#127
		cmp		(zsba),y
		bne		still_bytes
		
		;yes -- read next sector
		tay
		lda		(zsba),y
		and		#3
		sta		dos_sechi
		iny
		lda		(zsba),y
		sta		dos_seclo
		bne		nextsec_valid
		ldy		dos_sechi
		bne		nextsec_valid
		
		;huh... we're at EOF.
		ldy		#CIOStatEndOfFile
		rts
		
nextsec_valid:
		lda		dos_fcb_table[0].secbuf,x
		tax
		jsr		DOSReadSectorByVar
		
		;stash new sector number
		ldx		dos_iocb
		mwa		dos_seclo dos_fcb_table[0].sector
		
		;reset offset
		lda		#0
		sta		dos_fcb_table[0].offset
		tay
		
still_bytes:
		;check if we can burst (retaddr >= $c000, cmd=get byte)
		tsx
		ldy		$0102,x
		cpy		#$c0
		bcc		no_burst
		ldy		iccomz
		cpy		#CIOCmdGetChars
		bne		no_burst
		
		pha
		ldy		#127
		lda		(zsba),y
		sta		zbufp
		dec		zbufp
		pla
		tay
		ldx		#0
		beq		burst_loop_start

burst_loop:
		lda		icbllz
		lsr
		ora		icblhz
		beq		burst_exit
		
		lda		(zsba),y
		sta		(icbalz,x)
		inw		icbalz
		dew		icbllz
		iny
burst_loop_start:
		cpy		zbufp
		bne		burst_loop
		tya
		
no_burst:
		tay
burst_exit:
		lda		(zsba),y
		pha
		iny
		ldx		dos_iocb
		tya
		sta		dos_fcb_table[0].offset,x
		tya
		ldy		#127
		cmp		(zsba),y
		bne		not_at_eof
		
at_eof:
		;check if sector link is zero
		ldy		#125
		lda		(zsba),y
		and		#3
		iny
		ora		(zsba),y
		bne		not_at_eof
		
		;return EOF imminent
		pla
		ldy		#3
		rts
		
not_at_eof:
		pla
		ldy		#1
		rts
.endp

;==========================================================================
.proc DOSPutByte
		ldy		#CIOStatNotOpen
		lda		dos_fcb_table[0].secbuf,x
		bmi		not_open
		ldy		#CIOStatReadOnly
not_open:
		rts
.endp

;==========================================================================
.proc DOSGetStatus
		ldy		#CIOStatNotOpen
		lda		dos_fcb_table[0].secbuf,x
		bmi		not_open
		ldy		#1
		rts
not_open:
		ldy		errno
		rts
.endp

;==========================================================================
.proc DOSSpecial
		rts
.endp

;==========================================================================
; Search for a filename.
;
; Entry:
;	dos_filename	Filename pattern to search for
;
; Exit:
;	Y		CIO status
;	A		Base index of directory entry
;	ZSBA	Directory sector
;
.proc DOSSearchFilename
		;start with sector 361
		lda		#<361
		sta		dos_dsidx
		
dirsec_loop:
		ldx		#0
		ldy		#>361
		lda		dos_dsidx
		jsr		DOSReadSector
		
		;check if this is the end of the directory
		ldy		#0
		lda		(zsba),y
		beq		terminate_search
		
		;check if entry is deleted
		bmi		no_match
		
		;check each filename for a match
		ldy		#5
dirent_loop:
		ldx		#$f5
fntest_loop:
		lda		dos_filename-$f5,x
		cmp		#'?'
		bne		skip_wild
skip_wild:
		cmp		(zsba),y
		bne		no_match
		iny
		inx
		bne		fntest_loop
		
		tya
		sec
		sbc		#$10
		ldy		#1
		rts
		
no_match:
		tya
		and		#$f0
		clc
		adc		#16+5
		tay
		bpl		dirent_loop
		
		;next directory sector
		inc		dos_dsidx
		lda		dos_dsidx
		cmp		#<369
		bne		dirsec_loop
		
terminate_search:
		;not found
		ldy		#CIOStatFileNotFound
		rts
.endp

;==========================================================================
; Entry:
;	Y:A		Sector number
;	X		Sector buffer index
;
DOSReadSectorByVar = DOSReadSector.by_var
.proc DOSReadSector
		sta		dos_seclo
		sty		dos_sechi
by_var:
		jsr		DOSSetSectorPointer

		cmp		dos_secbuf_seclo,x
		bne		wrong_sector
		tya
		cmp		dos_secbuf_sechi,x
		bne		wrong_sector
		
		;we already have this sector
		ldy		#1
		rts

wrong_sector:
		;stash the sector buffer index
		stx		dos_secbidx
		
		;issue a disk sector read
		mwa		dos_seclo daux1
		mwa		zsba dbuflo
		mva		#'R' dcomnd
		jsr		dskinv
		bmi		error
		
		;update the cached sector index
		ldx		dos_secbidx
		mva		dos_seclo dos_secbuf_seclo,x
		mva		dos_sechi dos_secbuf_sechi,x
		
		;all done
		tya
error:
		rts
.endp

;==========================================================================
; Set ZSBA to sector buffer.
;
; Entry:
;	
;
.proc DOSSetSectorPointer
		txa
		lsr
		tay
		lda		#0
		ror
		adc		#<dos_sectorbuffers
		sta		zsba
		tya
		adc		#>dos_sectorbuffers
		sta		zsba+1
		rts
.endp

;==========================================================================

dos_sectorbuffers:

;==========================================================================


		end
