; ; ; ; S E L E C T D I S K D R I V E ; ; Select the disk drive for subsequent disk transfers and ; return the appropriate DPB address. This routine ; diverges from the normal CP/M implementation of just ; saving the disk selection value until the transfer is ; performed. This divergence is required because floppy ; disks are a removable media and come in more than one ; format. This routine determines the correct format and ; initializes the DPH with the appropriate values for the ; format type. ; ; ENTRY C = disk delection value. ; DE and 1 = 0, must determine disk type. ; = 1, drive type has been determined. ; ; EXIT HL = 0, if drive not selectable. ; HL = DPH address if drive is selectable. ; ; DPH is intialized for the appropriate floppy ; disk format. SELDSK: MOV A,C CPI NDSK JNC SELD2 ;If invalid drive PUSH D ;Save drive selection mask MVI B,0 PUSH B ;Save drive number CALL HOME ;Flush buffers POP B ;Restore disk selection MOV A,C STA SEKDSK ;Save disk selection code LXI H,DTYPE DAD B DAD B MOV A,M PUSH H ;Save pointer to Class 6, op code 0 MOV L,C ;Compute DPH address MOV H,B DAD H ;*2 DAD H ;*4 DAD H ;*8 DAD H ;*16 LXI D,DPH DAD D ;HL = DPH address STA SEKTYP ;Save disk type XTHL ;Get pointer to Class 6, op code 0 ; and save DPH address. ANI TYPEFPY+TYPEMIN ;Floppy? JZ SELD1 SELD0: INX H ;Yes. Get Class 6, op code 0 type MOV A,M STA CIOFS+5 LXI H,DSKMSK ;Get LUN DAD B MOV A,M STA CIOFS+1 STA CIOPB+1 ;Set for error handling LXI H,CIOFS ;Set track format code CALL EXEC CZ WAITF CALL SFINAL ;Check for errors JZ SELD1 POP H ;Error. Restore stack POP D JMP SELD2 SELD1: POP H ;Restore DPH address POP D ;Restore Drive selction mask RET SELD2: LXI H,0 LDA CDISK SUB C RNZ ;If default drive not in error STA CDISK RET CIOFS: DB FSCMD,0,0,0,0,0 ; ; H O M E ; ; Return disk to home. This routine sets the track number ; to zero. The current host disk buffer is flushed to the ; disk. HOME: CALL FLUSH ;Flush host buffer XRA A STA HSTACT ;Clear host active flag STA UNACNT ;Clear sector count STA SEKTRK STA SEKTRK+1 RET ; ; ; ; ; S E T T R A C K. ; ; Set track number. The track number is saved for later ; use during a disk transfer operation. ; ; ENTRY BC = track number. SETTRK: MOV L,C MOV H,B SHLD SEKTRK LHLD UNATRK MOV A,L XRA C MOV C,A MOV A,H XRA B ORA A RZ ;If same track ; JMP CUNACT ; ; ; ; ; ; ; ; ; Clear Unallocated block count (force pre-reads). CUNACT: XRA A ;A = 0 STA UNACNT ;Clear unallocated block count RET ; ; ; ; ; Set the sector for later use in the disk transfer. No ; actual disk operations are perfomed. ; ; Entry BC = sector number. SETSEC: MOV A,C STA SAVSEC ;sector to seek RET ; ; ; ; ; Set Disk memory address for subsequent disk read or ; write routines. This address is saved in DMAADR until ; the disk transfer is performed. ; ; ENTRY BC = Disk memory address. ; ; EXIT DMAADR = BC. SETDMA: MOV H,B MOV L,C SHLD DMAADR RET ; ; ; ; ; Translate sector number from logical to physical. ; ; ENTRY DE = 0, no translation required. ; DE = translation table address. ; BC = sector number to translate. ; ; EXIT HL = translated sector. SECTRN: LDA UNASEC CMP C CNZ CUNACT ;If sectors do not match MOV A,C STA LOGSEC MOV L,C MOV H,B MOV A,D ORA E RZ ;If no translation DAD D MOV L,M MVI H,0 RET ; B o o t C P / M f r o m d i s k. ; ; The WBOOT entry point gets control when a warm start ; occurs, a ^C from the console, a jump to BDOS (function ; 0), or a jump to location zero. The WBOOT routine reads ; the CCP and BDOS from the apprpriate Hard disk sectors. ; WBOOT must also re-initialize locations 0,1,2 and 5,6,7. ; The WBOOT routines exits with the C register set to the ; appropriate drive selection value. The exit address ; is to the CCP routine. WBOOT: LXI SP,DBUF LDA CIOBFS+5 ;Floppy? CPI 0FFh JZ WBOOT1 LXI H,CIOBFS ;Yes. Select track format CALL EXEC CZ WAITF CALL SFINAL JNZ WBOOT WBOOT1: LXI H,CIOBT LXI D,CCP CALL RDISK ;Read data into memory MOV A,C ANI FERR JNZ WBOOT ;If errors retry GOCPM: LXI H,DBUF SHLD DMAADR ;Set default address MVI A,0C3h ;Store jumps in low memory STA 0 STA 5 LXI H,BIOS+3 SHLD 1 LXI H,BDOS SHLD 6 LDA CDISK MOV C,A JMP CCP ;Go to CPM CIOBFS: DB FSCMD,0,0,0,0,0 CIOBT: DB RDCMD ;Command = read BTLUN: DB 0 ;Physical drive = hard disk DB 0 BTSLA0: DB 0 BTNSEC: DB 0 ;# CCP and BDOS sector DB 0 ; Read a CP/M 128 byte sector. ; ; EXIT A = 0, successful read operation. ; A = 1, unsucessful read operation. READ: CALL CHKBKD ;Check for blocked drive JC FREAD ;If non-blocked transfer XRA A ;Set flag to force a read STA UNACNT ;Clear sector counter CALL FILL ;Fill buffer with data POP H POP D MVI C,128 CALL MOVDTA ;Move 128 bytes LDA ERFLAG ORA A RZ XRA A STA HSTACT ORI 001h RET ; ; ; ; ; Write the selected 128 byte CP/M sector. ; ; ENTRY C = 0, write to a previously allocated block. ; C = 1, write to the directory. ; C = 2, write to the first sector of unallocated ; data block. ; ; EXIT A = 0, write was successful. ; A = 1, write was unsucessful. WRITE: CALL CHKBKD ;Check for blocked drive JC FWRITE ;If non-blocked transfer MOV A,C ;Move write type STA WRTYPE CPI WRUAL JNZ WRIT1 ;If not write to unallocated MVI A,HSTSIB-1 ;Set new unallocated parameters STA UNACNT LHLD SEKTRK SHLD UNATRK ;UNATRK = SEKTRK LDA LOGSEC INR A JMP WRIT2 WRIT1: LDA UNACNT ORA A JZ WRIT3 ;If no sectors left in block DCR A STA UNACNT LDA UNASEC ;Increment unallocated sectors INR A CPI CPMSPT JNZ WRIT3 ;If not new track LHLD UNATRK INX H SHLD UNATRK ;UNATRK = UNATRK+1 XRA A ;A = 0 WRIT2: STA UNASEC MVI A,0FFh WRIT3: CALL FILL POP D POP H MVI C,128 CALL MOVDTA ;Move 128 bytes MVI A,1 STA HSTWRT ;HSTWRT = 1 LDA ERFLAG ORA A RNZ ;If any errors occurred LDA WRTYPE ;write type CPI WRDIR ;to directory? CZ FLUSH ;Force write of directory LDA ERFLAG ORA A RET ; ; ; ; ; FILL - fill host buffer with approprite host sector. ; ; ENTRY A = 0, Read required if not in buffer. ; 0therwise read not required. ; ; EXIT On exit the stack will contain the following ; values: ; POP x ;x = host record address. ; POP y ;y = caller's buffer address. FILL: STA RDFLAG ;Save read flag LHLD DMAADR XTHL ;Set caller's buffer address PUSH H LDA ACTTYP ;Get physical sector size ANI TYPESEC RAR MOV B,A MOV C,A XRA A ;Generate sector mask FILL0: STC DCR B JM FILL1 RAL JMP FILL0 FILL1: MOV B,A LDA SEKSEC ;Compute relative record number ANA B LXI H,HSTBUF ;Compute host record address LXI D,128 FILL2: DCR A JM FILL3 DAD D JMP FILL2 FILL3: XTHL ;Put buffer address of record on stack PUSH H LDA SEKSEC ;Convert to physical sector numver FILL4: DCR C JM FILL5 ANA A ;Carry = 0 RAR JMP FILL4 FILL5: STA SEKSEC LXI H,HSTACT ;host active flag MOV A,M MVI M,1 ;always becomes 1 ORA A JZ FILL6 ;If host buffer inactive LXI H,HSTDSK CALL CMPSEK ;Compare HST with SEK RZ ;If everything same CALL FLUSH ;Flush host buffer FILL6: LHLD SEKDSK ;Move disk and type SHLD HSTDSK SHLD ACTDSK LHLD SEKTRK SHLD HSTTRK SHLD ACTTRK LDA SEKSEC STA HSTSEC STA ACTSEC LDA RDFLAG ORA A RNZ ;If no read required FREAD: MVI A,RDCMD ;Set read command LXI H,RDISK ;Set transfer routine address JMP FINAL ; ; ; ; ; FLUSH - Write out active host buffer onto disk. FLUSH: LXI H,HSTWRT MOV A,M ORA A RZ ;If host buffer already on disk MVI M,0 LHLD HSTDSK ;Move disk and type SHLD ACTDSK LHLD HSTTRK SHLD ACTTRK LDA HSTSEC STA ACTSEC FWRITE: MVI A,WTCMD ;Set write command LXI H,WDISK ;Set transfer routine address ; JMP FINAL ; ; ; ; ; F I N A L -- Preform final tranfer processing. ; ; ENTRY A = Command. ; HL = transfer routine address. FINAL: CALL SETUP SFINAL: ;Called from SELDSK IF I696 LXI H,NMSG2 ;Set message address ORA A JNZ FNL3 ;If message byte is non-zero LXI H,NMSG ;Set message address MOV A,B ANI MSSG JZ FNL3 ;If message bit zero ENDIF MOV A,C ANI FERR ;mask for errors STA ERFLAG RZ ;If no errors MOV A,C ANI CERR+TERR LXI H,CERMSG PUSH B CNZ PRINT ;If controller error POP B LXI H,TOMSG ;Check for timeout MOV A,C ANI TERR JNZ FNL3 FNL1: MOV A,C ANI PERR JZ FNL2 ;If no parity errors LXI H,PERMSG JMP FNL3 FNL2: LDA CIOPB+1 ANI 0E0H STA CIOER+1 LXI H,CIOER LXI D,TEMPBF CALL RDISK LXI H,TYPMSG ;Issue type message CALL PRINT LDA TEMPBF ;Get type RRC RRC RRC RRC ANI 3 CALL OHN ;Output hex nibble LXI H,CODMSG CALL PRINT ;Issue code message LDA TEMPBF ;Get code ANI 0Fh CALL OHN ;Output hex nibble LXI H,ENDMSG FNL3: CALL PRINT ORI 1 STA ERFLAG RET CIOER: DB ESCMD,0,0,0,0,0 ;Request Error code CERMSG: DB CR,LF,'Controller error',0 TYPMSG: DB ' Type <',0 CODMSG: DB '>, Code <',0 ENDMSG: DB '>',CR,LF,0 TOMSG: DB ' Timeout',0 IF I696 NMSG: DB CR,LF,'No message bit',0 NMSG2: DB CR,LF,'Message byte non-zero',0 ENDIF PERMSG: DB CR,LF,'Parity error.',0 ; ; ; ; ; S E T U P - Setup the CIOPB area. ; ; ENTRY A = Command. ; HL = transfer routine address. SETUP: PUSH H ;Set next phase address STA CIOPB+0 ;Set command LXI D,0 LDA ACTDSK ;Get LUN MOV E,A LXI H,DSKMSK ;Get LUN DAD D MOV C,M LXI H,DSKOFF DAD D DAD D DAD D MOV A,M INX H MOV D,M INX H MOV E,M ORA C ;LUN in bits 5-7 MOV C,A SETP1: PUSH B ;Save unit selection LHLD ACTTRK ;Get track number MOV B,H ;BC = 1*TRK MOV C,L DAD H ;HL = 2*TRK DAD B ;HL = (2+1)*TRK = 3*TRK DAD H ;HL = 6*TRK DAD H ;HL = 12*TRK DAD B ;HL = (12+1)*TRK = 13*TRK DAD H ;Hl = 26*TRK LDA ACTSEC MVI B,0 MOV C,A DAD B ;HL = 26*TRK+SEC POP B ;Restore BC XRA A ;A = 0 DAD D ADC C XCHG LXI H,CIOPB+1 MOV M,A INX H MOV M,D INX H MOV M,E INX H MVI M,1 ;Read one sector INX H MVI M,00h ;Force ECC correction LHLD BUFADR XCHG LXI H,CIOPB RET ;Dispatch to routine ; Disk I/O Routines ; ; IF I696 ; E X E C EXEC: MVI B,BUSY ;Wait for not busy. MVI C,BUSY and (not BUSY) CALL WAITM RNZ MVI A,SLCT ;Alert controller OUT DIO+1 EXEC1: MOV C,B ;Wait for controller busy CALL WAITM RNZ MVI A,DODTA ;Enable data in OUT DIO+1 EXEC2: IN DIO+2 ;Get status XRI 0FFh JM EXEC2 ;If not requesting next byte ANI CMND+DIROUT JNZ EXEC3 ;If CMND or DIROUT false MOV A,M INX H OUT DIO ;Send byte from command buffer JMP EXEC2 EXEC3: CMP A ;Z:=1 RET ; ; ; ; ; WDISK - Output from memory buffer. ; ENTRY: HL = COMMAND BUFFER ADDRESS ; DE = DATA BUFFER ADDRESS ; WDISK: CALL EXEC ;Output command RNZ ;Return if timeout WDISK1: IN DIO+2 ;Read status ORA A JP WDISK1 ;If request is present ANI CMND JNZ GCMPS ;If done with transfer LDAX D ;Get the data byte OUT DIO INX D ;Advance buffer address JMP WDISK1 ; ; ; ; ; RDISK - Input to memory buffer. ; ; Entry: HL = command buffer address ; DE = data buffer address RDISK: CALL EXEC RNZ ;Return if timeout RDISK1: IN DIO+2 ;Read status ORA A JP RDISK1 ;If request is present ANI CMND JNZ GCMPS IN DIO STAX D INX D JMP RDISK1 ; ; ; ; ; WAITF - Wait for function to complete. WAITF: MVI B,REQ+CMND ;Wait for both REQ and CMND MOV C,B CALL WAITM RNZ ; ; Get completion status. GCMPS: IN DIO ;Get completion status MOV C,A GCMP1: IN DIO+2 ORA A JP GCMP1 ;If REQ not set MOV B,A IN DIO ;Get message byte RET ENDIF ; ; ; ; IF I796 ; EXEC - Output the command ; ; Enter: HL is the command buffer address ; DE - data transfer address. EXEC: MOV A,E ;Output DMA address OUT DIO+2 MOV A,D OUT DIO+3 MOV A,L OUT DIO+4 MOV A,H OUT DIO+5 MVI A,0 OUT DIO+6 OUT DIO+7 OUT DIO CMP A ;Z:=1 RET ; Disk read/write ; ; Entry: same as EXEC ; RDISK: WDISK: CALL EXEC RNZ ;Return if timeout ; WAITF - Wait until transfer done ; ; Enter: none ; Exit: when transfer completed WAITF: MVI B,CMDDON ;Wait for CMDDON MOV C,B CALL WAITM RNZ ;Return if timeout ; ; GCMPS - Get completion status ; ; Enter: none ; Exit: Status in C GCMPS: IN DIO+1 MOV C,A RET ENDIF ; WAITM - Wait for controller with timeout ; ; Entry: B=Status mask ; C=Status value ; Exit: Z=1 if OK, else timeout with A=C=TERR ; WAITM: PUSH D ;Save D PUSH H LXI H,138 ;Two minute timeout LXI D,0 ;Max wait @4MHZ is 868 ms WAITML: IF I696 IN DIO+2 ENDIF IF I796 IN DIO ENDIF ANA B ;Mask wait bits CMP C ;Check value JZ WAITM1 DCX D ;Not ready. Decrement time MOV A,D ORA E JNZ WAITML DCX H MOV A,H ORA L JNZ WAITML MVI B,0 ;Timeout MVI A,TERR ORA A WAITM1: POP H POP D ;Restore D MOV C,A ;Return status in C RET ; MOVDTA - Move data in memory. ; ; ENTRY C = number of bytes to move ; DE = destination address. ; HL = source address. MOVDTA: MOV A,M ;source character STAX D ;to dest INX H INX D DCR C ;loop 128 times JNZ MOVDTA ;If transfer not complete RET ; ; ; ; ; Check blocked disk transfer. ; ; EXIT Cbit set, unblocked device. ; Cbit clear, blocked device. CHKBKD: XRA A STA ERFLAG ;Clear error flag LDA SEKTYP MOV H,A ANI TYPESEC MOV A,H JZ CBKD2 ;If not blocked device ANI TYPEFPY+TYPEMIN MOV A,H JZ CBKD1 ;If hard disk CBKD0: PUSH H LHLD SEKTRK MOV A,H ORA L POP H MOV A,H JNZ CBKD1 ANI NOT TYPESEC AND 0FFh ;Non-blocked JMP CBKD2 CBKD1: STA ACTTYP LXI H,HSTBUF SHLD BUFADR MVI A,BXADR ;BIOS extended address STA BUFADE LDA SAVSEC STA SEKSEC XRA A ;Clear carry flag RET SETACT: LDA SEKTYP CBKD2: STA ACTTYP ;Set actual disk type LHLD DMAADR SHLD BUFADR LDA DMAADE STA BUFADE LDA SEKDSK STA ACTDSK LHLD SEKTRK SHLD ACTTRK LDA SAVSEC STA ACTSEC STC ;Set carry flag RET ; ; ; ; ; Utility subroutine for 16-bit compare CMPSEK: LXI D,SEKDSK MVI C,SEKSEC-SEKDSK+1 CMPS1: LDAX D ;low byte compare CMP M RNZ ;If not the same INX D INX H DCR C JNZ CMPS1 ;If not all checked RET ; ; ; ; ; Output hex nibble. OHN: ADI 90h DAA ACI 40h DAA MOV C,A JMP CONOUT ; ; ; ; ; Print message terminated by zero byte. ; ; ENTRY HL -> message buffer, terminated by zero. ; ; EXIT HL -> zero byte. ; A = 0. ; Z bit set. ; ; Destroys only HL, Flags, and A registers. PRINT: MOV A,M ;Get a character ORA A RZ ;If zero the terminate INX H PUSH B MOV C,A CALL CONOUT ;Output to the console POP B JMP PRINT ; Physical data buffer address ((DMAADR) or HSTBUF) BUFADR: DW 0 ;Lower 16 bits (least, middle) BUFADE: DB 0 ;Extended address ; User data buffer address DMAADR: DW 0 ;Lower 16 bits (least, middle) DMAADE: DB 0 ;Extended address ; ; ; ; ; BIOS blocking / deblocking flags. HSTACT: DB 0 ;host active flag HSTWRT: DB 0 ;host written flag UNACNT: DB 0 ;unalloc rec CNT UNATRK: DW 0 ;Track UNASEC: DB CPMSPT+1 ;Sector LOGSEC DB 0 ;Logical sector