	title  'CCP/M-86 Loader Program and BIOS' 
;*****************************************************
;	The Loader consists of the three modules:
;	Loader BDOS (the file LBDOS.H86), the Loader
;	Program and the Loader BIOS.  This
;	module contains both the Loader Program
;	and the Loader BIOS.
;
;	The Loader resides in sectors 2-8 on the first track
;	of an IBM PC floppy diskette.  The Loader is
;	is brought into memory by the Boot Sector which
;	resides in sector 1 of track 0.  The Boot Sector
;	is brought into memory by the IBM PC's ROM
;	monitor. 
;
;	The Loader Program opens the file 'CCPM.SYS' using the
;	Loader BDOS and Loader BIOS,
;	and then reads it into memory.  The DS register is set
;	to the start of the CCPM DATA area, and a JMPF to the
;	first byte of the CCPM code is executed.
;
;	The first 128 byte record of the CCPM.SYS file is a header
;	with the following format:
;
;	+----+----+----+----+----+----+----+----+----+
;	|TYPE|   LEN   |   ABS   |   MIN   |   MAX   |
;	+----+----+----+----+----+----+----+----+----+
;
;	type	rb	1		;seg type 
;	len	rw	1		;length 
;	abs	dw	1		;absolute segment address for LOADER
;	min	rw	1		;minimum mem
;	max	rw	1		;max mem needed
;
;	The code is expected first and then the data within CCPM.SYS
;	This header record is constructed automatically by the
;	GENCCPM utility. See the variables declared at 'SEC1:'
;	where the first sector of CCPM.SYS will be read.
;
;	Loader may be read into any segment by the Boot
;	Sector that does not overlap the area of memory
;	the system image in CCPM.SYS will occupy.
;
;*****************************************************
 
false		equ	0
true		equ	not false

cr		equ	0dh
lf		equ	0ah

ldbdos_offset	equ	0000H		;offset of Loader BDOS
ldr_offset	equ	0900h		;offset of Loader BIOS

codetype	equ	1		; code type CMD header
datatype	equ	2		; data type CMD header

	; bdos function numbers

seldskf		equ	14
openf		equ	15
readsf		equ	20
setdmaf		equ	26
setuserf	equ	32
setmultcntf	equ	44
setdmabf	equ	51


;*****************************************************
;*
;*	CCPMLDR starts here
;*
;*****************************************************

	cseg
	org	ldr_offset

	jmp init
	jmp entry
	jmp loadp

ldusr	db	0			;load user is 0


loadp:		; loader entry from BDOS init
;-----
;	entry:	CH = boot user number
;		CL = boot disk number
;		DS,ES,SS = CS


	mov dx,offset signon
	call msg

	mov dl,ldusr
	mov cl,setuserf ! int 224	;set user number

	mov dx,offset ccpmfcb
	mov cl,openf ! int 224		;open CCPM.SYS file
	cmp al,255 ! jne perr		;insure good file
	  mov dx,offset nofile
	  jmp stop
perr:

	mov dx,ds
	mov cl,setdmabf ! int 224	;set DMA segment address

	mov dx,offset sec1 
	mov cl,setdmaf ! int 224	;set DMA offset address

	mov dl,1
	mov cl,setmultcntf ! int 224	;set Multi-sector count to 1

	mov dx,offset ccpmfcb
	mov cl,readsf ! int 224		; read first record

					; the following
					; "commented out" code
					; can be used to
					; perform error checking

;	cmp ctype,codetype		; code should be first
;	jnz badhdr
;	cmp dtype,datatype		; then data
;	jnz badhdr	
;	mov ax,cldseg			; code abs + code length
;	add ax,clen			; should be = to data abs
;	cmp ax,dldseg ! jnz  badhdr
;	add ax,dlen ! cmp ax,cldseg	; check for wrap around
;	ja hdrok
;badhdr:
;	  mov dx,offset rerr
;	  jmp stop
;hdrok:
	mov dx,offset csegment
	call msg			; put memory map on console
	mov ax,cldseg
	call phex			; print base code segment
	mov dx,offset dsegment
	call msg			; print base data segment
	mov ax,dldseg
	call phex

	mov dx,128
	mov cl,setmultcntf ! int 224	; set multi-sector count to 128
	mov dx,0
	mov cl,setdmaf ! int 224	; set DMA offset to 0
	mov dx,cldseg			; initial DMA segment
readit1:
	push dx				; save dma segment
	mov cl,setdmabf ! int 224	; set DMA segment for disk IO
	mov dx,offset ccpmfcb
	mov cl,readsf ! int 224		; next 128 sector read
	pop dx				; restore dma segment
	add dx,8*128			; increment dma segment
	cmp al,01H ! je done		; check for EOF 
	cmp al,0 ! je readit1		; check for good write  
	  mov dx,offset rerr		; print READ ERROR message
	  jmp stop
done:
	call pcrlf			; and a crlf

	mov clen,0			; set CCPM offset to 0

	mov ds,dldseg			; CCP/M data segment
	jmpf cs:dword ptr clen		; leap to CCPM initialization

;*****************************
;*
;*	subroutines
;*
;*****************************

stop:
;----
	call msg
	cli ! hlt


