; =============================================================================
;
;                             Litos8 system devices
;
; =============================================================================

; -----------------------------------------------------------------------------
; Exported user functions (5):
;	OpenDevice - open device
;	DevCheck - device check
;	DevSize - get device media size
;	DevRead - device read
;	DevWrite - device write
; -----------------------------------------------------------------------------

		CODE_SECTION

; -----------------------------------------------------------------------------
;                                Open device
; -----------------------------------------------------------------------------
; INPUT:	CL = length of device name
;		SI = device name (without ":")
;		DI = pointer to destination FILE structure
; OUTPUT:	CY = error, invalid device
; -----------------------------------------------------------------------------

; ------------- push registers

OpenDevice:	push	ax		; push AX
		push	bx		; push BX
		push	cx		; push CX
		push	dx		; push DX
		push	si		; push SI

; ------------- One character: system disk

		mov	ch,0		; CH <- 0
		cmp	cl,1		; 1 character
		jne	short OpenDevice1 ; not system disk
		mov	al,[si]		; AL <- load character
		call	UpperCase	; AL <- convert to upper case letter
		sub	al,"A"		; AL <- system disk number
		cmp	al,SDISK_MAX	; check disk number
		jae	short OpenDevice1 ; invalid disk number
		shl	al,1
		shl	al,1
		shl	al,1
		shl	al,1		; AL <- shift disk index to bit 4
		jmp	short OpenDevice8 ; set device

; ------------- prepare to find device

OpenDevice1:	mov	bx,DevNameTab	; BX <- device name table
OpenDevice2:	mov	ax,[bx]		; AL <- text length, AH <- device type
		inc	bx		; BX <- skip text length
		inc	bx		; BX <- skip device type
		cmp	al,cl		; check length of device name
		jne	short OpenDevice7 ; not suitable device name

; ------------- push registers 2

		push	bx		; push BX
		push	cx		; push CX
		push	si		; push SI

; ------------- get one character

OpenDevice3:	lodsb			; AL <- name
		call	UpperCase	; AL <- convert to upper case letter
		cmp	byte [bx],"?"	; index?
		jne	short OpenDevice4 ; no

; ------------- device index

		sub	al,"1"		; AL <- index
		cmp	al,3		; check max. index
		ja	short OpenDevice6 ; invalid index (here is NZ)
		shl	al,1
		shl	al,1
		shl	al,1
		shl	al,1		; AL <- shift device index to bit 4
		or	ah,al		; AH <- add index
		jmp	short OpenDevice5

; ------------- check one character

OpenDevice4:	cmp	al,[bx]		; check character
		jne	short OpenDevice6

; ------------- next character

OpenDevice5:	inc	bx		; increase pattern
		loop	OpenDevice3	; next character
		cmp	al,al		; set ZY flag

; ------------- pop registers 2

OpenDevice6:	pop	si		; pop SI
		pop	cx		; pop CX
		pop	bx		; pop BX
		mov	al,ah		; AL <- device type and index
		je	short OpenDevice8 ; device found OK

; ------------- next device

OpenDevice7:	mov	al,[bx-2]	; AL <- text length
		cbw			; AX <- text length
		add	bx,ax		; BX <- skip device name
		cmp	byte [bx],0	; end of table?
		jne	short OpenDevice2 ; no, check next device

; ------------- error, device not found

		stc			; set error flag
		jmp	short OpenDevice9

; ------------- clear FILE descriptor (AL = device type and index)

OpenDevice8:	call	ClearFileDesc	; clear FILE descriptor

; ------------- set device name

		push	di		; push DI (pointer to FILE structure)
		rep	movsb		; copy device name
		pop	di		; pop DI (pointer to FILE structure)

; ------------- set device parameters

		mov	byte [di+FILE_Attr],DIR_Device ; set device attribute
		mov	byte [di+FILE_DirInx],al ; set device type and index

; ------------- set device size

		xchg	ax,bx		; BL <- device type and index
		call	DevSize		; get device size
		mov	[di+FILE_Size],ax ; device size LOW
		mov	[di+FILE_Size+2],dx ; device size HIGH

; ------------- check device

		call	DevCheck	; check device

; ------------- pop registers

OpenDevice9:	pop	si		; pop SI
		pop	dx		; pop DX
		pop	cx		; pop CX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                             NUL device check
; -----------------------------------------------------------------------------
; INPUT:	BX = device index (0..15)
; OUTPUT:	CY = invalid device
; -----------------------------------------------------------------------------

NULCheck:	clc			; clear error flag
		ret

