mirror of https://github.com/wwarthen/RomWBW.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1502 lines
43 KiB
1502 lines
43 KiB
;********************************************************
|
|
; Bob's very simple BIOS.
|
|
;
|
|
; Work started in, oh, I don't know, probably 2008.
|
|
|
|
; Work RE-started on 02/14/2013
|
|
;
|
|
; Hardware description:
|
|
;
|
|
; Northstar ZPU
|
|
; CCS 2710 serial port. Console on first port.
|
|
; N8VEM 4MB RAM with only 1 MB installed
|
|
; N8VEM IDE with a CF card
|
|
;
|
|
; This is a merge of many pieces of code from all over
|
|
; the net, very little of it is mine other than some
|
|
; glue logic. So, here are some of the sources of info
|
|
; and code I borrowed from:
|
|
;
|
|
; www.speakeasy.org/~rzh/bios.mac
|
|
; http://www.s100computers.com/My%20System%20Pages/IDE%20Board/My%20IDE%20Card.htm
|
|
;
|
|
; These are some of my include files to make things easier
|
|
; for me.
|
|
;
|
|
#include "tasm.inc"
|
|
#include "cpm22.inc"
|
|
;
|
|
; Put out a bit of diagnostics
|
|
;
|
|
.echo "Building a BIOS for a RAM size of "
|
|
.echo RAMSIZE
|
|
.echo "K\n"
|
|
;
|
|
; Common CP/M items
|
|
;
|
|
DefaultDisk equ TDRIVE ;I like this name better
|
|
BDOSEntry equ (CCPBASE+CCPSIZE+6)
|
|
CPM_SECTOR_SIZE equ 128
|
|
;
|
|
; Base ports of various I/O devices.
|
|
;
|
|
IDE_PORT EQU 030H ;IDE board
|
|
;
|
|
; Base memory locations of various things
|
|
;
|
|
CCPEntry EQU CCPBASE
|
|
CPM_VERSION .equ 22H ;CP/M version
|
|
;
|
|
; Constants
|
|
;
|
|
FALSE .equ 0
|
|
TRUE .equ ~FALSE
|
|
;
|
|
; ASCII constants
|
|
;
|
|
CR .equ 0dh
|
|
LF .equ 0ah
|
|
BELL .equ 07h
|
|
EOF .equ 01ah ;CTRL-Z is CP/M EOF
|
|
;
|
|
; IDE board constants
|
|
;
|
|
IDEportA EQU IDE_PORT+0 ;Lower 8 bits of IDE interface (8255)
|
|
IDEportB EQU IDE_PORT+1 ;Upper 8 bits of IDE interface
|
|
IDEportC EQU IDE_PORT+2 ;Control lines for IDE interface
|
|
IDEportCtrl EQU IDE_PORT+3 ;8255 configuration port
|
|
|
|
READcfg8255 EQU 10010010b ;Set 8255 IDEportC out, IDEportA/B input
|
|
WRITEcfg8255 EQU 10000000b ;Set all three 8255 ports output
|
|
|
|
;IDE control lines for use with IDEportC. Change these 8
|
|
;constants to reflect where each signal of the 8255 each of the
|
|
;IDE control signals is connected. All the control signals must
|
|
;be on the same port, but these 8 lines let you connect them to
|
|
;whichever pins on that port.
|
|
|
|
IDEa0line EQU 01H ;direct from 8255 to IDE interface
|
|
IDEa1line EQU 02H ;direct from 8255 to IDE interface
|
|
IDEa2line EQU 04H ;direct from 8255 to IDE interface
|
|
IDEcs0line EQU 08H ;inverter between 8255 and IDE interface
|
|
IDEcs1line EQU 10H ;inverter between 8255 and IDE interface
|
|
IDEwrline EQU 20H ;inverter between 8255 and IDE interface
|
|
IDErdline EQU 40H ;inverter between 8255 and IDE interface
|
|
IDErstline EQU 80H ;inverter between 8255 and IDE interface
|
|
;
|
|
;Symbolic constants for the IDE Drive registers, which makes the
|
|
;code more readable than always specifying the address pins
|
|
|
|
REGdata EQU IDEcs0line
|
|
REGerr EQU IDEcs0line + IDEa0line
|
|
REGseccnt EQU IDEcs0line + IDEa1line
|
|
REGsector EQU IDEcs0line + IDEa1line + IDEa0line
|
|
REGcylinderLSB EQU IDEcs0line + IDEa2line
|
|
REGcylinderMSB EQU IDEcs0line + IDEa2line + IDEa0line
|
|
REGshd EQU IDEcs0line + IDEa2line + IDEa1line ;(0EH)
|
|
REGcommand EQU IDEcs0line + IDEa2line + IDEa1line + IDEa0line ;(0FH)
|
|
REGstatus EQU IDEcs0line + IDEa2line + IDEa1line + IDEa0line
|
|
REGcontrol EQU IDEcs1line + IDEa2line + IDEa1line
|
|
REGastatus EQU IDEcs1line + IDEa2line + IDEa1line + IDEa0line
|
|
|
|
;IDE Command Constants. These should never change.
|
|
|
|
COMMANDrecal EQU 10H
|
|
COMMANDread EQU 20H
|
|
COMMANDwrite EQU 30H
|
|
COMMANDinit EQU 91H
|
|
COMMANDid EQU 0ECH
|
|
COMMANDspindown EQU 0E0H
|
|
COMMANDspinup EQU 0E1H
|
|
;
|
|
;
|
|
; IDE Status Register:
|
|
; bit 7: Busy 1=busy, 0=not busy
|
|
; bit 6: Ready 1=ready for command, 0=not ready yet
|
|
; bit 5: DF 1=fault occured insIDE drive
|
|
; bit 4: DSC 1=seek complete
|
|
; bit 3: DRQ 1=data request ready, 0=not ready to xfer yet
|
|
; bit 2: CORR 1=correctable error occured
|
|
; bit 1: IDX vendor specific
|
|
; bit 0: ERR 1=error occured
|
|
;
|
|
;
|
|
|
|
;
|
|
; A Z80 JP instruction is manually put into a few locations
|
|
; by the BIOS, so here is an equate for that instruction.
|
|
;
|
|
JMP equ 0c3h
|
|
.page
|
|
;********************************************************
|
|
; PRIMARY JUMP TABLE. ALL CALLS FROM CP/M TO THE CBIOS
|
|
; COME THROUGH THIS TABLE.
|
|
;********************************************************
|
|
;
|
|
.org BIOSBASE
|
|
;
|
|
JP CBOOT ;COLD BOOT
|
|
WBOOTE: JP WBOOT ;WARM BOOT
|
|
JP CONST ;CONSOLE STATUS
|
|
JP CONIN ;CONSOLE CHARACTER IN
|
|
JP CONOUT ;CONSOLE CHARACTER OUT,
|
|
JP LIST ;LIST CHARACTER OUT
|
|
JP PUNCH ;PUNCH CHARACTER OUT
|
|
JP READER ;READER CHARACTER IN
|
|
JP HOME ;MOVE HEAD TO HOME POSITION
|
|
JP SELDSK ;SELECT DISK
|
|
JP SETTRK ;SET TRACK NUMBER
|
|
JP SETSEC ;SET SECTOR NUMBER
|
|
JP SETDMA ;SET DMA ADDRESS
|
|
JP READ ;READ DISK
|
|
JP WRITE ;WRITE DISK
|
|
JP LISTST ;RETURN LIST STATUS
|
|
JP SECTRA ;SECTOR TRANSLATE
|
|
;
|
|
;********************************************************
|
|
; Sneaking little Bob trick. My program to write CP/M
|
|
; to the disk needs to know the end of the BIOS, so I
|
|
; decided to put the last memory location used by the
|
|
; BIOS into the two bytes before the CBOOT code. So the
|
|
; program can find the end by picking up the first JP
|
|
; address in the jump table, and backing up two bytes.
|
|
;
|
|
DW BIOS_END
|
|
.page
|
|
;
|
|
;********************************************************
|
|
;
|
|
; This is the cold boot entry point, called from the first
|
|
; entry in the jump table. Control will be transfered here
|
|
; by the CP/M bootstrap loader.
|
|
;
|
|
|
|
CBOOT: ld sp,0080h ;set stack pointer
|
|
call initser ;initialize the serial port
|
|
call IDEinit ;initialize the IDE interface
|
|
|
|
|
|
call msg
|
|
.db CR,LF,LF
|
|
.text "Bob's BIOS :)"
|
|
.db CR,LF
|
|
.text "??"
|
|
.text "K CP/M v2.2"
|
|
.db CR,LF,
|
|
.text "Drives:"
|
|
.db CR,LF
|
|
.text " A: = IDE"
|
|
.db CR,LF,LF,0
|
|
|
|
gocpm: di ;disable interrupts
|
|
;
|
|
; Reset the IOBYTE to go to the main terminal
|
|
;
|
|
ld a,00000001B
|
|
ld (IOBYTE),a
|
|
;
|
|
; Set the default drive to A: and user to 0
|
|
;
|
|
xor a
|
|
ld (DefaultDisk),a
|
|
;
|
|
jp EnterCPM ;continue set-up
|
|
;
|
|
;********************************************************
|
|
; WARNING... The warm boot code is NOT COMPLETE!!!
|
|
;
|
|
WBOOT:
|
|
call msg
|
|
TEXT "Entering WBOOT"
|
|
db CR,LF,0
|
|
|
|
;
|
|
; Need to add a lot of logic here to reload the CCP and
|
|
; BDOS from disk, but for now we know they're already
|
|
; loaded and no need to load again.
|
|
|
|
|
|
jp EnterCPM
|
|
;
|
|
;********************************************************
|
|
;
|
|
; This routine is entered either from the cold or warm
|
|
; boot code. It sets up the JP instructions in the
|
|
; base page, and also sets the high-level disk driver's
|
|
; input/output address (also known as the DMA address).
|
|
;
|
|
EnterCPM: ld a,JMP ;Get machine code for JP
|
|
ld (0),a ;Set up Jp at location 0
|
|
ld (ENTRY),a ;...and at location 5
|
|
;
|
|
ld hl,WBOOTE ;Get BIOS vector address
|
|
ld (1),hl ;Put it at location 1
|
|
|
|
ld hl,BDOSEntry ;Get BDOS entry point address
|
|
ld (ENTRY+1),hl ;Put it at location 6
|
|
;
|
|
ld bc,80H ;Set disk I/O address to default
|
|
call SETDMA ;Use normal BIOS routine
|
|
;
|
|
ei ;Ensure interrupts are enabled
|
|
ld a,(DefaultDisk) ;Transfer current default disk to
|
|
ld c,a ; Console Command Processor
|
|
jp CCPEntry ;Jump to CCP
|
|
|
|
|
|
|
|
;********************************************************
|
|
; Select the disk drive for future reads/writes. On entry
|
|
; C contains the drive index: C = 0 for A:, 1 for B:, etc.
|
|
; On Exit, HL contains the pointer to the disk parameter
|
|
; header (section 6.10 in the CP/M book) or 0000 if the
|
|
; requested drive is out of range.
|
|
;********************************************************
|
|
|
|
SELDSK: push bc
|
|
call msg
|
|
TEXT "SELDSK: "
|
|
db 0
|
|
pop bc
|
|
push bc
|
|
ld a,c
|
|
add a,'A'
|
|
ld c,a
|
|
call CONOUT
|
|
call msg
|
|
db ':',CR,LF,0
|
|
pop bc
|
|
;
|
|
; Temp patch... always do deblocking for now. Some day
|
|
; I might add a disk that doesn't need it, but for now
|
|
; we always need it.
|
|
;
|
|
push af
|
|
ld a,0ffh
|
|
ld (DeblockingRequired),a
|
|
pop af
|
|
;
|
|
ld hl,0 ;assume an error
|
|
ld a,c ;move drive into A
|
|
cp 1 ;compare to max drives
|
|
ret nc ;return if bad drive
|
|
;
|
|
; Compute offset to disk parameter table... drive * 16
|
|
;
|
|
ld l,a
|
|
ld h,0
|
|
add hl,hl ;*2
|
|
add hl,hl ;*4
|
|
add hl,hl ;*8
|
|
add hl,hl ;*16
|
|
ld de,DPBASE
|
|
add hl,de
|
|
ret
|
|
;
|
|
;********************************************************
|
|
; FLOPPY DISK DPH TABLES.
|
|
;********************************************************
|
|
;
|
|
; CP/M manual, 6.10, page145
|
|
;
|
|
DPBASE:
|
|
IdeDPH: dw 0 ;no sector translation
|
|
dw 0 ;scratchpad
|
|
dw 0
|
|
dw 0
|
|
dw DirectoryBuffer
|
|
dw IdeDPB ;disk parameter block
|
|
dw IdeCSV ;checksum area
|
|
dw IdeALV
|
|
|
|
IdeCSV: .FILL 0 ;no directory checksum; non-removable drive
|
|
IdeALV: .FILL 256,0 ;drive size / BLS / 8
|
|
|
|
;__________________________________________________________________________________________________
|
|
;
|
|
; 8MB HARD DISK DRIVE, 65 TRKS, 1024 SECS/TRK, 128 BYTES/SEC
|
|
; BLOCKSIZE (BLS) = 4K, DIRECTORY ENTRIES = 128
|
|
; SEC/TRK ENGINEERED SO THAT AFTER DEBLOCKING, SECTOR NUMBER OCCUPIES 1 BYTE (0-255)
|
|
;
|
|
; The SPT value was taken from the track count from MYIDE (an Andrew Lynch
|
|
; program) and then multiplied by 4 since there are 4 CP/M 128 byte sectors
|
|
; in each 512 byte physical sector.
|
|
;
|
|
; Too many magic numbers in here; add some EQUs for these and use real math
|
|
; to compute the values at assembly time.
|
|
;
|
|
.DB (4096 / 128) ; RECORDS PER BLOCK (BLS / 128)
|
|
IdeDPB:
|
|
; .DW 1024 ; SPT: SECTORS PER TRACK
|
|
.DW 32*4 ; SPT: Sectors per track, gotten from drive
|
|
.DB 5 ; BSH: BLOCK SHIFT FACTOR
|
|
.DB 31 ; BLM: BLOCK MASK
|
|
.DB 1 ; EXM: EXTENT MASK
|
|
.DW 2047 ; DSM: TOTAL STORAGE IN BLOCKS - 1 BLK = ((8MB - 128K OFF) / 4K BLS) - 1 = 2047
|
|
.DW 511 ; DRM: DIR ENTRIES - 1 = 512 - 1 = 511
|
|
.DB 11110000B ; AL0: DIR BLK BIT MAP, FIRST BYTE
|
|
.DB 00000000B ; AL1: DIR BLK BIT MAP, SECOND BYTE
|
|
.DW 0 ; CKS: DIRECTORY CHECK VECTOR SIZE = 256 / 4
|
|
.DW 1 ; OFF: RESERVED TRACKS = 1 TRKS * (512 B/SEC * 1024 SEC/TRK) = 128K
|
|
;
|
|
DirectoryBuffer ds CPM_SECTOR_SIZE
|
|
|
|
|
|
;
|
|
; OLD CODE WARNING...
|
|
; This is my first attempt to calculate disk parameters. Left it all in
|
|
; here because it's the right way to do it, but needs debugging.
|
|
;
|
|
;SECSZ: equ 512 ;bytes per sector
|
|
;CPMSECS: equ (SECSZ/128) ;CP/M records per phys sector
|
|
;SECTORS: equ 64 ;sectors per track
|
|
;TRACKS: equ (65536/SECTORS/CPMSECS);
|
|
;
|
|
;SPT: equ (SECTORS*CPMSECS)
|
|
;BLS: equ 8192
|
|
;BLM: equ (BLS/128-1)
|
|
; #if (BLS == 1024)
|
|
;BSH equ 3
|
|
; #endif
|
|
; #if (BLS == 2048)
|
|
;BSH equ 4
|
|
; #endif
|
|
; #if (BLS == 4096)
|
|
;BSH equ 5
|
|
; #endif
|
|
; #if (BLS == 8192)
|
|
;BSH equ 6
|
|
; #endif
|
|
; #if (BLS == 16384)
|
|
;BSH equ 7
|
|
; #endif
|
|
;OFF: equ 1
|
|
;DSM: equ (((TRACKS-OFF)*SECSZ*SECTORS/BLS)-1)
|
|
; #if (DSM < 256)
|
|
;EXM equ (BLS/1024-1)
|
|
; #else
|
|
;EXM equ (BLS/2048-1)
|
|
; #endif
|
|
;DRM: equ (1024-1);is this a good value?
|
|
;
|
|
;fldpb: .dw SPT ;CP/M records per track
|
|
; .db BSH ;allocation block shift
|
|
; .db BLM ;allocation block mask
|
|
; .db 0 ;extent mask
|
|
; .dw DSM ;max allocation block #
|
|
; .dw 255 ;max directory entry #
|
|
; .db 0f0h,0 ;directory allocation mask
|
|
; .dw 64 ;directory check alloc size
|
|
; .dw OFF ;number of reserved tracks
|
|
|
|
;IdeDPB: dw SECTORS ;sectors per track
|
|
; db BSH
|
|
; db BLM
|
|
; db EXM
|
|
; dw DSM
|
|
; dw DRM
|
|
; db AL0
|
|
; db AL1
|
|
; dw CKS
|
|
; dw OFF
|
|
|
|
|
|
;dphbase0: .dw trans ;LOGICAL TO PHYSICAL XLATE TAB
|
|
; .dw 0 ;SCRATCH
|
|
; .dw 0
|
|
; .dw 0
|
|
; .dw DIRBUF ;DIRECTORY BUFFER
|
|
; .dw fldpb ;DISK PARAMETER BLOCK
|
|
; .dw chk00 ;CHECKSUM VECTOR
|
|
; .dw ALV0 ;ALLOCATION VECTOR
|
|
;
|
|
;dphbase1: .dw trans
|
|
; .dw 0
|
|
; .dw 0
|
|
; .dw 0
|
|
; .dw DIRBUF
|
|
; .dw fldpb
|
|
; .dw chk01
|
|
; .dw ALV1
|
|
;
|
|
;********************************************************
|
|
; Sector translation table. The IDE drive does not do
|
|
; translation but left it here for future use. Ie, it
|
|
; might be used later but could be removed for now.
|
|
;
|
|
trans: .db 0,1,2,3,4,5,6,7
|
|
.db 8,9,10,11,12,13,14,15
|
|
.db 16,17,18,19,20,21,22,23
|
|
.db 24,25,26,27,28,29,30,31
|
|
;
|
|
;********************************************************
|
|
; CP/M DPB
|
|
;
|
|
; AGAIN, OLD CODE WARNING!!!! Some of this was carry-over
|
|
; from my previous attempt to read BoGUS format disks.
|
|
; BoGUS was a CP/M box made by Franklin Computer in 1984.
|
|
; There are about 3 in existance today.
|
|
;
|
|
; BoGUS = Bob Grieb's Underground System
|
|
;
|
|
; This has always been one of the most confusing bits of
|
|
; CP/M legacy. To make things a tad easier, here are some
|
|
; bits of data that will make reading section 6.10 of the
|
|
; "CP/M Operating System Manual" a bit clearer.
|
|
;
|
|
; The CF drive I'm using (Sandisk SDCFB-192) has the
|
|
; following raw parameters. Hex numbers first, decimal
|
|
; in parens:
|
|
;
|
|
; Cylinders: 02DE (734)
|
|
; Heads: 10 (16)
|
|
; Sectors: 20 (32)
|
|
; Sector size: (512)
|
|
;
|
|
; Total disk size = 734*16*32*512 = 192,413,696 bytes
|
|
;
|
|
; SPT = 128 Sectors per track
|
|
; BLS =
|
|
; 1 Reserved track
|
|
|
|
;
|
|
; BLS = 2048 (why, I don't know)
|
|
; 9 physical sectors per track, 4 CP/M sectors each
|
|
; DOuble sided
|
|
; 2 reserved tracks
|
|
;
|
|
;TRACKS: .equ (80 * 2);total tracks (count both sides)
|
|
;SECTORS: .equ 9 ;sectors per track
|
|
;SECSZ: .equ 512 ;bytes per sector
|
|
;CPMSECS: .equ (SECSZ/128) ;CP/M records per phys sector
|
|
;
|
|
;SPT: .equ (SECTORS*CPMSECS)
|
|
;BLS: .equ 2048 ;ask Dave ask to why
|
|
;BSH: .equ 4
|
|
;BLM: .equ ((BLS/128)-1)
|
|
;OFF: .equ 2
|
|
;DSM: .equ (((TRACKS-OFF)*SECSZ*SECTORS/BLS)-1)
|
|
;
|
|
;fldpb: .dw SPT ;CP/M records per track
|
|
; .db BSH ;allocation block shift
|
|
; .db BLM ;allocation block mask
|
|
; .db 0 ;extent mask
|
|
; .dw DSM ;max allocation block #
|
|
; .dw 255 ;max directory entry #
|
|
; .db 0f0h,0 ;directory allocation mask
|
|
; .dw 64 ;directory check alloc size
|
|
; .dw OFF ;number of reserved tracks
|
|
|
|
|
|
|
|
|
|
|
|
;********************************************************
|
|
; SET TRACK FOR FUTURE READS OR WRITES TO TRACK 0. ALSO
|
|
; PARTIALLY RESET THE DISK SYSTEM TO ALLOW FOR CHANGED
|
|
; DISKS.
|
|
;********************************************************
|
|
|
|
HOME: call msg
|
|
.text "HOME"
|
|
db CR,LF,0
|
|
|
|
ld a,(MustWriteBuffer) ;unwritten data?
|
|
or a
|
|
jp nz,HomeNoWrite
|
|
ld (DataInDiskBuffer),a
|
|
HomeNoWrite: ld c,0 ;set to track 0
|
|
jp SETTRK
|
|
|
|
|
|
;********************************************************
|
|
; SET TRACK FOR FUTURE READS OR WRITES TO THAT PASSED
|
|
; IN REGISTER PAIR BC.
|
|
;********************************************************
|
|
|
|
SETTRK: ld l,c
|
|
ld h,b
|
|
ld (SelectedTrack),hl
|
|
|
|
; call msg
|
|
; db "SETTRK: ",0
|
|
; ld a,h
|
|
; call a_hex
|
|
; ld a,l
|
|
; call a_hex
|
|
; call crlf
|
|
|
|
ret
|
|
|
|
;********************************************************
|
|
; SET SECTOR FOR FUTURE READS OR WRITES TO THAT PASSED
|
|
; IN REGISTER PAIR BC.
|
|
;********************************************************
|
|
|
|
SETSEC: ld a,c
|
|
ld (SelectedSector),a
|
|
|
|
; push af
|
|
; call msg
|
|
; db "SETSEC: ",0
|
|
; pop af
|
|
; call a_hex
|
|
; call crlf
|
|
ret
|
|
|
|
;********************************************************
|
|
; SET DMA ADDRESS FOR FUTURE READS OR WRITES TO THAT
|
|
; PASSED IN REGISTER PAIR BC.
|
|
;********************************************************
|
|
|
|
SETDMA: ld l,c
|
|
ld h,b
|
|
ld (DMAAddress),hl
|
|
|
|
#IF 0 ;debug code
|
|
push hl ;DEBUG
|
|
call msg
|
|
db "SETDMA: ",0
|
|
pop hl
|
|
push hl
|
|
ld a,h
|
|
call a_hex
|
|
ld a,l
|
|
call a_hex
|
|
call crlf
|
|
pop hl ;END DEBUG
|
|
#ENDIF
|
|
|
|
ret
|
|
|
|
;********************************************************
|
|
; SECTOR TRANSLATION ROUTINE. THE ROUTINE ONLY
|
|
; TRANSLATES SECTORS ON THE USER TRACKS, SINCE CP/M
|
|
; ACCESSES THE SYSTEM TRACKS WITHOUT CALLING FOR
|
|
; TRANSLATION.
|
|
; BC contains the sector number to translate, and the
|
|
; translation table address is in DE. Returns result
|
|
; in HL.
|
|
;********************************************************
|
|
|
|
SECTRA: ld l,c ;no translation
|
|
ld h,b
|
|
ret
|
|
|
|
;********************************************************
|
|
; The main events... read and write routines! They share
|
|
; a lot of common code, so each of these is pretty small,
|
|
; falls into common area, then eventually splits again.
|
|
;********************************************************
|
|
|
|
READ:
|
|
; Read in the 128-byte CP/M sector specified by previous calls
|
|
; to select disk and to set track and sector. The sector will be read
|
|
; into the address specified in the previous call to set DMA address.
|
|
;
|
|
; If reading from a disk drive using sectors larger than 128 bytes,
|
|
; deblocking code will be used to "unpack" a 128-byte sector from
|
|
; the physical sector.
|
|
|
|
; call msg
|
|
; db "READ",CR,LF,0
|
|
ld a,(DeblockingRequired) ;Check if deblocking needed
|
|
or a ;(flag was set in SELDSK call)
|
|
jp z,ReadNoDeblock ;No, use normal nondeblocked
|
|
|
|
; The deblocking algorithm used is such
|
|
; that a read operation can be viewed
|
|
; up until the actual data transfer as
|
|
; though it was the first write to an
|
|
; unallocated allocation block.
|
|
|
|
xor a ;Set the record count to 0
|
|
ld (UnallocatedRecordCount),a ; for first "write"
|
|
inc a ;Indicate that it is really a read
|
|
ld (ReadOperation),a ;that is to be performed
|
|
ld (MustPrereadSector),a ;and force a preread of the sector
|
|
;to get it into the disk buffer
|
|
ld a,WriteUnallocated ;Fake deblocking code into responding
|
|
ld (WriteType),a ;as if this is the first write to an
|
|
;unallocated allocation block.
|
|
jp PerformReadWrite ;Use common code to execute read
|
|
|
|
|
|
|
|
WRITE:
|
|
;
|
|
; Write a 128-byte sector from the current DMA address to
|
|
; the previously selected disk, track, and sector.
|
|
;
|
|
; On arrival here, the BDOS will have set register C to indicate
|
|
; whether this write operation is to an already allocated allocation
|
|
; block (which means a preread of the sector may be needed),
|
|
; to the directory (in which case the data will be written to the
|
|
; disk immediately), or to the first 128-byte sector of a previously
|
|
; unallocated allocation block (in which case no preread is required).
|
|
;
|
|
; Only writes to the directory take place immediately. In all other
|
|
; cases, the data will be moved from the DMA address into the disk
|
|
; buffer, and only written out when circumstances force the
|
|
; transfer. The number of physical disk operations can therefore
|
|
; be reduced considerably.
|
|
;
|
|
|
|
ld a,(DeblockingRequired) ;Check if deblocking is required
|
|
or a ;(flag set in SELDSK call)
|
|
jp z,WriteNoDeblock
|
|
|
|
xor a ;Indicate that a write operation
|
|
ld (ReadOperation),a ; is required (i.e. NOT a read)
|
|
ld a,c ;Save the BDOS write type
|
|
ld (WriteType),a
|
|
cp WriteUnallocated ;Check if the first write to an
|
|
; unallocated allocation block
|
|
jp nz,CheckUnallocatedBlock ;No, check if in the middle of
|
|
; writing to an unallocated block
|
|
;Yes, first write to unallocated
|
|
; allocation block -- initialize
|
|
; variables associated with
|
|
; unallocated writes.
|
|
ld a,AllocationBlockSize/128 ;Get number of 128-byte
|
|
; sectors and
|
|
ld (UnallocatedRecordCount),a ; set up a count.
|
|
;
|
|
ld hl,SelectedDkTrkSec ;Copy disk, track and sector
|
|
ld de,UnallocatedDkTrkSec ; into unallocated variables
|
|
call MoveDkTrkSec
|
|
|
|
;
|
|
; Check if this is not the first write to an unallocated
|
|
; allocation block -- if it is, the unallocated record count
|
|
; has just been set to the number of 128-byte sectors in the
|
|
; allocation block.
|
|
;
|
|
CheckUnallocatedBlock:
|
|
ld a,(UnallocatedRecordCount)
|
|
or a
|
|
jp z,RequestPreread ;No, this is a write to an
|
|
; allocated block
|
|
;Yes, this is a write to an
|
|
; unallocated block
|
|
dec a ;Count down on number of 128-byte sectors
|
|
; left unwritten to in allocation block
|
|
ld a,(UnallocatedRecordCount) ; and store back new value.
|
|
|
|
ld hl,SelectedDkTrkSec ;Check if the selected disk, track,
|
|
ld de,UnallocatedDkTrkSec ; and sector are the same as for
|
|
call CompareDkTrkSec ; those in the unallocated block.
|
|
jp nz,RequestPreread ;No, a preread is required
|
|
;Yes, no preread is needed.
|
|
;Now is a convenient time to
|
|
; update the current sector and see
|
|
; if the track also needs updating.
|
|
;
|
|
;By design, Compare$Dk$Trk$Sec
|
|
; returns with
|
|
; DE -> Unallocated$Sector
|
|
ex de,hl ; HL -> Unallocated$Sector
|
|
inc (hl) ;Update Unallocated$Sector
|
|
ld a,(hl) ;Check if sector now > maximum
|
|
cp CPMSecPerTrack ; on a track
|
|
jp c,NoTrackChange ;No (A < (HL) )
|
|
;Yes,
|
|
ld (hl),0 ;Reset sector to 0
|
|
ld hl,(UnallocatedTrack) ;Increase track by 1
|
|
inc hl
|
|
ld (UnallocatedTrack),hl
|
|
;
|
|
NoTrackChange:
|
|
;Indicate to later code that
|
|
; no preread is needed.
|
|
xor a
|
|
ld (MustPrereadSector),a ;Must$Preread$Sector=0
|
|
jp PerformReadWrite
|
|
|
|
RequestPreread:
|
|
xor a ;Indicate that this is not a write
|
|
ld (UnallocatedRecordCount),a ; into an unallocated block.
|
|
inc a
|
|
ld (MustPrereadSector),a ;Indicate that a preread of the
|
|
; physical sector is required.
|
|
;
|
|
;
|
|
PerformReadWrite: ;Common code to execute both reads and
|
|
; writes of 128-byte sectors.
|
|
xor a ;Assume that no disk errors will
|
|
ld (DiskErrorFlag),a ; occur
|
|
|
|
ld a,(SelectedSector) ;Convert selected 128-byte sector
|
|
rra ; into physical sector by dividing by 4
|
|
rra
|
|
and 3fh ;Remove any unwanted bits
|
|
ld (SelectedPhysicalSector),a
|
|
;
|
|
ld hl,DataInDiskBuffer ;Check if disk buffer already has
|
|
ld a,(hl) ; data in it.
|
|
ld (hl),1 ;(Unconditionally indicate that
|
|
; the buffer now has data in it)
|
|
or a ;Did it indeed have data in it?
|
|
jp z,ReadSectorIntoBuffer ;No, proceed to read a physical
|
|
; sector into the buffer.
|
|
;
|
|
;The buffer does have a physical sector
|
|
; in it.
|
|
; Note: The disk, track, and PHYSICAL
|
|
; sector in the buffer need to be
|
|
; checked, hence the use of the
|
|
; Compare$Dk$Trk subroutine.
|
|
;
|
|
ld de,InBufferDkTrkSec ;Check if sector in buffer is the
|
|
ld hl,SelectedDkTrkSec ; same as that selected earlier
|
|
call CompareDkTrk ;Compare ONLY disk and track
|
|
jp nz,SectorNotInBuffer ;No, it must be read in
|
|
|
|
ld a,(InBufferSector) ;Get physical sector in buffer
|
|
ld hl,SelectedPhysicalSector
|
|
cp (hl) ;Check if correct physical sector
|
|
jp z,SectorInBuffer ;Yes, it is already in memory
|
|
SectorNotInBuffer:
|
|
;No, it will have to be read in
|
|
; over current contents of buffer
|
|
ld a,(MustWriteBuffer) ;Check if buffer has data in that
|
|
or a ; must be written out first
|
|
call nz,WritePhysical ;Yes, write it out
|
|
;
|
|
ReadSectorIntoBuffer:
|
|
call SetInBufferDkTrkSec ;Set in buffer variables from
|
|
; selected disk, track, and sector
|
|
; to reflect which sector is in the
|
|
; buffer now.
|
|
ld a,(MustPrereadSector) ;In practice, the sector need only
|
|
or a ; be physically read in if a preread
|
|
; is required
|
|
call nz,ReadPhysical ;Yes, preread the sector
|
|
xor a ;Reset the flag to reflect buffer
|
|
ld (MustWriteBuffer),a ; contents.
|
|
;
|
|
SectorInBuffer: ;Selected sector on correct track and
|
|
; disk is already in the buffer.
|
|
;Convert the selected CP/M (128-byte)
|
|
; sector into a relative address down
|
|
; the buffer.
|
|
ld a,(SelectedSector) ;Get selected sector number
|
|
and SectorMask ;Mask off only the least significant bits
|
|
ld l,a ;Multiply by 128 by shifting 16-bit value
|
|
ld h,0 ; left 7 bits
|
|
add hl,hl ;* 2
|
|
add hl,hl ;* 4
|
|
add hl,hl ;* 8
|
|
add hl,hl ;* 16
|
|
add hl,hl ;* 32
|
|
add hl,hl ;* 64
|
|
add hl,hl ;* 128
|
|
;
|
|
ld de,DiskBuffer ;Get base address of disk buffer
|
|
add hl,de ;Add on sector number * 128
|
|
;HL -> 128-byte sector number start
|
|
; address in disk buffer
|
|
ex de,hl ;DE -> sector in disk buffer
|
|
ld hl,(DMAAddress) ;Get DMA address set in SETDMA call
|
|
ex de,hl ;Assume a read operation, so
|
|
; DE -> DMA address
|
|
; HL -> sector in disk buffer
|
|
ld c,128/8 ;Because of the faster method used
|
|
; to move data in and out of the
|
|
; disk buffer, (eight bytes moved per
|
|
; loop iteration) the count need only
|
|
; be 1/8th of normal.
|
|
;At this point --
|
|
; C = loop count
|
|
; DE -> DMA address
|
|
; HL -> sector in disk buffer
|
|
ld a,(ReadOperation) ;Determine whether data is to be moved
|
|
or a ; out of the buffer (read) or into the
|
|
jp nz,BufferMove ; buffer (write)
|
|
;Writing into buffer
|
|
;(A must be 0 get here)
|
|
inc a ;Set flag to force a write
|
|
ld (MustWriteBuffer),a ; of the disk buffer later on.
|
|
ex de,hl ;Make DE -> sector in disk buffer
|
|
; HL -> DMA address
|
|
;
|
|
;
|
|
BufferMove: ;The following move loop moves eight bytes
|
|
; at a time from (HL) to (DE), C contains
|
|
; the loop count.
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
ld a,(hl) ;Get byte from source
|
|
ld (de),a ;Put into destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
|
|
dec c ;Count down on loop counter
|
|
jp nz,BufferMove ;Repeat until CP/M sector moved
|
|
;
|
|
ld a,(WriteType) ;If write to directory, write out
|
|
cp WriteDirectory ; buffer immediately
|
|
ld a,(DiskErrorFlag) ;Get error flag in case delayed write or read
|
|
ret nz ;Return if delayed write or read
|
|
;
|
|
or a ;Check if any disk errors have occured
|
|
ret nz ;Yes, abandon attempt to write to directory
|
|
;
|
|
xor a ;Clear flag that indicates buffer must be
|
|
ld (MustWriteBuffer),a ; written out
|
|
call WritePhysical ;Write buffer out to physical sector
|
|
ld a,(DiskErrorFlag) ;Return error flag to caller
|
|
ret
|
|
;
|
|
;
|
|
SetInBufferDkTrkSec: ;Indicate selected disk, track, and
|
|
; sector now residing in buffer
|
|
ld a,(SelectedDisk)
|
|
ld (InBufferDisk),a
|
|
|
|
ld hl,(SelectedTrack)
|
|
ld (InBufferTrack),hl
|
|
|
|
ld a,(SelectedPhysicalSector)
|
|
ld (InBufferSector),a
|
|
|
|
ret
|
|
;
|
|
CompareDkTrk: ;Compares just the disk and track
|
|
; pointed to by DE and HL
|
|
ld c,3 ;Disk (1), track (2)
|
|
jp CompareDkTrkSecLoop ;Use common code
|
|
|
|
CompareDkTrkSec: ;Compares the disk, track, and sector
|
|
; variables pointed to by DE and HL
|
|
ld c,4 ;Disk (1), track (2), and sector (1)
|
|
CompareDkTrkSecLoop:
|
|
ld a,(de) ;Get comparitor
|
|
cp (hl) ;Compare with comparand
|
|
ret nz ;Abandon comparison if inequality found
|
|
inc de ;Update comparitor pointer
|
|
inc hl ;Update comparand pointer
|
|
dec c ;Count down on loop count
|
|
ret z ;Return (with zero flag set)
|
|
jp CompareDkTrkSecLoop
|
|
;
|
|
; Moves the disk, track and sector
|
|
; variables pointed at by HL to
|
|
; those pointed at by DE
|
|
;
|
|
MoveDkTrkSec: ld c,4 ;Disk (1), track (2), and sector (1)
|
|
MoveDkTrkSecLoop:
|
|
ld a,(hl) ;Get source byte
|
|
ld (de),a ;Store in destination
|
|
inc de ;Update pointers
|
|
inc hl
|
|
dec c ;Count down on byte count
|
|
ret z ;Return if all bytes moved
|
|
jp MoveDkTrkSecLoop
|
|
|
|
|
|
|
|
|
|
|
|
;
|
|
; Should not get here with the IDE!
|
|
;
|
|
|
|
ReadNoDeblock:
|
|
call msg
|
|
db "ERROR - ReadNoDeblock",CR,LF,0
|
|
ret
|
|
|
|
WriteNoDeblock:
|
|
call msg
|
|
db "ERROR - WriteNoDeblock",CR,LF,0
|
|
ret
|
|
|
|
|
|
|
|
;
|
|
; Here's the big high-level view of the logic here. It's
|
|
; rather involved since a lot of decisions needs to be made.
|
|
;
|
|
; First, the variables:
|
|
;
|
|
; DRIVE = This is the drive the user has selected. It may not
|
|
; be the same drive currently in use.
|
|
;
|
|
; TRACK = This is the track the user wants to use, 0-79. It
|
|
; may not be the current track we're on.
|
|
;
|
|
; SECTOR = The CP/M logic sector the user wants to use. 0 - 35.
|
|
; Again, this might not be the same as the current one.
|
|
;
|
|
; CURDRV = This is the currently selected drive OR NODRIVE if
|
|
; none selected yet. If NODRIVE that implies this is
|
|
; the first disk access.
|
|
;
|
|
; CURTRK = Track where the current drive has its head located.
|
|
; NOTRACK implies the location is unknown.
|
|
;
|
|
; CURSEC = Current physical sector with the side bit (bit 0) still
|
|
; included.
|
|
;
|
|
|
|
; So here's the drive select logic...
|
|
;
|
|
; if (DRIVE != CURDRV) - a new drive is being selected
|
|
; if (CURDRV != NODRIVE) - if a valid drive is already selected
|
|
; Save the current track into the drive data area.
|
|
; Load the new drive's data from the drive data area.
|
|
; if (CURTRK == NOTRACK) - first time for this disk
|
|
; Home disk
|
|
; set CURTRK to 0
|
|
; else
|
|
; save track to FDC
|
|
; else
|
|
; Home disk
|
|
; set CURTRK to 0
|
|
;
|
|
; ...followed by the track select logic...
|
|
;
|
|
; if (TRACK != CURTRK)
|
|
; seek to TRACK
|
|
; set CURTRK to TRACK
|
|
;
|
|
; ...and then compute physical sector and side...
|
|
;
|
|
; divide sector by 4 for temp sector
|
|
; if bit 0 is set then select side 0, else select side 1
|
|
; divide the temp sector by 2. This is now CURSEC.
|
|
; load CURSEC into the sector register
|
|
;
|
|
; Perform read or write operation
|
|
;
|
|
|
|
|
|
|
|
|
|
|
|
;
|
|
;********************************************************
|
|
; Initilze the 8255 and drive then do a hard reset on the drive,
|
|
;
|
|
IDEinit: ld a,READcfg8255 ;10010010b
|
|
OUT (IDEportCtrl),a ;Config 8255 chip, READ mode
|
|
|
|
ld a,IDErstline
|
|
OUT (IDEportC),a ;Hard reset the disk drive
|
|
|
|
ld B,020H ;<<<<< fine tune later
|
|
ResetDelay: DJNZ ResetDelay ;Delay (reset pulse width)
|
|
xor a
|
|
out (IDEportC),a ;No IDE control lines asserted
|
|
|
|
ld D,11100000b ;Data for IDE SDH reg (512bytes, LBA mode,single drive,head 0000)
|
|
;For Trk,Sec,head (non LBA) use 10100000
|
|
;Note. Cannot get LBA mode to work with an old Seagate Medalist 6531 drive.
|
|
;have to use teh non-LBA mode. (Common for old hard disks).
|
|
|
|
ld E,REGshd ;00001110,(0EH) for CS0,A2,A1,
|
|
CALL IDEwr8D ;Write byte to select the MASTER device
|
|
;
|
|
ld B,0FFH ;<<< May need to adjust delay time
|
|
WaitInit: ld E,REGstatus ;Get status after initilization
|
|
CALL IDErd8D ;Check Status (info in [D])
|
|
BIT 7,D
|
|
jp z,DoneInit ;Return if ready bit is zero
|
|
ld A,2
|
|
CALL DELAYX ;Long delay, drive has to get up to speed
|
|
DJNZ WaitInit
|
|
CALL SHOWerrors ;Ret with NZ flag set if error (probably no drive)
|
|
RET
|
|
DoneInit: xor A
|
|
RET
|
|
;
|
|
DELAYX: ld (DELAYStore),a
|
|
PUSH BC
|
|
ld bc,0FFFFH ;<<< May need to adjust delay time to allow cold drive to
|
|
DELAY2: LD a,(DELAYStore) ; get up to speed.
|
|
DELAY1: dec a
|
|
jp nz,DELAY1
|
|
dec bc
|
|
ld A,C
|
|
or b
|
|
jp nz,DELAY2
|
|
POP BC
|
|
RET
|
|
;
|
|
;********************************************************
|
|
; Read a sector, specified by the 4 bytes in LBA
|
|
; Z on success, NZ call error routine if problem
|
|
;
|
|
ReadPhysical: ld hl,DiskBuffer
|
|
ld a,042h
|
|
ld b,0
|
|
rpfill ld (hl),a
|
|
inc hl
|
|
ld (hl),a
|
|
inc hl
|
|
djnz rpfill
|
|
|
|
call msg
|
|
db "ReadPhysical",CR,LF,0
|
|
|
|
CALL wrlba ;Tell which sector we want to read from.
|
|
;Note: Translate first in case of an error otherewise we
|
|
;will get stuck on bad sector
|
|
CALL IDEwaitnotbusy ;make sure drive is ready
|
|
jp c,SHOWerrors ;Returned with NZ set if error
|
|
|
|
ld D,COMMANDread
|
|
ld E,REGcommand
|
|
CALL IDEwr8D ;Send sec read command to drive.
|
|
CALL IDEwaitdrq ;wait until it's got the data
|
|
jp c,SHOWerrors
|
|
;
|
|
ld hl,DiskBuffer ;DMA address
|
|
ld B,0 ;Read 512 bytes to [HL] (256X2 bytes)
|
|
MoreRD16: ld A,REGdata ;REG regsiter address
|
|
OUT (IDEportC),a
|
|
|
|
OR IDErdline ;08H+40H, Pulse RD line
|
|
OUT (IDEportC),a
|
|
|
|
IN a,(IDEportA) ;Read the lower byte first (Note early versions had high byte then low byte
|
|
ld (hl),a ;this made sector data incompatable with other controllers).
|
|
inc hl
|
|
IN a,(IDEportB) ;THEN read the upper byte
|
|
ld (hl),A
|
|
inc hl
|
|
|
|
ld A,REGdata ;Deassert RD line
|
|
OUT (IDEportC),a
|
|
DJNZ MoreRD16
|
|
|
|
ld E,REGstatus
|
|
CALL IDErd8D
|
|
ld A,D
|
|
BIT 0,A
|
|
call nz,SHOWerrors ;If error display status
|
|
RET
|
|
;
|
|
;********************************************************
|
|
;Write a sector, specified by the 3 bytes in LBA (@ IX+0)",
|
|
;Z on success, NZ to error routine if problem
|
|
;
|
|
WritePhysical:
|
|
; call msg
|
|
; db "WritePhysical",CR,LF,0
|
|
|
|
CALL wrlba ;Tell which sector we want to read from.
|
|
;Note: Translate first in case of an error otherewise we
|
|
;will get stuck on bad sector
|
|
CALL IDEwaitnotbusy ;make sure drive is ready
|
|
jp c,SHOWerrors
|
|
|
|
ld D,COMMANDwrite
|
|
ld E,REGcommand
|
|
CALL IDEwr8D ;tell drive to write a sector
|
|
CALL IDEwaitdrq ;wait unit it wants the data
|
|
jp c,SHOWerrors
|
|
;
|
|
ld hl,DiskBuffer
|
|
ld B,0 ;256X2 bytes
|
|
|
|
ld A,WRITEcfg8255
|
|
OUT (IDEportCtrl),a
|
|
WRSEC1: ld A,(hl)
|
|
inc hl
|
|
OUT (IDEportA),a ;Write the lower byte first (Note early versions had high byte then low byte
|
|
ld A,(hl) ;this made sector data incompatable with other controllers).
|
|
inc hl
|
|
OUT (IDEportB),a ;THEN High byte on B
|
|
ld A,REGdata
|
|
PUSH af
|
|
OUT (IDEportC),a ;Send write command
|
|
OR IDEwrline ;Send WR pulse
|
|
OUT (IDEportC),a
|
|
POP af
|
|
OUT (IDEportC),a
|
|
DJNZ WRSEC1
|
|
|
|
ld A,READcfg8255 ;Set 8255 back to read mode
|
|
OUT (IDEportCtrl),a
|
|
|
|
ld E,REGstatus
|
|
CALL IDErd8D
|
|
ld A,D
|
|
BIT 0,A
|
|
call nz,SHOWerrors ;If error display status
|
|
RET
|
|
;
|
|
;********************************************************
|
|
; Write the logical block address to the drive's registers
|
|
; Note we do not need to set the upper nibble of the LBA
|
|
; It will always be 0 for these small drives. Use the
|
|
; physical sector number which has already been divided
|
|
; down from the logical sector number.
|
|
;
|
|
wrlba: ld hl,(SelectedPhysicalSector)
|
|
ld a,l
|
|
inc a ;Sectors are numbered 1 to MAXSEC
|
|
ld (DRIVESEC),a ;For Diagnostic Display Only
|
|
ld D,A
|
|
ld E,REGsector ;Send info to drive
|
|
call IDEwr8D
|
|
;Note: For drive we will have 0 - MAXSEC sectors only
|
|
; ld hl,TRK
|
|
ld hl,(SelectedTrack)
|
|
ld A,L
|
|
ld (DRIVETRK),a
|
|
ld D,L ;Send Low TRK#
|
|
ld E,REGcylinderLSB
|
|
call IDEwr8D
|
|
|
|
ld A,H
|
|
ld (DRIVETRK+1),a
|
|
ld D,H ;Send High TRK#
|
|
ld E,REGcylinderMSB
|
|
call IDEwr8D
|
|
|
|
ld D,1 ;For now, one sector at a time
|
|
ld E,REGseccnt
|
|
call IDEwr8D
|
|
|
|
;DEBUG
|
|
call msg
|
|
.text "wrlba - Track "
|
|
.db 0
|
|
ld a,(DRIVETRK+1)
|
|
call a_hex
|
|
ld a,(DRIVETRK)
|
|
call a_hex
|
|
call msg
|
|
.db ", Sector ",0
|
|
ld a,(DRIVESEC)
|
|
call a_hex
|
|
call msg
|
|
.db CR,LF,0
|
|
;END DEBUG
|
|
RET
|
|
;
|
|
;********************************************************
|
|
;ie Drive READY if 01000000
|
|
;
|
|
IDEwaitnotbusy: ld B,0FFH
|
|
ld A,0FFH ;Delay, must be above 80H for 4MHz Z80. Leave longer for slower drives
|
|
ld (DELAYStore),a
|
|
|
|
MoreWait: ld E,REGstatus ;wait for RDY bit to be set
|
|
CALL IDErd8D
|
|
ld A,D
|
|
and 11000000B
|
|
xor 01000000B
|
|
jp z,DoneNotBusy
|
|
DJNZ MoreWait
|
|
ld a,(DELAYStore) ;Check timeout delay
|
|
dec A
|
|
ld (DELAYStore),a
|
|
jp nz,MoreWait
|
|
scf ;Set carry to indicqate an error
|
|
ret
|
|
DoneNotBusy: or A ;Clear carry it indicate no error
|
|
RET
|
|
;
|
|
;Wait for the drive to be ready to transfer data.
|
|
;Returns the drive's status in Acc
|
|
;
|
|
IDEwaitdrq: ld B,0FFH
|
|
ld A,0FFH ;Delay, must be above 80H for 4MHz Z80. Leave longer for slower drives
|
|
ld (DELAYStore),a
|
|
|
|
MoreDRQ: ld E,REGstatus ;wait for DRQ bit to be set
|
|
CALL IDErd8D
|
|
ld A,D
|
|
and 10001000B
|
|
cp 00001000B
|
|
jp z,DoneDRQ
|
|
DJNZ MoreDRQ
|
|
ld a,(DELAYStore) ;Check timeout delay
|
|
dec A
|
|
ld (DELAYStore),a
|
|
jp nz,MoreDRQ
|
|
scf ;Set carry to indicate error
|
|
RET
|
|
DoneDRQ: OR A ;Clear carry
|
|
RET
|
|
;
|
|
;********************************************************
|
|
; Low Level 8 bit R/W to the drive controller. These are
|
|
; the routines that talk directly to the drive controller
|
|
; registers, via the 8255 chip. Note the 16 bit I/O to
|
|
; the drive (which is only for SEC R/W) is done directly
|
|
; in the read/write functions for speed reasons.
|
|
;
|
|
; READ 8 bits from IDE register in [E], return info in [D]
|
|
;
|
|
IDErd8D: ld A,E
|
|
OUT (IDEportC),a ;drive address onto control lines
|
|
|
|
OR IDErdline ;RD pulse pin (40H)
|
|
OUT (IDEportC),a ;assert read pin
|
|
|
|
IN a,(IDEportA)
|
|
ld D,A ;return with data in [D]
|
|
|
|
xor A
|
|
OUT (IDEportC),a ;Zero all port C lines
|
|
ret
|
|
;
|
|
; WRITE Data in [D] to IDE register in [E]
|
|
;
|
|
IDEwr8D: ld A,WRITEcfg8255 ;Set 8255 to write mode
|
|
OUT (IDEportCtrl),a
|
|
|
|
ld A,D ;Get data put it in 8255 A port
|
|
OUT (IDEportA),a
|
|
|
|
ld A,E ;select IDE register
|
|
OUT (IDEportC),a
|
|
|
|
OR IDEwrline ;lower WR line
|
|
OUT (IDEportC),a
|
|
NOP
|
|
|
|
xor A ;Deselect all lines including WR line
|
|
OUT (IDEportC),a
|
|
|
|
ld A,READcfg8255 ;Config 8255 chip, read mode on return
|
|
OUT (IDEportCtrl),a
|
|
RET
|
|
|
|
|
|
SHOWerrors: call msg
|
|
.TEXT "SHOWerrors called"
|
|
.db CR,LF,0
|
|
|
|
.page
|
|
;********************************************************
|
|
; Input/Output devices. For now, just call the TTY
|
|
; functions, but later I'll add iobyte support.
|
|
;
|
|
CONST: jp ttystat
|
|
CONIN: jp ttyin
|
|
CONOUT: jp ttyout
|
|
;
|
|
;********************************************************
|
|
; Message output subroutines. msghl will print the null-
|
|
; terminated message pointed to by HL. msg will
|
|
; print the null terminated string immediately following
|
|
; the "call MSG" line.
|
|
;
|
|
msg: ex (sp),hl
|
|
call msghl
|
|
ex (sp),hl
|
|
ret
|
|
;
|
|
msghl: ld a,(hl)
|
|
or a
|
|
ret z
|
|
ld c,a
|
|
call CONOUT
|
|
inc hl
|
|
jr msghl
|
|
;
|
|
;********************************************************
|
|
; Display the contents of A as two hex digits
|
|
;
|
|
a_hex: push af
|
|
rrca
|
|
rrca
|
|
rrca
|
|
rrca
|
|
call ahex2
|
|
pop af
|
|
ahex2: and 0fh
|
|
add a,90h
|
|
daa
|
|
adc a,40h
|
|
daa
|
|
ld c,a
|
|
jr ttyout
|
|
;
|
|
;********************************************************
|
|
; Do a CR/LF
|
|
;
|
|
crlf: ld c,CR
|
|
call ttyout
|
|
ld c,LF
|
|
jr ttyout
|
|
.page
|
|
;
|
|
;********************************************************
|
|
; Other I/O devices. Make them all do nothing for now.
|
|
;
|
|
LIST: jp ttyout
|
|
PUNCH: ret
|
|
READER: ld a,EOF
|
|
ret
|
|
LISTST: ld a,TRUE
|
|
ret
|
|
|
|
#include "ccs2710.inc"
|
|
|
|
|
|
|
|
; stuff from bios.mac
|
|
|
|
InBufferDkTrkSec: ;Variables for physical sector
|
|
; currently in Disk$Buffer in memory
|
|
InBufferDisk: db 0 ; These are moved and compared
|
|
InBufferTrack: dw 0 ; as a group, so do not alter
|
|
InBufferSector: db 0 ; these lines.
|
|
;
|
|
DataInDiskBuffer: db 0 ;When nonzero, the disk buffer has
|
|
; data from the disk in it.
|
|
MustWriteBuffer: db 0 ;Nonzero when data has been
|
|
; written into Disk$Buffer but
|
|
; not yet written out to disk
|
|
|
|
DMAAddress: dw 0
|
|
|
|
WriteType db 0
|
|
|
|
;These are the values handed over by the BDOS
|
|
; when it calls the WRITE operation.
|
|
;The allocated/unallocated indicates whether the
|
|
; BDOS is set to write to an unallocated allocation
|
|
; block (it only indicates this for the first
|
|
; 128-byte sector write) or to an allocation block
|
|
; that has already been allocated to a file.
|
|
;The BDOS also indicates if it is set to write to
|
|
; the file directory.
|
|
;
|
|
WriteAllocated equ 0
|
|
WriteDirectory equ 1
|
|
WriteUnallocated equ 2
|
|
|
|
;
|
|
;Variables for selected disk, track, and sector
|
|
; (Selected by SELDSK, SETTRK,n and SETSEC)
|
|
;
|
|
; Note that these are in a specific order, so do not change
|
|
; the size nor order without checking where other pieces of
|
|
; code are affected!
|
|
;
|
|
SelectedDkTrkSec:
|
|
SelectedDisk: db 0 ; These are moved and
|
|
SelectedTrack: dw 0 ; compared as a group so
|
|
SelectedSector: db 0 ; do not alter order.
|
|
SelectedPhysicalSector: db 0 ;Selected physical sector derived
|
|
; from selected (CP/M) sector by
|
|
; shifting it right the number of
|
|
; bits specified by
|
|
; Sector$Bit$Shift
|
|
;
|
|
SelectedDiskType: db 0 ;Set by SELDSK to indicate either
|
|
; 8" or 5 1/4" floppy
|
|
SelectedDiskDeblock: db 0 ;Set by SELDSK to indicate whether
|
|
; deblocking is required.
|
|
|
|
UnallocatedDkTrkSec: ;Parameters for writing to a previously
|
|
; unallocated allocation block.
|
|
UnallocatedDisk: db 0 ; These are moved and compared
|
|
UnallocatedTrack: dw 0 ; as a group so do not alter
|
|
UnallocatedSector: db 0 ; these lines.
|
|
|
|
UnallocatedRecordCount: db 0 ;Number of unallocated "records"
|
|
; in current previously unallocated
|
|
; allocation block.
|
|
|
|
DiskErrorFlag: db 0 ;Nonzero to indicate an error
|
|
; that could not be recovered
|
|
; by the disk drivers. BDOS will
|
|
; output a "bad sector" message.
|
|
|
|
;
|
|
;Flags used inside the deblocking code
|
|
|
|
MustPrereadSector: db 0 ;Nonzero if a physical sector must
|
|
; be read into the disk buffer
|
|
; either before a write to an
|
|
; allocated block can occur, or
|
|
; for a normal CP/M 128-byte
|
|
; sector read
|
|
ReadOperation: db 0 ;Nonzero when a CP/M 128-byte
|
|
; sector is to be read
|
|
DeblockingRequired: db 0 ;Nonzero when the selected disk
|
|
; needs deblocking (set in SELDSK)
|
|
DiskType: db 0 ;Indicates 8" or 5 1/4" floppy
|
|
; selected (set in SELDSK).
|
|
|
|
|
|
;
|
|
; This is the actual sector size
|
|
; for the 5 1/4" mini-floppy diskettes.
|
|
; The 8" diskettes use 128-byte sectors.
|
|
; Declare the physical disk buffer for the
|
|
; 5 1/4" diskettes
|
|
;
|
|
; This is the low level buffer used to hold a sector of data.
|
|
;
|
|
PhysicalSectorSize equ 512
|
|
DiskBuffer: ds PhysicalSectorSize
|
|
;
|
|
; Data written to, or read from, the mini-floppy drive is transferred
|
|
; via a physical buffer that is actually 512 bytes long (it was
|
|
; declared at the front of the BIOS and holds the "one-time"
|
|
; initialization code used for the cold boot procedure).
|
|
;
|
|
; The blocking/deblocking code attempts to minimize the amount
|
|
; of actual disk I/O by storing the disk, track, and physical sector
|
|
; currently residing in the Physical Buffer. If a read request is for
|
|
; a 128-byte CP/M "sector" that already is in the physical buffer,
|
|
; then no disk access occurs.
|
|
;
|
|
;
|
|
AllocationBlockSize equ 2048
|
|
PhysicalSecPerTrack equ 18
|
|
CPMSecPerPhysical equ PhysicalSectorSize/128
|
|
CPMSecPerTrack equ CPMSecPerPhysical*PhysicalSecPerTrack
|
|
SectorMask equ CPMSecPerPhysical-1
|
|
SectorBitShift equ 2 ;LOG2(CPM$Sec$Per$Physical)
|
|
|
|
|
|
|
|
; stuff from MYIDE
|
|
|
|
DELAYStore: DS 1
|
|
DRIVESEC: db 0
|
|
DRIVETRK: dw 0
|
|
|
|
;
|
|
; This MUST BE the last label!
|
|
;
|
|
BIOS_END .equ *
|
|
|
|
.end
|
|
|