phex:					;print 4 hex characters from ax
;----
	mov cx,0404h			; 4 in both CH and CL
lhex:	
	rol ax,cl			; rotate left 4
	push cx ! push ax		; save crucial registers
	call pnib			; print hex nibble
	pop ax ! pop cx			; restore registers
	dec ch ! jnz lhex		; and loop four times
	ret
pnib:					;print low nibble in AL as hex char
	and al,0fh ! cmp al,9
	ja p10				;above 9 ?
	add al,'0'			;digit
	jmps prn
p10:	add al,'A'-10			;char a-e
prn:	mov dl,al

putchar:
;-------
	mov cl,dl
	jmp io_conout

pcrlf:
;-----
	mov dx,offset crlf		;print carriage return, line feed

msg:					;print msg starting at dx until $
;---
	mov bx,dx
msg1:
	mov dl,[bx]
	cmp dl,'$' ! je msg2
	  push bx
	  call putchar
	  pop bx
	  inc bx
	  jmps msg1
msg2:
	ret

eject
;************************************************************************
;*
;*
;*                    L O A D E R 
;*
;*                   B I O S - 8 6
;*                   =============
;*
;*         CP/M-86 3.0 or Concurrent CP/M-86 2.0
;*
;*		 Boot Loader I/O System
;*			for the
;*		 IBM Personal Computer
;*
;*		 Copyright (c) 1982
;*		 Digital Research, Inc.
;*		 box 579, Pacific Grove
;*		 California, 93950

;*
;*	(Permission is hereby granted to use or
;*	 abstract the following program in  the
;*	 implementation of Concurrent CP/M-86,
;*	 CP/M, MP/M or CP/Net for the 8086 or 8088
;*	 micro-processor.)
;*
;************************************************************************
;*
;* 	Register usage for BIOS interface routines:
;*
;*	Entry:	AL = function # (in entry)
;*		CX = entry parameter
;*		DX = entry parameter
;*		DS = LDBDOS data segment
;*
;*	Exit:	AX = return
;*		BX = AX (in exit)
;*		ALL SEGMENT REGISTERS PRESERVED:
;*		CS,DS,ES,SS must be preserved though call
;*
;*	Notes: flag set and the far jump to the dispatcher are
;*		the only legal Operating System "entry" points
;*		for an interrupt routine)
;*
;*		changes have been made in the
;*		register conventions from
;*		the CP/M-86 BIOS and the MP/M-86 BIOS.
;*
;************************************************************************

;************************************************************************
;*									*
;*		IBM PC SOFTWARE INTERRUPT STRUCTURE			*
;*									*
;************************************************************************

tick_interrupt		equ	08h
keyboard_interrupt	equ	09h
disk_interrupt		equ	0Eh
os_interrupt		equ	224		;Loader BDOS entry
debugger_interrupt	equ	225		;debugger entry to O.S.

;************************************************************************
;*									*
;*		INTERFACE TO LOADER BDOS				*
;*									*
;************************************************************************

;=====
;=====
entry:					;arrive here from JMP at
;=====					;03H in BIOS code segment
;=====

;	entry:	AL = function number
;		CX, DX parameters
;	exit:	AX = BX = return
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

;	Note: no alteration of stack is allowed during entry except
;	for the return address caused by the "call function_table[bx]"
;	instruction.

	cld				;set the direction flag
	xor ah,ah
	shl al,1			;multiply by 2
	mov bx,ax			;put in indirect register
	call function_table[bx]		;no range checking needed
	mov bx,ax			;only called by loader BDOS
	retf				;return to loader BDOS

io_ret:
	ret

;------
i_disk:
;------
	mov al,pic_nseoi		;signal end of interrupt
	out pic_even_port,al		;to 8259
	iret				;note we destroyed AL

;=========
io_conout:
;=========
;	entry:	CL = character to output
;		DL = device number
;	exit:	None
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

;	Put character in screen and update cursor position
;	BX = screen structure

	mov bx,offset ss0
	mov di,ss_cursor[bx]		;cursor offset in bytes
	cmp cl,cr
	 je carriage_return
	cmp cl,lf
	 je linefeed
	push es
	mov ax,bw_video_seg
	mov es,ax			;segment of foreground screen
	mov al,cl
	mov ah,07h			;default attribute
	stosw				;update and don't touch the attribute
	pop es				;DI incremented by 2
	cmp ss_column[bx],columns_per_screen - 1
	jne inc_col
	  call carriage_return
	  jmp line_feed
inc_col:
	inc ss_column[bx]		;DI = next data, attribute
	mov ss_cursor[bx],di		;save new cursor position
	ret

;---------------
carriage_return:
;---------------
;	entry:	BX = screen structure
;	exit:	BX preserved

	xor dx,dx			;AX = 0
	xchg dl,ss_column[bx]		;set cursor position to begining of
	shl dx,1			;times 2 for data and attribute
	sub ss_cursor[bx],dx		;set new cursor position
	ret

;---------
line_feed:
;---------
;	entry:	BX = screen structure
;	exit:	BX preserved

;		This routine assumes the loader will never
;		need to scroll the screen
 
	inc ss_row[bx]
	add ss_cursor[bx],columns_per_screen*2
	ret