; -----------------------------------------------------------------------------
;                           Read from NUL device
; -----------------------------------------------------------------------------
; INPUT:	BX = device index (0..15)
;		CX = number of bytes
;		DI = destination buffer
; OUTPUT:	CY = error (CX > 0)
;		CX = remaining bytes (or CX = 0 if no error NC)
; -----------------------------------------------------------------------------

NULRead:	push	ax		; push AX
		push	di		; push DI

		xor	ax,ax		; AX <- 0
		shr	cx,1		; CX <- number of words
		rep	stosw		; clear buffer in words
		adc	cx,cx		; CX <- last byte
		rep	stosb		; clear last byte

		pop	di		; pop DI
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                         Write to NUL device
; -----------------------------------------------------------------------------
; INPUT:	BX = device index (0..15)
;		CX = number of bytes
;		DI = source buffer
; OUTPUT:	CY = error (CX > 0)
;		CX = remaining bytes (or CX = 0 if no error NC)
; -----------------------------------------------------------------------------

NULWrite:	xor	cx,cx		; CX <- 0, no data left, clear CF
		ret

; -----------------------------------------------------------------------------
;                         Device check (invalid device)
; -----------------------------------------------------------------------------
; INPUT:	BX = device index (0..15)
; OUTPUT:	CY = invalid device
; -----------------------------------------------------------------------------

; -----------------------------------------------------------------------------
;                         Device read (invalid device)
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = current sector offset (only block device)
;		BX = device index (0..15)
;		CX = number of bytes/sectors
;		DI = destination buffer
; OUTPUT:	CY = error (CX > 0)
;		CX = remaining bytes/sectors (or CX = 0 if no error NC)
; NOTES:	Block device uses 512-byte sectors, char device uses bytes.
; -----------------------------------------------------------------------------

; -----------------------------------------------------------------------------
;                         Device write (invalid device)
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = current sector offset (only block device)
;		BX = device index (0..15)
;		CX = number of bytes/sectors
;		DI = source buffer
; OUTPUT:	CY = error (CX > 0)
;		CX = remaining bytes/sectors (or CX = 0 if no error NC)
; NOTES:	Block device uses 512-byte sectors, char device uses bytes.
; -----------------------------------------------------------------------------

; -----------------------------------------------------------------------------
;                         Device size (invalid device)
; -----------------------------------------------------------------------------
; INPUT:	BX = device index (0..15)
; OUTPUT:	DX:AX = media size (0 on error)
;		CY = invalid device (DX:AX = 0)
; -----------------------------------------------------------------------------

DevSizeNone:	xor	ax,ax		; AX <- 0
		xor	dx,dx		; DX <- 0
DevCheckNone:
DevReadNone:
DevWriteNone:	stc			; set error flag
		ret

; -----------------------------------------------------------------------------
;                           Get device interface
; -----------------------------------------------------------------------------
; INPUT:	BL =	bit 0..3: device type (bit 3: 0=block/1=char device)
;			bit 4..7: device index (0..15)
; OUTPUT:	BX = device index (0..15)
;		SI = pointer to device interface DEVINT
; -----------------------------------------------------------------------------

GetDevInt:	xchg	ax,si		; SI <- push AX
		mov	al,bl		; AL <- device type and index
		and	al,FILEDEV_MASKTYPE ; AL <- device type
		mov	ah,DEVINT_size	; AH <- size of DEVINT descriptor
		mul	ah		; AX <- offset of descriptor
		add	ax,DevIntTab	; AX <- descriptor
		xchg	ax,si		; SI <- descriptor, AX <- pop AX

		mov	bh,0
		shr	bl,1
		shr	bl,1
		shr	bl,1
		shr	bl,1		; BL <- device index
		ret

; -----------------------------------------------------------------------------
;                               Device check
; -----------------------------------------------------------------------------
; INPUT:	BL =	bit 0..3: device type (bit 3: 0=block/1=char device)
;			bit 4..7: device index (0..15)
; OUTPUT:	CY = invalid device
; -----------------------------------------------------------------------------

DevCheck:	push	bx		; push BX
		push	si		; push SI

		call	GetDevInt	; get device interface -> SI
		call	[si+DEVINT_Check] ; device check

		pop	si		; pop SI
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                               Get device media size
; -----------------------------------------------------------------------------
; INPUT:	BL =	bit 0..3: device type (bit 3: 0=block/1=char device)
;			bit 4..7: device index (0..15)
; OUTPUT:	DX:AX = media size (0 on error)
;		CY = invalid device (or character device, DX:AX = 0 on error)
; -----------------------------------------------------------------------------

