Files
Sepp J Morris 31738079c4 Upload
Digital Research
2020-11-06 18:50:37 +01:00

829 lines
21 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

title 'Track Buffered BIOS - SBC204'
; 5 Jan 82 -jrp
; Copyright (C) 1980,1981
; Digital Research, Inc.
; Box 579, Pacific Grove
; California, 93950
;
; (Permission is hereby granted to use
; or abstract the following program in
; the implementation of CP/M, MP/M or
; CP/NET for the 8086 or 8088 Micro-
; processor)
true equ -1
false equ not true
; Track buffering equates...
host_sectsiz equ 128
host_spt equ 26
host_fsn equ 1
bdos_int equ 224
bios_code equ 2500h
ccp_offset equ 0000h
bdos_ofst equ 0B06h ;BDOS entry point
csts equ 0DAh ;i8251 status port
cdata equ 0D8h ; " data port
lsts equ 41h ;2651 No. 0 on BLC8538 status port
ldata equ 40h ; " " " " " data port
blc_reset equ 60h ;reset selected USARTS on BLC8538
;*********************************************
;* *
;* Intel iSBC 204 Disk Controller Ports *
;* *
;*********************************************
base204 equ 0a0h ;SBC204 assigned address
fdc_com equ base204+0 ;8271 FDC out command
fdc_stat equ base204+0 ;8271 in status
fdc_parm equ base204+1 ;8271 out parameter
fdc_rslt equ base204+1 ;8271 in result
fdc_rst equ base204+2 ;8271 out reset
dmac_adr equ base204+4 ;8257 DMA base address out
dmac_cont equ base204+5 ;8257 out control
dmac_scan equ base204+6 ;8257 out scan control
dmac_sadr equ base204+7 ;8257 out scan address
dmac_mode equ base204+8 ;8257 out mode
dmac_stat equ base204+8 ;8257 in status
fdc_sel equ base204+9 ;FDC select port (not used)
fdc_segment equ base204+10 ;segment address register
reset_204 equ base204+15 ;reset entire interface
max_retries equ 10 ;max retries on disk i/o
;before perm error
cr equ 0dh ;carriage return
lf equ 0ah ;line feed
cseg
org ccpoffset
ccp:
org bios_code
;*********************************************
;* *
;* BIOS Jump Vector for Individual Routines *
;* *
;*********************************************
jmp INIT ;Enter from BOOT ROM or LOADER
jmp WBOOT ;Arrive here from BDOS call 0
jmp CONST ;return console keyboard status
jmp CONIN ;return console keyboard char
jmp CONOUT ;write char to console device
jmp LISTOUT ;write character to list device
jmp PUNCH ;write character to punch device
jmp READER ;return char from reader device
jmp HOME ;move to trk 00 on cur sel drive
jmp SELDSK ;select disk for next rd/write
jmp SETTRK ;set track for next rd/write
jmp SETSEC ;set sector for next rd/write
jmp SETDMA ;set offset for user buff (DMA)
jmp READ ;read a 128 byte sector
jmp WRITE ;write a 128 byte sector
jmp LISTST ;return list status
jmp SECTRAN ;xlate logical->physical sector
jmp SETDMAB ;set seg base for buff (DMA)
jmp GETSEGT ;return offset of Mem Desc Table
jmp GETIOBF ;return I/O map byte (IOBYTE)
jmp SETIOBF ;set I/O map byte (IOBYTE)
;print signon message and initialize hardware
INIT:
mov ax,cs ;we entered with a JMPF so use
mov ds,ax ; CS: as the initial value of DS:,
mov es,ax ; and ES:
mov ss,ax ; use local stack during initialization
mov sp,offset stkbase
cld ;set forward direction
push ds ;save the DS register
mov ax,0
mov ds,ax
mov es,ax ;set ES and DS to zero
;setup interrupt 0 to address trap routine
mov int0_offset,offset int_trap
mov int0_segment,CS
mov di,4
mov si,0 ;then propagate
mov cx,510 ;trap vector to
rep movs ax,ax ;all 256 interrupts
;BDOS offset to proper interrupt
mov bdos_offset,bdos_ofst
pop ds ;restore the DS register
; Initialize the BLC 8538 printer port
mov al,0FFh
out blc_reset,al ;reset all usarts on 8538
mov al,4Eh
out ldata+2,al ;set usart 0 in async 8 bit mode
mov al,3Eh
out ldata+2,al ;set usart 0 to 9600 baud
mov al,37h
out ldata+3,al ;enable Tx/Rx, and set up RTS,DTR
mov bx,offset signon
call pmsg ;print signon message
call clear_flags ; initialize track buffering
mov cl,0 ;default to dr A: on coldstart
jmp ccp ;jump to cold start entry of CCP
WBOOT: jmp ccp+6 ;direct entry to CCP at command level
int_trap:
cli ;block interrupts
mov ax,cs
mov ds,ax ;get our data segment
mov bx,offset int_trp
call pmsg
hlt ;hardstop
;*********************************************
;* *
;* CP/M Character I/O Interface Routines *
;* Console is Usart (i8251a) on iSBC 86/12 *
;* at ports D8/DA *
;* *
;*********************************************
CONST: ;console status
in al,csts
and al,2
jz const_ret
or al,255 ;return non-zero if RDA
const_ret:
ret ;Receiver Data Available
CONIN: ;console input
call const
jz CONIN ;wait for RDA
in al,cdata
and al,7fh ;read data and remove parity bit
ret
CONOUT: ;console output
in al,csts
and al,1 ;get console status
jz CONOUT ;wait for TBE
mov al,cl
out cdata,al ;Transmitter Buffer Empty
ret ;then return data
LISTOUT: ;list device output
call LISTST
jz LISTOUT ;wait for printer not busy
mov al,cl
out ldata,al ;send char to TI 810
ret
LISTST: ;poll list status
in al,lsts
and al,81h ;look at both TxRDY and DTR
cmp al,81h
jnz zero_ret ;either false, printer is busy
or al,255 ;both true, LPT is ready
ret
PUNCH: ;not implemented in this configuration
READER:
mov al,1ah
ret ;return EOF for now
GETIOBF:
mov al,0 ;TTY: for consistency
ret ;IOBYTE not implemented
SETIOBF:
ret ;iobyte not implemented
zero_ret:
and al,0
ret ;return zero in AL and flags
; Routine to get and echo a console character
; and shift it to upper case
uconecho:
call CONIN ;get a console character
push ax
mov cl,al ;save and
call CONOUT
pop ax ;echo to console
cmp al,'a'
jb uret ;less than 'a' is ok
cmp al,'z'
ja uret ;greater than 'z' is ok
sub al,'a'-'A' ;else shift to caps
uret:
ret
; utility subroutine to print messages
pmsg:
mov al,[BX] ;get next char from message
test al,al
jz return ;if zero return
mov CL,AL
call CONOUT ;print it
inc BX
jmps pmsg ;next character and loop
;*********************************************
;* *
;* Disk Input/Output Routines *
;* *
;*********************************************
SELDSK: ;select disk given by register CL
mov cpm_disk,cl ; save the selected drive
mov bx,0000h
cmp cl,2 ;this BIOS only supports 2 disks
jnb return ;return w/ 0000 in BX if bad drive
;now, we need disk parameter address
mov ch,0
mov bx,cx ;BX = word(CL)
mov cl,4
shl bx,cl ;multiply drive code * 16
add bx,offset dp_base ;create offset from Disk Parameter Base
return:
ret
home:
test wr_flag,1 ! jnz home1 ; if the buffer is clean,
mov cur_disk,-1 ; insure we read the directory by invalidating
; the track buffer
home1:
mov cx,0 ; home is a settrk zero
settrk:
mov cpm_track,cx ; save track number for next operation
ret
setsec:
mov cpm_sec,cx ; save sector number for next operation
ret
setdma:
mov dma_offset,cx ; save DMA offset address
ret
setdmab:
mov dma_segment,cx ; save DMA segment address
ret
sectran:
mov bx,cx ; Put logical sector into dest. reg.
test dx,dx ; see if table address is zero
jz sectran_exit ; yeah, logical = physical
add bx,dx ; else, we need to fetch the
mov bl,[BX] ; actual sector number from the table
mov bh,0 ; zero high byte for good luck
sectran_exit:
ret
GETSEGT: ;return address of physical memory table
mov bx,offset seg_table
ret
read:
call track_setup
push es ; save the extra segment register
mov si,offset track_buffer ; source segment is systems DS:
add si,ax ; gives the offset into the buffer
les di,dma_longword ; point ES:DI at the users sector
rep movsw ; doit
pop es ; restore the extra segment
sub ax,ax ; make a zero return code
ret
write:
push cx ; save the write mode from the BDOS
call track_setup
push ax ; save buffer offset
push ds ; save the data segment
push es ; save the extra segment
mov bx,ds ! mov es,bx ; destination is our data segment
mov di,offset track_buffer ; destination is in track buffer
add di,ax ; plus appropriate offset
lds si,dma_longword ; source is users DMA address
rep movsw ; move that sector
pop es ; restore the extra segment
pop ds ; and the data segment registers
pop ax ; recover buffer offset
mov cx,host_sectsiz ; setup to divide by host sector size
sub dx,dx ; extend ax to 32 bits
div cx ; find out which host sector we changed
mov bx,ax ; put into index [BX]
mov sec_flags[BX],1 ; set the update flag for that sector
mov wr_flag,1 ; also set the dirty buffer flag
pop cx ; recover BDOS write code
cmp cl,1 ; is this a directory update ?
jne write_return ; no, we may leave dirty records
; in the buffer
call flush_buffer ; we have a directory write, need to
; flush the buffer to insure the
; disks integrity
write_return:
sub ax,ax ; never return BAD SECTOR code
ret
track_setup: ; common code for setting up reads and writes
mov al,cpm_disk ; see if selected disk is
cmp al,cur_disk ; the same as last time
jne wrong_track ; no, we have wrong track
mov ax,cpm_track ; see if desired track is same as
cmp ax,cur_track ; the track in the buffer
je correct_track ; same drive and track, we don't need to read
; Desired operation is on a different track then is in our buffer,
; So it will be nessecary to read in the desired track. First, we
; must check to see if any sectors of the current buffer are dirty.
wrong_track:
call flush_buffer ; write any old records, if nessecary
mov ax,cpm_track ; get desired track number
mov cur_track,ax ; make in new track
mov al,cpm_disk ; get desired disk number
mov cur_disk,al ; make it current drive
mov cur_dma_adr,offset track_buffer
; point dma offset at track buffer
mov cur_sec,host_fsn ; starting from first sector
call track_read ; load the track
correct_track:
mov ax,cpm_sec ; get the cp/m sector number
if (host_fsn ne 0)
sub ax,host_fsn ; correct if we start with sector one
endif
mov cl,7 ; log2(128)
shl ax,cl ; sector times 128 gives offset
mov cx,64 ! cld ; move 64 words forward
ret
flush_buffer:
test wr_flag,1 ; see if we have anything to write
jz no_flush ; no, skip scanning for dirty sectors
mov bx,host_fsn ; start at first host sector
mov cx,host_spt ; for host_spt sectors...
next_sect:
test sec_flags-host_fsn[BX],1 ; see if this sector has been changed
jz not_updated ; no, leave it alone
mov sec_flags-host_fsn[BX],0 ; zero the flag for next time
push bx ; save the registers
push cx
mov cur_sec,bx ; save host sector number
mov ax,host_sectsiz
if (host_fsn ne 0)
sub bx,host_fsn
endif
mul bx ; make track buffer offset
add ax,offset track_buffer ; make direct pointer
mov cur_dma_adr,ax ; save for write routine
call sector_write
pop cx
pop bx
not_updated:
inc bx
loop next_sect
no_flush:
mov wr_flag,0 ; clear the dirty buffer flag
ret
clear_flags: ; Clear all variables associated with the track buffer,
; so next operation will have to read a track.
; This is involves clearing all write flags and setting
; the old drive code to the invalid -1.
push es ; save extra segment
mov cur_disk,-1 ; insure initial pre-read
sub ax,ax ; make a zero
mov wr_flag,al ; clear the dirty buffer flag
mov di,offset sec_flags ; point to the update flag list
mov bx,ds ! mov es,bx ; ES <- DS
mov cx,host_spt ! cld ; set length and direction
rep stosb ; zero the sector update flags
mov cur_dma_seg,ds ; get our segment address
pop es ; recover extra segment
ret
track_READ:
mov io_com,4 ; read track takes 4 byte command
mov io_com+1,13h ; special read track command
jmps r_w_common
sector_WRITE:
mov io_com,3 ; write sector takes 3 byte command
mov io_com+1,0ah ; basic write sector command
r_w_common:
cmp cur_disk,1 ! jne not_first_b ; see if drive B
test b_first_flag,-1 ! jz not_first_b ; and first reference to B
call restore ; then restore drive B
mov b_first_flag,0 ; and clear flag
not_first_b:
mov bx,offset io_com ;point to command string
mov ax,cur_sec ; put sector in
mov sect,al ; iopb as 8 bits
mov ax,cur_track ; same with
mov trk,al ; track . .
; fall into execute and return
execute: ;execute command string.
;[BX] points to length,
; followed by Command byte,
; followed by length-1 parameter bytes
mov last_com,BX ;save command address for retries
outer_retry:
;allow some retrying
mov rtry_cnt,max_retries
retry:
mov BX,last_com
call send_com ;transmit command to i8271
; check status poll
mov BX,last_com
mov al,1[bx] ;get command op code
mov cx,0800h ;mask if it will be "int req"
cmp al,2ch
jb exec_poll ;ok if it is an interrupt type
mov cx,8080h ;else we use "not command busy"
and al,0fh
cmp al,0ch ;unless there isn't
mov al,0
ja exec_exit ; any result
;poll for bits in CH,
exec_poll: ; toggled with bits in CL
in al,fdc_stat ;read status
and al,ch
xor al,cl ; isolate what we want to poll
jz exec_poll ;and loop until it is done
;Operation complete,
in al,fdc_rslt ; see if result code indicates error
and al,1eh
jz exec_exit ;no error, then exit
;some type of error occurred . . .
cmp al,10h
je dr_nrdy ;was it a not ready drive ?
;no,
dr_rdy: ; then we just retry read or write
push ax ; save error code
call restore ; after physically homing this disk
pop ax ; recover error code
dec rtry_cnt
jnz retry ; up to 10 times
; retries do not recover from the
; hard error
mov ah,0
mov bx,ax ;make error code 16 bits
mov bx,errtbl[BX]
call pmsg ;print appropriate message
in al,cdata ;flush usart receiver buffer
call uconecho ;read upper case console character
cmp al,'C'
je wboot_l ;cancel
cmp al,'R'
je outer_retry ;retry 10 more times
cmp al,'I'
je z_ret ;ignore error
or al,255 ;set code for permanent error
exec_exit:
ret
dr_nrdy: ;here to wait for drive ready
call test_ready
jnz retry ;if it's ready now we are done
call test_ready
jnz retry ;if not ready twice in row,
mov bx,offset nrdymsg
call pmsg ;"Drive Not Ready"
nrdy01:
call test_ready
jz nrdy01 ;now loop until drive ready
jmps retry ;then go retry without decrement
zret:
and al,0
ret ;return with no error code
wboot_l: ;can't make it w/ a short leap
jmp WBOOT
;*********************************************
;* *
;* The i8271 requires a read status command *
;* to reset a drive-not-ready after the *
;* drive becomes ready *
;* *
;*********************************************
test_ready:
mov dh, 40h ;proper mask if dr 1
test sel_mask,80h
jnz nrdy2
mov dh, 04h ;mask for dr 0 status bit
nrdy2:
mov bx,offset rds_com
call send_com
dr_poll:
in al,fdc_stat ;get status word
test al,80h
jnz dr_poll ;wait for not command busy
in al,fdc_rslt ;get "special result"
test al,dh ;look at bit for this drive
ret ;return status of ready
restore: ;move selected disk to home position (Track 0)
mov bx,offset hom_com
call execute
jz restore_exit ;home drive and return if OK
mov bx,offset bad_hom ;else print
call pmsg ;"Home Error"
jmps restore ;and retry
restore_exit:
ret
;*********************************************
;* *
;* Send_com sends a command and parameters *
;* to the i8271: BX addresses parameters. *
;* The DMA controller is also initialized *
;* if this is a read or write *
;* *
;*********************************************
send_com:
in al,fdc_stat
test al,80h ;insure command not busy
jnz send_com ;loop until ready
;see if we have to initialize for a DMA operation
mov al,1[bx] ;get command byte
cmp al,13h
jne write_maybe ;if not a read it could be write
mov cl,40h
jmps init_dma ;is a read command, go set DMA
write_maybe:
cmp al,0ah
jne dma_exit ;leave DMA alone if not read or write
mov cl,80h ;we have write, not read
init_dma:
;we have a read or write operation, setup DMA controller
; (CL contains proper direction bit)
mov al,04h
out dmac_mode,al ;enable dmac
mov al,00
out dmac_cont,al ;send first byte to control port
mov al,cl
out dmac_cont,al ;load direction register
mov ax,cur_dma_adr
out dmac_adr,al ;send low byte of DMA
mov al,ah
out dmac_adr,al ;send high byte
mov ax,cur_dma_seg
out fdc_segment,al ;send low byte of segment address
mov al,ah
out fdc_segment,al ;then high segment address
dma_exit:
mov cl,[BX] ;get count
inc BX
mov al, 80h
cmp cur_disk,0
jne sel1 ;drive 1 if not zero
mov al, 40h ;else drive is 0
sel1: mov sel_mask,al ; save select mask
or al,[BX] ;merge command and drive code
out fdc_com,al ;send command byte
parm_loop:
dec cl
jz parm_exit ;no (more) parameters, return
inc BX ;point to (next) parameter
parm_poll:
in al,fdc_stat
test al,20h ;test "parameter register full" bit
jnz parm_poll ;idle until parm reg not full
mov al,[BX]
out fdc_parm,al ;send next parameter
jmps parm_loop ;go see if there are more parameters
parm_exit:
ret
;*********************************************
;* *
;* Data Areas *
;* *
;*********************************************
data_offset equ offset $
dseg
org data_offset ;contiguous with code segment
signon db cr,lf,cr,lf
db ' CP/M-86, 1 Feb 82 ',cr,lf,0
bad_hom db cr,lf,'Home Error',cr,lf,0
int_trp db cr,lf,'Interrupt Trap Halt',cr,lf,0
errtbl dw er0,er1,er2,er3
dw er4,er5,er6,er7
dw er8,er9,erA,erB
dw erC,erD,erE,erF
er0 db cr,lf,'Error Code 0 :',0
er1 db cr,lf,'Error Code 1 :',0
er2 db cr,lf,'Error Code 2 :',0
er3 db cr,lf,'Error Code 3 :',0
er4 db cr,lf,'Clock Error :',0
er5 db cr,lf,'Late DMA :',0
er6 db cr,lf,'ID CRC Error :',0
er7 db cr,lf,'Data CRC Error :',0
er8 db cr,lf,'Drive Not Ready :',0
er9 db cr,lf,'Write Protect :',0
erA db cr,lf,'Track 00 Not Found :',0
erB db cr,lf,'Write Fault :',0
erC db cr,lf,'Sector Not Found :',0
erD db cr,lf,'Error Code D :',0
erE db cr,lf,'Error Code E :',0
erF db cr,lf,'Error Code F :',0
nrdymsg equ er8
b_first_flag db -1
rtry_cnt db 0 ;disk error retry counter
last_com dw 0 ;address of last command string
cur_dma_adr dw 0 ;dma offset stored here
cur_dma_seg dw 0 ;dma segment stored here
sel_mask db 40h ;select mask, 40h or 80h
; Various command strings for i8271
io_com db 3 ;length (changed to 4 to read a track)
rd_wr db 0 ;read/write function code
trk db 0 ;track #
sect db 0 ;sector #
sec_len db 26 ;transfer 26 sectors on a track read
hom_com db 2,29h,0 ;home drive command
rds_com db 1,2ch ;read status command
; Track buffering variables
cpm_disk rb 1
cpm_track rw 1
cpm_sec rw 1
cur_disk rb 1
cur_track rw 1
cur_sec rw 1
dma_offset rw 1
dma_segment rw 1
dma_longword equ dword ptr dma_offset
bdos_wr_code rb 1
wr_flag rb 1
; System Memory Segment Table
segtable db 2 ;2 segments
dw tpa_seg ;1st seg starts after BIOS
dw tpa_len ;and extends to 08000
dw 1000h ;second is 10000 -
dw 1000h ;1FFFF (64k)
; include singles.lib ;read in disk definitions
; DISKS 2
dpbase equ $ ;Base of Disk Parameter Blocks
dpe0 dw xlt0,0000h ;Translate Table
dw 0000h,0000h ;Scratch Area
dw dirbuf,dpb0 ;Dir Buff, Parm Block
dw csv0,alv0 ;Check, Alloc Vectors
dpe1 dw xlt1,0000h ;Translate Table
dw 0000h,0000h ;Scratch Area
dw dirbuf,dpb1 ;Dir Buff, Parm Block
dw csv1,alv1 ;Check, Alloc Vectors
; DISKDEF 0,1,26,6,1024,243,64,64,2
;
; 1944: 128 Byte Record Capacity
; 243: Kilobyte Drive Capacity
; 64: 32 Byte Directory Entries
; 64: Checked Directory Entries
; 128: Records / Extent
; 8: Records / Block
; 26: Sectors / Track
; 2: Reserved Tracks
; 6: Sector Skew Factor
;
dpb0 equ offset $ ;Disk Parameter Block
dw 26 ;Sectors Per Track
db 3 ;Block Shift
db 7 ;Block Mask
db 0 ;Extnt Mask
dw 242 ;Disk Size - 1
dw 63 ;Directory Max
db 192 ;Alloc0
db 0 ;Alloc1
dw 16 ;Check Size
dw 2 ;Offset
xlt0 equ offset $ ;Translate Table
db 1,7,13,19
db 25,5,11,17
db 23,3,9,15
db 21,2,8,14
db 20,26,6,12
db 18,24,4,10
db 16,22
als0 equ (243+7)/8 ;Allocation Vector Size
css0 equ 16 ;Check Vector Size
; DISKDEF 1,0
;
; Disk 1 is the same as Disk 0
;
dpb1 equ dpb0 ;Equivalent Parameters
als1 equ als0 ;Same Allocation Vector Size
css1 equ css0 ;Same Checksum Vector Size
xlt1 equ xlt0 ;Same Translate Table
; ENDEF
;
; Uninitialized Scratch Memory Follows:
;
dirbuf rs 128 ;Directory Buffer
alv0 rs als0 ;Alloc Vector
csv0 rs css0 ;Check Vector
alv1 rs als1 ;Alloc Vector
csv1 rs css1 ;Check Vector
sec_flags rb host_spt
track_buffer rb host_spt*host_sectsiz
loc_stk rw 32 ;local stack for initialization
stkbase equ offset $
lastoff equ offset $
tpa_seg equ ((lastoff+07FFh)/0400h)*40h ; round off to 1K boundary
tpa_len equ 0800h - tpa_seg
db 0 ;fill last address for GENCMD
;*********************************************
;* *
;* Dummy Data Section for Interrupt Vectors *
;* *
;*********************************************
dseg 0 ;absolute low memory
org 0 ;(interrupt vectors)
int0_offset rw 1
int0_segment rw 1
; pad to system call vector
org bdos_int * 4
bdos_offset rw 1
bdos_segment rw 1
END