;************************************************************************
;*									*
;*	       6845 CRT CONTROLLER PORT AND COMMAND EQUATES		*
;*									*
;************************************************************************


;	The IBM PC's monochrome memory mapped video display begins
;	at paragraph 0B000H.  It represents a screen 80 X 25.
;	Each video character requires a word value, the low byte
;	is the ASCII code (characters codes > 128 are also displayed)
;	and the high byte is an attribute byte.  The 25th line
;	is reserved by this BIOS as a status line.


bw_card			equ	003b4h

video_on		equ	00029h
video_off		equ	00021h

cursor_start		equ	10
cursor_end		equ	11
display_start_hi	equ	12
display_start_low	equ	13
cursor_hi		equ	14
cursor_low		equ	15
light_pen_hi		equ	16
light_pen_low		equ	17


;************************************************************************
;*									*
;*		SCREEN PARAMETERS					*
;*									*
;************************************************************************

rows_per_screen		equ	24
columns_per_screen	equ	80
screen_siz		equ	rows_per_screen * columns_per_screen
						;in words
bw_video_seg		equ	0b000h		;segment address of
						;start of video ram
bw_video_status_line	equ	screen_siz * 2
						;byte offset of status line

;************************************************************************
;*									*
;*		SCREEN STRUCTURES					*
;*									*
;************************************************************************

;	Each virtual console has a structure of the following
;	format associated with it.  (SS = Screen Structure)
;	The data in this structure is dependent on the type of screen
;	supported and any escape sequence handling in io_conout.
;	Note: ss_cursor, ss_row, ss_column are relative to 0 and are
;	word pointers, i.e., if ss_cursor is 1 then it refers to
;	bytes 2 and 3 in the video RAM or a background screen's
;	data area.

ss_cursor		equ	word ptr 0			;points at data/attrib
ss_escape		equ	word ptr ss_cursor + word	;escape routine to return to
ss_screen_seg		equ	word ptr ss_escape + word	;data for screen image
ss_row			equ	byte ptr ss_screen_seg + word	;current row
ss_column		equ	byte ptr ss_row + byte		;current col

;			DISK I/O
;			--------

;************************************************************************
;*									*
;*	8237 DIRECT MEMORY ACCESS CONTROLLER PORT AND COMMANDS		*
;*									*
;************************************************************************

dma_c0_address	equ	000h		;8237 channel 0 address		rw
dma_c0_count	equ	001h		;8237 channel 0 transfer count	rw
dma_c1_address	equ	002h		;8237 channel 1 address		rw
dma_c1_count	equ	003h		;8237 channel 1 transfer count	rw
dma_c2_address	equ	004h		;8237 channel 2 address		rw
dma_c2_count	equ	005h		;8237 channel 2 transfer count	rw
dma_c3_address	equ	006h		;8237 channel 3 address		rw
dma_c3_count	equ	007h		;8237 channel 3 transfer count	rw
dma_stat_reg	equ	008h		;8237 status register		ro
dma_cmd_reg	equ	dma_stat_reg	;8237 command register		wo
dma_requ_reg	equ	dma_stat_reg+1	;8237 software dma request	wo
dma_bmsk_reg	equ	dma_stat_reg+2	;8237 binary channel mask	wo
dma_mode_reg	equ	dma_stat_reg+3	;8237 mode register		wo
dma_cbpf	equ	dma_stat_reg+4	;8237 clear byte pointer f/f	wo
dma_temp_reg	equ	dma_stat_reg+5	;8237 temporary register	ro
dma_clear	equ	dma_stat_reg+5	;8237 master clear		wo
dma_mask_reg	equ	dma_stat_reg+7	;8237 linear channel mask	wo

dma_page_c1	equ	080h		;a16 to a20 for channel 1

dma_page_fdc	equ	081h		;a16 to a20 for channel 2
dma_page_c3	equ	082h		;a16 to a20 for channel 3


;	The following labels define single mode, address increment
;	auto-initialization disable, read or write using channel 2

dma_mode_write_fdc	equ	01001010b
dma_mode_read_fdc	equ	01000110b

dma_bmsk_fdc		equ	00000010b	;binary channel mask for disk
 

;	DMA channel assignments

;channel 0 dynamic memory refresh
;channel 1
;channel 2 floppy disk controller
;channel 3


;************************************************************************
;*									*
;*		FLOPPY DISK DRIVER EQUATES				*
;*									*
;************************************************************************


;	The following equates are set to the size of a double density,
;	single sided 5 & 1/4" floppy.

bytes_per_sector	equ	512
sectors_per_track	equ	8		;1 to 8
bytes_per_track		equ	sectors_per_track * bytes_per_sector
tracks_per_disk		equ	40		;0 to 39
bytes_per_disk		equ	tracks_per_disk * bytes_per_track


;	The following equates are for the INTEL 8272 Floppy Disk
;	Controller.