DevSize:	push	bx		; push BX
		push	si		; push SI

		call	GetDevInt	; get device interface -> SI
		call	[si+DEVINT_Size] ; get device size

		pop	si		; pop SI
		pop	bx		; pop BX
		ret

; -----------------------------------------------------------------------------
;                                Device read
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = current byte offset (only block device)
;		BL =	bit 0..3: device type (bit 3: 0=block/1=char device)
;			bit 4..7: device index (0..15)
;		CX = number of bytes
;		DI = destination buffer
; OUTPUT:	CY = error (CX > 0)
;		CX = remaining bytes (or CX = 0 if no error NC)
; NOTES:	Block device is limited to 4 GB.
; -----------------------------------------------------------------------------

; ------------- push registers

DevRead:	push	ax		; push AX
		push	bx		; push BX
		push	dx		; push DX
		push	si		; push SI
		push	di		; push DI
		push	bp		; push BP

; ------------- no data required

		clc			; clear error flag
		jcxz	DevRead1	; no data required

; ------------- character device

		test	bl,B3		; character device?
		jz	short DevRead2	; no, it is block device
		call	GetDevInt	; get device interface -> SI
		call	[si+DEVINT_Read] ; device read
DevRead1:	jmp	DevRead9

; ------------- get device interface -> SI

DevRead2:	call	GetDevInt	; get device interface -> SI

; ------------- change byte offset to sector offset -> DX:AX

		mov	bp,ax		; BP <- save start offset LOW

		mov	al,ah
		mov	ah,dl
		mov	dl,dh
		mov	dh,0		; DX:AX <- offset / 256
		shr	dx,1
		rcr	ax,1		; DX:AX <- sector offset

; ------------- unaligned data in first sector?

		test	bp,SECTSIZE-1	; check data in first sector
		jz	short DevRead6	; start is aligned OK

; ------------- read first sector

		push	cx		; push CX
		push	di		; push DI

		xor	cx,cx		; CX <- 0
		mov	[OldSectBDD],cx	; invalid sector buffer
		inc	cx		; CX <- 1 sector
		mov	di,SectBuf	; DI <- sector buffer
		call	[si+DEVINT_Read] ; read first sector

		pop	di		; pop DI
		pop	cx		; pop CX
		jc	short DevRead9	; error

; ------------- increase sector

		inc	ax		; AX <- sector LOW + 1
		jnz	short DevRead3	; no carry
		inc	dx		; carry

; ------------- copy data from first sector

DevRead3:	push	si		; push SI

		mov	si,bp		; SI <- byte offset LOW
		and	si,SECTSIZE-1	; SI <- byte offset in sector
		add	si,SectBuf	; SI <- start address

		xchg	bp,cx		; BP <- total size, CX <- offset LOW
		neg	cx
		and	ch,(SECTSIZE-1)/256 ; CX <- remaining data
		cmp	cx,bp		; check remaining data
		jb	short DevRead4	; data size is OK
		mov	cx,bp		; CX <- limit remaining data

DevRead4:	sub	bp,cx		; BP <- shift total size
		shr	cx,1		; CX <- size in words
		rep	movsw		; copy data in words
		adc	cx,cx		; CX <- odd byte
		rep	movsb		; copy odd byte
		mov	cx,bp		; CX <- new total size

		pop	si		; pop SI
		jcxz	DevRead9	; no data left (here is NC)

; ------------- read full sectors

DevRead6:	mov	bp,cx		; BP <- total size
		and	cx,~(SECTSIZE-1) ; CX <- mask full sectors
		jz	short DevRead8	; no full sectors

		push	cx		; push CX
		xchg	cl,ch		; CX <- size / 256
		shr	cx,1		; CX <- number of sectors
		push	cx		; push CX
		call	[si+DEVINT_Read] ; read sectors
		pop	cx		; pop CX
		jc	short DevRead7	; error
		add	ax,cx		; AX <- shift sector LOW
		adc	dx,byte 0	; DX <- carry
DevRead7:	pop	cx		; pop CX
		jc	short DevRead9	; error		
		add	di,cx		; DI <- shift destination address
		sub	bp,cx		; BP <- shift total size

DevRead8:	mov	cx,bp		; CX <- new total size
		jcxz	DevRead9	; no data left (here is NC)