fdc_stat		equ	03f4h		;status port for the disk controller
fdc_data		equ	fdc_stat+1	;data port for the disk controller
fdc_port		equ	03f2h		;all bits clear on channel reset

		;7 6 5 4 3 2 1 0
		;| | | | | | \_/
		;| | | | | |  |
		;| | | | | |  drive select 00=a,01=b,10=c,11=d
		;| | | | | fdc reset*
		;| | | | int & dmarq enable
		;d c b a motor on

fdc_on		equ	00001100b	;mask to keep the 8272 unreset
fdc_no_motor	equ	11111100b	;mask for no motors
fdc_read_cmd	equ	01100110b	;mfm, skip deleted data, read
fdc_write_cmd	equ	01000101b	;mfm, write
fdc_format_cmd	equ	01001101b	;mfm, format
fdc_seek_cmd	equ	00001111b	;seek
fdc_recal_cmd	equ	00000111b	;home to track 0
fdc_si_cmd	equ	00001000b	;sense interupt status
fdc_spec_cmd	equ	00000011b	;specify

fdc_ready	equ	10000000b	;mask for transfer ready

fdc_spec_1	equ	11001111b	;srt=0c, hd unload=0f first specify byte
fdc_spec_2	equ	00000011b	;hd load=1, mode=DMA second specify byte

f_bytes		equ	2		;magic number for 512 bytes per sector
f_sectors	equ	8		;sectors per track
f_gap		equ	03ah		;magic number for format gap
f_filler	equ	0e5h		;fill character

r_bytes		equ	2		;magic number for 512 bytes
r_sectors	equ	8

r_gap		equ	02ah
r_dtl		equ	0ffh

;	Equates for paramter passing for read and write from the
;	BDOS.

;	At the disk read and write function entries,
;	all disk I/O parameters are on the stack.
;	The stack at these entries appears as
;	follows:
;

;           +-------+-------+
;       +14 |  DRV  | MCNT  |   Drive and Multi sector count
;           +-------+-------+
;       +12 |     TRACK     |   Track number
;           +-------+-------+
;       +10 |    SECTOR     |   Physical sector number
;           +-------+-------+
;        +8 |    DMA_SEG    |   DMA segment 
;           +-------+-------+
;        +6 |    DMA_OFF    |   DMA offset
;           +-------+-------+
;        +4 |    RET_SEG    |   BDOS return segment
;           +-------+-------+
;        +2 |    RET_OFF    |   BDOS return offset
;           +-------+-------+
;      SP+0 |    RET_ADR    |   Return address to BIOS ENTRY routine
;           +-------+-------+
;
;	These parameters may be indexed and modifided
;	directly on the stack by the BIOS read and write rotines
;	They will be removed by the BDOS when the BIOS completes
;	the read/write function and returns to the BDOS.


drive	equ	byte ptr 14[bp]
mcnt	equ	byte ptr 15[bp]
track	equ	word ptr 12[bp]
sector	equ	word ptr 10[bp]
dma_seg	equ	word ptr 8[bp]
dma_off	equ	word ptr 6[bp]


;	Some equtes in the Disk Parameter Header (DPH)
;	and the Disk Parameter Block.

xlt	equ	0	;translation table offset in DPH
dpb	equ	8	;disk parameter block offset in DPH

spt	equ	0	;sectors per track offset in DPB
psh	equ	15	;physical shift factor offset in DPB


;************************************************************************
;*									*
;*		DISK DRIVER ROUTINES					*
;*									*
;************************************************************************


;=========
io_seldsk:	    ; Function 7:  Select Disk
;=========
;	entry:	CL = disk to be selected
;		DL = 00h if disk has not been previously selected
;		   = 01h if disk has been previously selected
;	exit:	AX = 0 if illegal disk
;		   = offset of DPH relative from
;			BIOS Data Segment
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	xor ax,ax		;get ready for error(s)
	cmp cl,0		;is it a A: ?
	jne sel_ret		;if not just exit
	  mov ax,offset dph0
sel_ret:
	ret

;=======
io_read:	    ; Function 11: Read sector
;=======
; Reads the sector on the current disk, track and
; sector into the current DMA buffer.

;	entry:	parameters on stack
;	exit:	AL = 00	if no error occured
;		AL = 01 if an error occured
;		AL = 0ffh if density change detected
;		ALL SEGMENT REGISTERS PRESERVED:
;		CS,DS,ES,SS must be preserved though call

	mov bp,sp		;set BP for indexing into IOPB
;	mov dma_mode_storage,dma_mode_read_fdc
;	mov fdc_rw_cmd,fdc_read_cmd	;loader always reads
	call flp_io
	mov al,1
	jnz i_read_ret		;zero flag is set with successful
	  dec al		;I/O
i_read_ret:
	ret

flp_io:
;------
;	entry:	parameters on stack
;	exit:	zero flag set if no error

	mov ax,sectors_per_track;max sectors - starting sector =
	sub ax,sector		;sectors left on track
	xor bh,bh
	mov bl,mcnt		;multi sector count is byte variable
	cmp ax,bx		;sectors left on track, sectors requested 
	jbe flp_track		;transfer to end of track 
	  mov al,mcnt		;transfer to before end of track

flp_track:
	sub mcnt,al		;AL = # sectors to R/W on this iteration
				;through FLP_IO,
	mov track_mcnt,al	;sectors to R/W on current track 
				
				;check for 64K page overlap
	mov ah,al		;shl al,8 (* 256)
	xor al,al
	shl ah,1		;* 512
	push ax			;how many bytes to R/W on cur trk

	mov ax,dma_seg		;compute new 20 bit DMA addr
	mov bx,dma_off
	call comp_dma		;returns AX = low 16 bits of 20 bit adr
	not ax			;how many bytes left in this 64K page

	pop bx			;BX=bytes to R/W on current track
	cmp ax,bx		;does this transfer fit in 64K page
	jb flp_end_64K
	jmp flp_pg_ok

flp_end_64K:
				;read to end of 64K page and then
				;read spanning sector locally
	  mov al,ah		;how many sectors fit in this page ?
	  xor ah,ah		;bytes left in 64K page
	  shr ax,1		;divided by 512
	  test ax,ax		;if 0, no sectors fit
	  jz flp_rwlocal	;in the rest of this 64K page

	    mov num_sec,al	;sectors that fit in end 64K page
	    sub track_mcnt,al	;track_mcnt always > AL
	    call flp_phys_io	;read/write them, DMA already computed
	    jz flp_end64k_ok
	    jmp flp_ret		;return if zero reset on error

flp_end64K_ok:
	      xor ax,ax
	      mov al,num_sec	;compute new DMA offset
	      add sector,ax	;still on the same track
	      xchg ah,al	;shl ax,8 (* 256)
	      shl ah,1		;* bytes_per_sector (512)
	      add dma_off,ax	;64K wrap around is legal
	    
flp_rwlocal:			;read into BIOS data segment the spanning
	    mov bx,local_buf	;sector
	    mov ax,ds			;compute 20 bit local DMA addr
	    call comp_dma
;	    cmp fdc_rw_cmd,fdc_read_cmd	;loader always reads
;					;only need this for writes

;					;reading or writing ?
;	    je flp_local
;	      mov si,dma_off		;get the sector to write from local
;	      mov di,local_buf		;buffer
;	      push es ! push ds ! push ds ! pop es
;	      mov ax,dma_seg ! mov ds,ax
;	      mov cx,bytes_per_sector/2
;	      rep movsw
;	      pop ds ! pop es
;	      mov dma_off,si		;update DMA offset
;
;flp_local:

	    mov num_sec,1		;read/write one sector
	    call flp_phys_io
	    jnz flp_ret			;return zero flag reset
	      inc sector ! dec track_mcnt

;	      cmp fdc_rw_cmd,fdc_read_cmd	;loader always reads
;	      jne flp_local_done

	        mov di,dma_off		;move the sector to user's area
	        mov si,local_buf	;if reading
	        push es ! mov es,dma_seg
	        mov cx,bytes_per_sector/2
	        rep movsw
	        mov dma_off,di		;update DMA offset
	        pop es

flp_local_done:
	      mov ax,dma_seg	;compute new 20 bit DMA addr
	      mov bx,dma_off	;for next FDC read/write
	      call comp_dma

flp_pg_ok:			;read will not cross 64K boundary
	mov al,track_mcnt	;could be 0 if we just read locally
	test al,al ! jz nxt_track
	  mov num_sec,al	;read the rest from this track
	  call flp_phys_io	;DMA is already computed
	  jnz flp_ret		;return zero flag reset on error
	    xor ax,ax
	    mov ah,num_sec	;shl num_sec,8 (* 256)
	    shl ah,1		;* 512
	    add dma_off,ax

nxt_track:
	xor al,al
	cmp mcnt,al
	jz flp_ret		;return successful with zero flag set
	  inc track
	  mov sector,0
	  jmp flp_io
flp_ret:
	ret

comp_dma:		;Compute 20 bit address from offset, segment
;--------

;	entry:	AX =	segment
;		BX =	offset
;	exit:	AX =	low 16 bits
;		CH =	highest 4 bits of address, always less then 16 -
;			no megabyte wrap around
;
;		The BIOS variables DMA_LOW16 and DMA_HIGH4 are
;		set by this routine.  These variables are transferred
;		to the floppy disk controller by the routine DMA_SET_UP.

	mov cl,4 ! rol ax,cl		;make paragraphs into bytes
	mov ch,al ! and al,0f0h		;save high 4 bits, 0 low 4 bits
	add ax,bx			;add byte offset
	adc ch,0 ! and ch,0fh		;add in the carry, page is less than
	mov dma_low16,ax		;16
	mov dma_high4,ch
	ret

flp_phys_io:
;-----------
;	entry:	num_sec = number of sectors to read in this
;		operation
;		disk parameters on the stack
;	exit:	zero flag set if ok

;	Perform physical actions to read/write floppy diskette

	mov d_a_number,r_bytes
	mov d_a_eot,r_sectors
	mov d_a_gpl,r_gap
	mov d_a_dtl,r_dtl

	call motor_and_recal		;make sure motor is on
	mov recals,max_recals
recal_loop:
	mov retries,max_retries
retry_loop:
	call seek
	call dma_setup
	call fdc_read_write
	jz phys_ret			;if errors 
	  dec retries			;attempt retries
	  jnz retry_loop
	    call recal
	    dec recals
	    jnz recal_loop
	      or al,1			;reset zero flag