; ------------- read last sector

		push	cx		; push CX
		push	di		; push DI

		xor	cx,cx		; CX <- 0
		mov	[OldSectBDD],cx	; invalid sector buffer
		inc	cx		; CX <- 1 sector
		mov	di,SectBuf	; DI <- sector buffer
		call	[si+DEVINT_Read] ; read first sector

		pop	di		; pop DI
		pop	cx		; pop CX
		jc	short DevRead9	; error

; ------------- copy data from last sector

		mov	si,SectBuf	; SI <- start address
		shr	cx,1		; CX <- size in words
		rep	movsw		; copy data in words
		adc	cx,cx		; CX <- odd byte
		rep	movsb		; copy odd byte

; ------------- pop registers

DevRead9:	pop	bp		; pop BP
		pop	di		; pop DI
		pop	si		; pop SI
		pop	dx		; pop DX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                               Device write
; -----------------------------------------------------------------------------
; INPUT:	DX:AX = current byte offset (only block device)
; 		BL =	bit 0..3: device type (bit 3: 0=block/1=char device)
;			bit 4..7: device index (0..15)
;		CX = number of bytes
;		DI = source buffer
; OUTPUT:	CY = error (CX > 0)
;		CX = remaining bytes (or CX = 0 if no error NC)
; NOTES:	Block device is limited to 4 GB.
; -----------------------------------------------------------------------------

; ------------- push registers

DevWrite:	push	ax		; push AX
		push	bx		; push BX
		push	dx		; push DX
		push	si		; push SI
		push	di		; push DI
		push	bp		; push BP

; ------------- no data required

		clc			; clear error flag
		jcxz	DevWrite1	; no data required

; ------------- character device

		test	bl,B3		; character device?
		jz	short DevWrite2	; no, it is block device
		call	GetDevInt	; get device interface -> SI
		call	[si+DEVINT_Write] ; device write
DevWrite1:	jmp	DevWrite9

; ------------- get device interface -> SI

DevWrite2:	call	GetDevInt	; get device interface -> SI

; ------------- change byte offset to sector offset -> DX:AX

		mov	bp,ax		; BP <- save start offset LOW

		mov	al,ah
		mov	ah,dl
		mov	dl,dh
		mov	dh,0		; DX:AX <- offset / 256
		shr	dx,1
		rcr	ax,1		; DX:AX <- sector offset

; ------------- unaligned data in first sector?

		test	bp,SECTSIZE-1	; check data in first sector
		jz	short DevWrite6	; start is aligned OK

; ------------- read first sector

		push	cx		; push CX
		push	di		; push DI

		xor	cx,cx		; CX <- 0
		mov	[OldSectBDD],cx	; invalid sector buffer
		inc	cx		; CX <- 1 sector
		mov	di,SectBuf	; DI <- sector buffer
		call	[si+DEVINT_Read] ; read first sector

		pop	di		; pop DI
		pop	cx		; pop CX
		jc	short DevRead9	; error

; ------------- copy data to first sector

		push	si		; push SI
		mov	si,di		; SI <- source buffer

		mov	di,bp		; DI <- byte offset LOW
		and	di,SECTSIZE-1	; DI <- byte offset in sector
		add	di,SectBuf	; DI <- start address

		xchg	bp,cx		; BP <- total size, CX <- offset LOW
		neg	cx
		and	ch,(SECTSIZE-1)/256 ; CX <- remaining data
		cmp	cx,bp		; check remaining data
		jb	short DevWrite4	; data size is OK
		mov	cx,bp		; CX <- limit remaining data

DevWrite4:	sub	bp,cx		; BP <- shift total size
		shr	cx,1		; CX <- size in words
		rep	movsw		; copy data in words
		adc	cx,cx		; CX <- odd byte
		rep	movsb		; copy odd byte

		mov	di,si		; DI <- new source buffer
		pop	si		; pop SI

; ------------- write first sector

		push	di		; push DI
		inc	cx		; CX <- 1 sector
		mov	di,SectBuf	; DI <- sector buffer
		call	[si+DEVINT_Write] ; write first sector
		mov	cx,bp		; CX <- new total size
		pop	di		; pop DI
		jc	short DevWrite9	; error
		jcxz	DevWrite9	; no data left

; ------------- increase sector

		inc	ax		; AX <- sector LOW + 1
		jnz	short DevWrite6	; no carry
		inc	dx		; carry

; ------------- write full sectors

DevWrite6:	mov	bp,cx		; BP <- total size
		and	cx,~(SECTSIZE-1) ; CX <- mask full sectors
		jz	short DevWrite8	; no full sectors

		push	cx		; push CX
		xchg	cl,ch		; CX <- size / 256
		shr	cx,1		; CX <- number of sectors
		push	cx		; push CX
		call	[si+DEVINT_Write] ; write sectors
		pop	cx		; pop CX
		jc	short DevWrite7	; error
		add	ax,cx		; AX <- shift sector LOW
		adc	dx,byte 0	; DX <- carry