phys_ret:				;and return error
	ret

motor_and_recal:
;---------------
;	entry:	none
;	exit:	none

;	Turn on the motor if off.  Also do RECAL operation if
;	motor is off.  Note: loader does not turn off the motor.

	mov al,010h			;pick up a bit for motor a
	mov cl,drive			;fetch the binary drive code
	shl al,cl			;make it a bit for motor x
	mov ah,motor_flags		;fetch the motor bits
	test ah,al			;check to see if its on
	jnz motor_on_done		;yes then leap
	  or al,fdc_on			;mask in the no reset,enable interupt
	  or al,drive			;mask in the drive
	  mov motor_flags,al		;save for later
	  mov dx,fdc_port		;point to the port number
	  out dx,al			;select & motor on
	  call recal			;back to track zero
motor_on_done:				;when we first turn on the motor
	ret

recal:
;-----
;	entry:	none
;	exit:	none

;	Move the head to home on the selected disk drive.

	mov disk_arguments,2		;specify number of arguments
	mov d_a_command,fdc_recal_cmd
	mov al,drive			;get current disk
	mov d_a_drive,al		;set up the command block
	call fdc_command_put		;go send the command block
	hlt				;wait for FDC interrupt
	jmp sense_interrupt		;required after RECAL command
	;ret

seek:
;----
;	entry:	TRACK to seek to on the stack relative to BP
;	exit:	none

	mov disk_arguments,3		;request 3 byte command transfer
	mov d_a_command,fdc_seek_cmd
	xor bl,bl			;select head 0
	mov ax,track			;get track off stack
	cmp al,40			;test for off side 1
	jb side_ok			;if single sided then leap
	  mov bl,1			;else get a head 1
	  mov ah,79			;else get back track bias
	  sub ah,al			;compute new track number
	  xchg ah,al			;and get it into al
side_ok:
	mov d_a_head,bl			;set up the head
	add bl,bl			;multiply by 2
	add bl,bl			;multiply by 4
	mov d_a_cylinder,al		;set up the track number
	or bl,drive			;bits 0,1 are drive number
	mov d_a_drive,bl		;set up disk number
	mov ax,sector			;get sector off stack
	inc ax				;sectors are relative to 1
	mov d_a_record,al		;and set it up
	add al,num_sec			;compute new end of track
	dec al
	mov d_a_eot,al			;save it
	call fdc_command_put		;send the command block
	hlt				;wait for FDC interrupt
	jmp sense_interrupt		;required after SEEK command
	;ret

dma_setup:
;---------
;	entry:	DMA_MODE_STORAGE, DMA_LOW16, DMA_HIGH4 set up
;	exit:   none

;	Set the DMA device up for a read/write operation.
;	The current DMA command word must in DMA_MODE_STORAGE.
;	DMA_LOW16 and DMA_HIGH4 are the twenty bit starting address.
;	The read/write operation cannot cross a physical 64K boundary.

	out dma_cbpf,al			;reset the byte pointer

	mov al,dma_mode_storage		;get the mode byte
	out dma_mode_reg,al		;set the mode
	mov ax,dma_low16		;low 16 bits of 20 bit DMA address
	out dma_c2_address,al		;send low 8 bits
	mov al,ah
	out dma_c2_address,al		;send next 8 bits
	mov al,dma_high4		;high 4 bits of 20 bit DMA address
	out dma_page_fdc,al
	xor ax,ax
	mov ah,num_sec			;shl num_sec,8 (* 256)
	shl ah,1			;*512
	dec ax				;0 relative
	out dma_c2_count,al		;set up the low byte
	mov al,ah			;get the low byte
	out dma_c2_count,al		;and the high byte
	mov al,dma_bmsk_fdc		;get the binary channel mask
	out dma_bmsk_reg,al		;enable the disk channel
	ret

fdc_read_write:
;--------------

;	entry:	DMA device set up, head positioned on correct
;		track
;	exit:	zero flag set if successful

;	Send read or write command to 8272 FDC

	mov disk_arguments,9		;9 byte command for read or write
	mov al,fdc_rw_cmd		;get read or write command
	mov d_a_command,al		;put it in the command string
	call fdc_command_put		;send the command to the FDC
	hlt				;wait for FDC interrupt
	mov disk_results,7		;7 byte result transfer
	call fdc_status_get		;get the result bytes
	test d_r_st0,0C0H		;test status register 0
	ret				;return zero flag set on success

sense_interrupt:
;---------------
;	Sense interrupt command on the FDC.  It is called
;	after a recal or a seek to test for seek complete.

	mov disk_arguments,1		;only one byte of command
	mov d_a_command,fdc_si_cmd	;sense interrupt commmand
	call fdc_command_put		;send the command to the FDC
	mov disk_results,2		;2 bytes are returned
	call fdc_status_get		;get the 2 result bytes
	ret

fdc_command_put:
;---------------
;	entry:	DISK_ARGUMENT array set up
;	exit:	none

;	Send the command block in the DISK_ARGUMENTS table to
;	the 8272 FDC.
;	The number of commands to write to the FDC is the
;	first item in the table.


	mov dx,fdc_stat			;point to the i/o port
	mov si,offset disk_arguments	;point to the table of arguments
	cld				;make sure we go forward
	lodsb				;get the length of the arguments table
	mov cl,al			;get it into the count register
	sub ch,ch			;zero the high byte

fdc_command_loop:
	in al,dx			;get the current control byte
	test al,fdc_ready		;if not ok to send next byte
	jz fdc_command_loop		;then loop waiting
	  inc dx			;point at the data port
	  lodsb				;else get the byte to send
	  out dx,al			;send it
	  dec dx			;point back at the status port
	  loop fdc_command_loop		;if not last byte then loop
	  ret				;else were all done

fdc_status_get:
;--------------
;	entry:	number of results in 1st byte of DISK_RESULTS array
;	exit:	none

;	Get the status information from the 8272
;	FDC and place them in the table at DISK_RESULTS.
;	The first byte in the table is the number of results
;	to read from the FDC

	push es				;save UDA
	mov dx,fdc_stat			;point at the status port
	mov ax,ds			;get our data segment
	mov es,ax			;into the extra segment
	mov di,offset disk_results + 1	;point to where to put the data
	cld 				;make sure we go forward
	mov cl,disk_results		;fetch the number of expected results
	sub ch,ch			;zero the high byte

fdc_status_loop:
	in al,dx			;get the current control byte
	test al,fdc_ready		;if not ok to read next byte
	jz fdc_status_loop		;then loop waiting
	  inc dx			;point at the data port
	  in al,dx			;get the byte
	  stosb				;put it in the structure
	  dec dx			;point back at the status port
	  loop fdc_status_loop		;if not last then loop
	pop es				;restore UDA
	ret				;else return



;		INIT ROUTINE

;************************************************************************
;*									*
;*	8259 PROGRAMMABLE INTERRUPT CONTROLLER COMMANDS			*
;*			AND PORTS					*
;*									*
;************************************************************************

pic_even_port		equ	020h	;port 0
pic_odd_port		equ	021h	;bit 0 is A0 of 8259 instructions

pic_nseoi		equ	020h	;non specific end of interupt

;************************************************************************
;*									*
;*		      BIOS INITIALIZATION ROUTINE			*
;*									*
;************************************************************************

					;track buffer
memory_size	equ	12h		;ROM interrupt to get memory size
equipment	equ	11h		;ROM interrupt to get equipment word

;	The following routine is used to initialize any required
;	data areas and alter any peripheral chip programming when
;	starting up CCP/M-86.  This code is called once from the
;	SUP(ERVISOR) after calling the SUP has called the RTM,
;	RTM, CIO, MEM, BDOS initialization routines and before the
;	SUP has created the RSP processes.
;	This code can be placed in an BIOS data area if the BIOS is
;	8080 model (mixed code and data).  Usually, overlaying the
;	initialization code with a data area is done after the BIOS
;	has been debugged.


;====
;====
init:					;arrive here from the JMP
;====					;at 0 in BIOS code segment
;====

	cli ! cld			;Supervisor restores DS,ES and int
					;224 after on INIT call 

;		Disk I/O initialization


set_up_interrupts:
	mov al,10111101b		;enable diskette and keyboard
	out pic_odd_port,al		;interrupts only
					;and mask off the rest

;	mov disk_arguments,3		;send specify command to the
;	mov d_a_command,fdc_spec_cmd	;FDC (Intel 8272 or NEC 765)
;	mov d_a_drive,0d0h		;step rate=3ms, head unload=0ms
;	mov d_a_cylinder,20h		;head load=2ms, DMA mode true
;	call fdc_command_put		;head unload and head load times
					;are meaningless on mini floppies
					;where the head is loaded
					;when the motor is turned on

	mov ax,ds
	add data_buf_seg,ax		;fix up data buffer segment

	push es ! xor ax,ax
	mov es,ax
	mov di,disk_interrupt*4		;point at vector location
	mov ax,offset i_disk
	stosw				;save and inc DI
	mov ax,cs
	stosw
	pop es

set_up_video:	
	mov dx,bw_card			;get the video chip port
	mov si,offset bw_table		;initialization commands
	mov cx,length bw_table		;how many commands
	call video_init			;send commands to port
					;color board is similar ...
	push es
	mov di,bw_video_seg		;clear the video RAM
	mov es,di
	xor di,di
	mov cx,screen_siz + columns_per_screen
					;screen length in words plus
					;status line
	mov ax,0720h			;7 = default attribute
					;20 = ASCII space
	rep stosw
	pop es

	sti
	retf				;initializaiton done

;----------
video_init:
;----------
	xor bl,bl
video_init_l:
	mov al,bl ! inc bl
	out dx,al ! inc dx
	lodsb !	out dx,al
	dec dx !
	loop video_init_l
	  add dx,4
	  mov al,video_on
	  out dx,al
	  ret


last_code_offset	equ	offset $
DSEG
ORG	last_code_offset

;	Loader BIOS data

bw_table	db	61h,50h,52h,0fh,19h,06h,19h,19h,02h,0dh,0bh,0ch,0,0,0,0