DevWrite7:	pop	cx		; pop CX
		jc	short DevWrite9	; error		
		add	di,cx		; DI <- shift destination address
		sub	bp,cx		; BP <- shift total size

DevWrite8:	mov	cx,bp		; CX <- new total size
		jcxz	DevWrite9	; no data left (here is NC)

; ------------- read last sector

		push	cx		; push CX
		push	di		; push DI

		xor	cx,cx		; CX <- 0
		mov	[OldSectBDD],cx	; invalid sector buffer
		inc	cx		; CX <- 1 sector
		mov	di,SectBuf	; DI <- sector buffer
		call	[si+DEVINT_Read] ; read first sector

		pop	di		; pop DI
		pop	cx		; pop CX
		jc	short DevWrite9	; error

; ------------- copy data to last sector

		push	cx		; push CX

		push	si		; push SI
		mov	si,di		; SI <- source buffer
		mov	di,SectBuf	; DI <- start address
		shr	cx,1		; CX <- size in words
		rep	movsw		; copy data in words
		adc	cx,cx		; CX <- odd byte
		rep	movsb		; copy odd byte
		pop	si		; pop SI

; ------------- write last sector

		inc	cx		; CX <- 1 sector
		mov	di,SectBuf	; DI <- sector buffer
		call	[si+DEVINT_Write] ; write last sector

		pop	cx		; pop CX
		jc	short DevWrite9	; error
		xor	cx,cx		; CX <- 0, no data left

; ------------- pop registers

DevWrite9:	pop	bp		; pop BP
		pop	di		; pop DI
		pop	si		; pop SI
		pop	dx		; pop DX
		pop	bx		; pop BX
		pop	ax		; pop AX
		ret

; -----------------------------------------------------------------------------
;                            Constant data
; -----------------------------------------------------------------------------

		CONST_SECTION

; ------------- device names (length, type, text; ?=index 1..4)

DevNameTab:	db	3,FILEDEV_FD,"FD?"
		db	3,FILEDEV_HD,"HD?"
		db	3,FILEDEV_RAM,"RAM"
		db	3,FILEDEV_RD,"RD?"
		db	3,FILEDEV_NUL,"NUL"
		db	3,FILEDEV_CON,"CON"
		db	4,FILEDEV_COM,"COM?"
		db	3,FILEDEV_COM,"AUX"
		db	4,FILEDEV_LPT,"LPT?"
		db	3,FILEDEV_LPT,"PRN"
		db	0

; ------------- device interfaces

		align	2, db 0
DevIntTab:	dw	SDDCheck,SDDSize,SDDRead,SDDWrite	; 0: A: ...
		dw	FDDCheck,FDDSize,FDDRead,FDDWrite	; 1: FD?
		dw	HDDCheck,HDDSize,HDDRead,HDDWrite	; 2: HD?
		dw	RAMCheck,RAMSize,RAMRead,RAMWrite	; 3: RAM
		dw	RDDCheck,RDDSize,RDDRead,RDDWrite	; 4: RD?
		dw	DevCheckNone,DevSizeNone,DevReadNone,DevWriteNone ; 5:
		dw	DevCheckNone,DevSizeNone,DevReadNone,DevWriteNone ; 6:
		dw	DevCheckNone,DevSizeNone,DevReadNone,DevWriteNone ; 7:

		dw	NULCheck,DevSizeNone,NULRead,NULWrite	; 8: NUL
		dw	CONCheck,DevSizeNone,CONRead,CONWrite	; 9: CON
		dw	COMCheck,DevSizeNone,COMRead,COMWrite	; 10: COM?, AUX
		dw	LPTCheck,DevSizeNone,DevReadNone,LPTWrite ; 11:LPT?,PRN
		dw	DevCheckNone,DevSizeNone,DevReadNone,DevWriteNone ; 12:
		dw	DevCheckNone,DevSizeNone,DevReadNone,DevWriteNone ; 13:
		dw	DevCheckNone,DevSizeNone,DevReadNone,DevWriteNone ; 14:
		dw	DevCheckNone,DevSizeNone,DevReadNone,DevWriteNone ; 15:

; -----------------------------------------------------------------------------
;                            Uninitialised data
; -----------------------------------------------------------------------------

		DATA_SECTION