function_table:
	dw	io_ret			; 0 console status
	dw	io_ret			; 1 console input
	dw	io_conout		; 2 console output
	dw	io_ret			; 3 list status
	dw	io_ret			; 4 list output
	dw	io_ret			; 5 auxillary input
	dw	io_ret			; 6 auxillary out
	dw	io_ret			; 7 switch screen
	dw	io_ret			; 8 update or print new status
	dw	io_seldsk		; 9 select disk
	dw	io_read			;10 read logical sector
	dw	io_ret			;11 write logical sector
	dw	io_ret			;12 flush buffers
	dw	io_ret			;13 poll device


				;short screen structure
				;for IO_CONOUT

ss0		dw	0,0,0	;cursor, escape, screen_seg
		db	0,0	;row,column


;	Floppy Disk Driver data

	
max_retries		equ		3
retries			db	0	;retry counter
max_recals		equ		5

recals			db	0

;	The following 2 tables are used to issue commands to,
;	and read results from the 8272 FDC.  The first entry
;	in each table is the number of bytes to send or receive
;	from the device

disk_arguments	db	0		;number of arguments to send
d_a_command	db	0		;command read/write
d_a_drive	db	0		;drive select & head select
d_a_cylinder	db	0		;cylinder to read/write
d_a_head	db	0		;head
d_a_record	db	0		;sector
d_a_number	db	2		;magic number for 512 bytes/sector 
d_a_eot		db	sectors_per_track	;end of track sector number
d_a_gpl		db	02ah		;inter sector gap length
d_a_dtl		db	0ffh		;data length

disk_results	db	0		;number of bytes to read
d_r_st0		db	0		;status byte 0
d_r_st1		db	0		;status byte 1
d_r_st2		db	0		;status byte 2
d_r_cylinder	db	0		;cylinder we are on now
d_r_head	db	0
d_r_record	db	0
d_r_number	db	0		;number of sectors read

f_a_bytes	db	0
f_a_sectors	db	0
f_a_gap		db	0
f_a_filler	db	0

fdc_rw_cmd		db	fdc_read_cmd	;read or write command
dma_mode_storage	db	dma_mode_read_fdc

num_sec			db	1		;num sectors to read/write
						;in one call to controller

track_mcnt		rb	1		;multi sector count on
						;current track
dma_low16		rw	1		;20 bit address storage
dma_high4		rb	1

motor_flags		db	0		;last state of the motor bits

;	Disk Parameter Header

dph0	dw	0		;translate table
	dw	0,0,0		;scratch area
	dw	dpb0		;dsk parm block
	dw	0		;check
	dw	0		;alloc vectors
	dw	dir_bcb_hdr	;dir buff cntrl	blk
	dw	data_bcb_hdr	;data buff cntrl blk
	dw	0		;hash table segment

;	Disk Parameter Block

dpb0	dw	8		;sectors per track
	db	3		;block shift
	db	7		;block mask
	db	0		;extnt mask
	dw	155		;disk size in 1k blocks
				;less offset track(s)
	dw	63		;directory max
	db	11000000b	;alloc0
	db	0		;alloc1
	dw	16		;check size
	dw	1		;offset
	db	2		;phys sec shift
	db	3		;phys sec mask

dir_bcb_hdr	dw	dir_bcb
		db	1

dir_bcb		db	0ffh	;drive
		db	0,0,0	;record
		db	0,0	;wflg,seg
		dw	0,0	;track,sector
dir_buf_off	dw	dir_buf	;buffer offset
		dw	0,0	;link,pdadr

data_bcb_hdr	dw	data_bcb
		db	1

data_bcb	db	0ffh	;drive
		db	0,0,0	;record
		db	0,0	;wflg,seg
		dw	0,0	;track,sector
data_buf_seg	dw	data_buf;buffer segment, fixed up by LBIOS init:
		dw	0,0	;link,pdadr


;	Loader Program data

signon		db	'CCP/M-86 Loader 2.0$' 
nofile		db	cr,lf,'No CCPM.SYS File On Disk$'
rerr		db	cr,lf,'Error Reading CCPM.SYS$'
csegment	db	cr,lf,'Code Paragraph = $'
dsegment	db	cr,lf,'Data Paragraph = $'
crlf		db	cr,lf,'$'

ccpmfcb	db	1,'CCPM    ','SYS',0,0,0,0
	db	0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0

	db	0


;	Note the Loader Program and Loader BIOS uninitialized
;	data areas are combined to save space.

;	Unitialized data for Loader Program

				;read first CMD header of
				;CCPM.SYS file here
ctype	rb	1		;type
clen	rw	1		;length
cldseg	rw	1		;abs
cmin	rw	1		;minimum
cmax	rw	1		;maximum
dtype	rb	1
dlen	rw	1
dldseg	rw	1
dmin	rw	1
dmax	rw	1	

org	offset ctype
sec1	rb	128		;reserve space for the sector


;		Unitialized data for Loader BIOS


dir_buf		equ	((offset  $) + 15) and 0fff0h

;		Sector for reads that span 64K boundaries;
;		this sector can not cross a 64K boundary

local_buf	equ	dir_buf + 512

data_buf	equ	(local_buf + 512)/16
