;******************************************************************************************** ; CP/M 3.0 LOADER BIOS FOR THE Godbout Z80 board with Imsai MIO board. ; The Imsai MIO board is used for system serial console port and one of the ; parallel ports is used to talk with an ESP32 based adapter board that provides ; an *MB hardisk image. ; ; custom file used to build CPMLDR the mid level system boot loader for booting ; from SD cards ; ; ; WRITTEN BY: Lawrence Glaister VE7IT 12-Feb-2021 ; Note: since we are using rmac, we must use 8080 type opcode format, not z80 syntax ; ; ; 07/20/2020 v1.0 - for non banked cpm, SD card, serial/usb console port ; - Fixed issue with read from sd routine... high byte,low byte ; is the required order in the spi read cmd for sector number. ; 02/12/2021 V1.1 - removed SD card code and provided parallel port access to ; SD card on a ESP32. ; 02/17/2021 V1.2 - added support for 4 HD images (always boots off 0) ; 02/27/2021 V1.3 - scaled back to 2 HD to save ram space, added cksums to parallel comms ; ;******************************************************************************************** TRUE EQU -1 FALSE EQU NOT TRUE ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< BANKED EQU FALSE ;<--- WE WILL BE USING A NON-BANKED CPM3 ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< BELL EQU 07H CR EQU 0DH LF EQU 0AH ; serial port on Imsai MIO card (jumper dependant) SER1STATUSPORT EQU 3 ; Status port for serial port SER1DAV EQU 00000010B ; data available flag SER1CTS EQU 00000001B ; clear to send flag SER1DATAPORT EQU 2 ; defines for IMSAI MIO board ( parallel port on J2 or J3) PIO equ 01h ; in/out for both ports CNT equ 03h ; location of command/status reg (shared with serial) PIOM equ 80h ; PP select mux bit 0 for J2, 1 for J3 I1DA equ 40h ; input 1 data available O1DR equ 80h ; output 1 data ready SEC$SIZE EQU 512 ;Assume sector size as 512. CPM$BOOT$COUNT EQU 12 ;Allow up to 12 CPM sectors for CPMLDR CPMLDR$ADDRESS EQU 100H ;Load the CPMLDR at 100H in RAM ; INCLUDE CP/M 3.0 MACRO LIBRARY AND THE Z80 INSTRUCTION MACROS: MACLIB CPM3 MACLIB Z80 ;-------------------------------------------------------------------------- ; CODE BEGINS HERE: ;-------------------------------------------------------------------------- JMP BOOT ;<----- INITIAL ENTRY ON COLD START JMP WBOOT ;REENTRY ON PROGRAM EXIT, WARM START JMP CONST ;RETURN CONSOLE INPUT STATUS JMP CONIN ;RETURN CONSOLE INPUT CHARACTER JMP CONOUT ;<------------ SEND CONSOLE OUTPUT CHARACTER JMP LIST ;SEND LIST OUTPUT CHARACTER JMP AUXOUT ;SEND AUXILLIARY OUTPUT CHARACTER JMP AUXIN ;RETURN AUXILLIARY INPUT CHARACTER JMP HOME ;SET DISKS TO LOGICAL HOME JMP SELDSK ;SELECT DISK DRIVE RETURN DISK PARAMETER INFO JMP SETTRK ;SET DISK TRACK JMP SETSEC ;SET DISK SECTOR JMP SETDMA ;SET DISK I/O MEMORY ADDRESS JMP READ ;<----------- READ PHYSICAL BLOCK(S) JMP WRITE ;WRITE PHYSICAL BLOCK(S) JMP LISTST ;RETURN LIST DEVICE STATUS JMP SECTRN ;TRANSLATE LOGICAL TO PHYSICAL SECTOR JMP CONOST ;RETURN CONSOLE OUTPUT STATUS JMP AUXIST ;RETURN AUXILLIARY INPUT STATUS JMP AUXOST ;RETURN AUXILLIARY OUTPUT STATUS JMP DEVTBL ;RETURN ADDRESS OF DEVICE DEFINITION TABLE JMP ?CINIT ;CHANGE BAUD RATE OF DEVICE JMP GETDRV ;RETURN ADDRESS OF DISK DRIVE TABLE JMP MULTIO ;SET MULTIPLE RECORD COUNT FOR DISK I/O JMP FLUSH ;FLUSH BIOS MAINTAINED DISK CACHING JMP ?MOVE ;BLOCK MOVE MEMORY TO MEMORY JMP ?TIME ;SIGNAL TIME AND DATE OPERATION JMP BNKSEL ;SEL BANK FOR CODE EXECUTION AND DEFAULT DMA JMP SETBNK ;SELECT DIFFERENT BANK FOR DISK I/O DMA OPS. JMP ?XMOVE ;SET SOURCE AND DEST. BANKS FOR ONE OPERATION JMP 0 ;RESERVED FOR FUTURE EXPANSION JMP 0 ; DITTO JMP 0 ; DITTO CONST: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: LISTST: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: AUXIST: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: AUXOST: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: FLUSH: XRA A ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: RET ; RETURN A FALSE STATUS LIST: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: AUXOUT: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: DEVTBL: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: ?CINIT: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: MULTIO: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: ?TIME: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: BNKSEL: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: SETBNK: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: ?XMOVE: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: CONIN: MVI A,'Z'-40H ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: RET AUXIN: MVI A,'Z'-40H ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: RET CONOUT: CALL CONOST ; ROUTINE OUTPUTS A CHARACTER IN [C] TO THE CONSOLE: JZ CONOUT MOV A,C ANI 7FH OUT SER1DATAPORT RET CONOST: IN SER1STATUSPORT ; RETURN CONSOLE OUTPUT STATUS: ; HW=0 MEANS BUSY... 1= CLEAR TO SEND ;; CMA ; INVERT LOGIC ANI SER1CTS RZ ; 0 IF NOT READY XRA A DCR A RET ?MOVE: XCHG LDIR XCHG RET SELDSK: LXI H,DPH0 ; RETURN DPH ADDRESS FOR DRIVE A: RET HOME: LXI B,0 ; HOME SELECTED DRIVE -- TREAT AS SETTRK(0): SETTRK: SBCD @TRK ; ROUTINE SETS TRACK TO ACCESS ON NEXT READ RET SETSEC: SBCD @SECT ; ROUTINE SETS SECTOR TO ACCESS ON NEXT READ RET SETDMA: SBCD @DMA ; ROUTINE SETS DISK MEMORY ADDRESS FOR READ RET SECTRN: MOV L,C ; NO TRANSLATION FOR HDISK MOV H,B RET GETDRV: LXI H,@DTBL ; RETURN ADDRESS OF DISK DRIVE TABLE: RET DCBINIT: RET ; ROUTINE HAS NO FUNCTION IN LOADER BIOS: WRITE: XRA A ; RETURN GOOD RESULT CODE RET WBOOT: RET ; WARM BOOT IS NOT USED IN LOADER BIOS ;-------------------------------------------------------------------------- ; BOOT ; ROUTINE DOES COLD BOOT INITIALIZATION: ;-------------------------------------------------------------------------- BOOT: CALL HDLOGIN ;Bring SD card online RZ ;Ret Z if no problem RESERR: LXI H,SD$FAIL ;Initilization of SD Drive failed CALL SPECIAL$PMSG ;Note we cannot use the normal @PMSG BIOS call. It appears not to be valid yet HLT ;Cannot recover easily, banks may be screwed up, just HALT ; GIVE UP WITH ERROR SetErrorFlag: ;For now just return with error flag set XRA A DCR A STA ERFLG ; Ret NZ if problem LSPD OLDSTACK RET ; SETUP THE SD CARD FOR OPERATION--------------------------------------- ; RETURNS Z SET=OK, NZ=ERROR ; at this point, disk comms are probably ok as the eprom primary boot ; loader has already read 12 sectors of this program into ram. HDLOGIN: LXI H,START$BOOT ; GIVE US A HINT ON CONSOLE CALL SPECIAL$PMSG XRA A STA ERFLG ; CLEAR THE ERROR FLAG STA CURRENT$DRIVE ; DRIVE TO BOOT FROM always 0? OUT CNT ; select pp 1 on pio1 ; USE ECHO CMD TO SEE IF ESP32 IS ALIVE MVI A,'E' ; request a byte echoback CALL PP1WR JNC HDERR MVI A,055H ; just random bits for the echo test CALL PP1WR ; wait and write out data JNC HDERR CALL PP1RD JNC HDERR ; bailout if we didnt get char back CPI 055H JNZ HDERR ; does it match? ; PP sd drive should be ready for action LXI H,END$BOOT ; GIVE US A HINT ON CONSOLE CALL SPECIAL$PMSG XRA A ;Z IS OK RET HDERR: XRA A DCR A STA ERFLG ;NZ IS ERROR RET ;-------------------------------------------------------------------------------- ; SD READ A SECTOR AT @TRK, @SECT TO Address at @DMA ; ;Returned Values: A=000H if no errors occurred ; A=001H if nonrecoverable error condition occurred ; A=0FFH if media has changed ;-------------------------------------------------------------------------------- READ: SSPD OLDSTACK ;At bottom of this smodule LXI SP,NEWSTACK XRA A STA ERFLG ;CLEAR THE ERROR FLAG STA CKSUM ;COMPUTE CKSUM AS WE GO ; COMPUTE OUR LINEAR SD SECTOR NUMBER FROM CPM @TRK AND @SEC = TRK*64+SEC LHLD @SECT XCHG LHLD @TRK ; DE HAS SEC, HL HAS TRK MVI H,00H ;zero high track byte MAX OF 255 TRKS IN CPM? DAD H ; *2 DAD H ; *4 DAD H ; *8 DAD H ; *16 DAD H ; *32 DAD H ; *64 DAD D ; + SECTOR # SHLD @LBA ; SAVE LINEAR SECTOR NUMBER ; CALL DBG$RD ; PRINT TRACK,SECTOR,DMA AND LBA TO CONSOLE MVI A,'R' ; request a sector read CALL PP1WR JNC RE07 MVI A,0 ; DISK IMAGE 0 CALL PP1WR JNC RE07 MOV A,L ; send the sector number CALL PP1WR JNC RE07 MOV A,H CALL PP1WR JNC RE07 LDA CKSUM CALL PP1WR ; send the pkt cksum JNC RE07 ; now expect 512 bytes to come back XRA A STA CKSUM LHLD @DMA ; DESTINATION BUFFER LXI B,512 ; BYTES TO READ DR02: CALL PP1RD JNC RE05 ; error out if we didnt get byte MOV M,A INX H DCX B MOV A,C ORA B JNZ DR02 ; loop for all bytes in sector LDA CKSUM ; get cksum from previous reads MOV B,A CALL PP1RD ; fetch cksum from esp32 JNC RE05 ; error out if we didnt get byte CMP B JNZ RE09 ; CKSUM ERROR LSPD OLDSTACK XRA A ; NEED Z=0 FOR NO ERRORS RET RE05: ;**** PP1 Timeout reading data RE07: ;**** PP1 Timeout writing data RE09: ; BAD CKSUM JMP SetErrorFlag ; Got error with Read Sector command ;=======================Parallel Port Routines========================== ;======================================================================= ; Check to see if we have data available for reading PP1DAV: IN CNT ; check for port 1 data available ANI I1DA ; (LATCH h->l) on pin 22 RNZ XRA A RET ;======================================================================= ; Check to see if it is clear to send a byte to external device PP1CTS: IN CNT ; check for port 1 DATA ACK'D ANI O1DR RNZ XRA A RET ;======================================================================= ; Read data sent to us by external device (waits short time for data) ; assumes pp1 has been previously selected ; read will timeout and clear cy if nothing to read within a short time ; read will return with data in A and cy set if successful PP1RD: PUSH H LXI H,4000H ; delay loop counter (16384 tries at getting data) PP102: CALL PP1DAV ; check for data JNZ PP106 DCX H ; no data.. try again for 65536 times MOV A,L ; can waste time by adding extra inst here ORA H JNZ PP102 POP H MVI A,0FFH ; dummy data STC ; set cy flag, then complement it CMC ; let caller know there was no data within timeout RET PP106: LDA CKSUM MOV L,A IN PIO MOV H,A ; save input byte ADD L ; update cksum STA CKSUM MOV A,H ; restore read byte POP H STC ; flag successful data fetch RET ;======================================================================= ; write out parallel port with timeouts ; assumes pp1 has been previously selected ; write will timeout and clear cy if unable to write within a short time ; write will return after sending data in A and setting cy if successful PP1WR: PUSH H ; we trash hl using it as a counter PUSH PSW ; save output char LXI H,0000H ; delay loop counter PP1W0: CALL PP1CTS ; clear to send? JNZ PP1W1 ; waste some time ; must use these in pairs or big trouble follows! XTHL ; timeouts of up to 2 sec required XTHL ; 18 t states in 8080, 19T EX (SP),HL in z80 XTHL ; nice single byte inst that takes a long time XTHL XTHL XTHL DCX H ; no data.. try again for 65536 times MOV A,L ; can waste time by adding extra inst here ORA H JNZ PP1W0 POP PSW POP H STC ; set cy flag, then complement it CMC ; let caller know unable to wr within timeout RET PP1W1: POP PSW PUSH PSW MOV L,A ; COPY OF CHAR TO O/P LDA CKSUM ADD L STA CKSUM ; UPDATE RUNNING CKSUM OF O/P CHARS POP PSW POP H OUT PIO ; actually send data out STC ; flag successful data wr RET ;----------------------- PRINT string @HL ON CONSOLE until $ ----------- SPECIAL$PMSG: ; Cannot use @PMSG in LOADERBIOS MOV A,M INX H CPI '$' RZ MOV C,A CALL CONOUT ; Hardware send to console JMP SPECIAL$PMSG ;----------------------------- PRINT [HL] in hex ON CONSOLE ------------ PRINT$HL: MOV A,H CALL PRINT01 MOV A,L PRINT01: PUSH PSW RRC RRC RRC RRC CALL PRINT02 POP PSW PRINT02: CALL CONV JP CONOUT ; CONVERT A to 1 HEX ASCII character CONV: ANI 0FH ADI 90H DAA ACI 40H DAA MOV C,A RET ;---------------- DEBUG ROUTINE TO PRINT CPM TRK AND SECTOR BEING READ IF 0 DBG$RD: PUSH H PUSH B PUSH D LXI H,DBG01 ; TRACK CALL SPECIAL$PMSG LHLD @TRK CALL PRINT$HL LXI H,DBG02 ; SECTOR CALL SPECIAL$PMSG LHLD @SECT CALL PRINT$HL LXI H,DBG03 ; DMA CALL SPECIAL$PMSG LHLD @DMA CALL PRINT$HL LXI H,DBG04 ; LBA CALL SPECIAL$PMSG LHLD @LBA CALL PRINT$HL POP D POP B POP H RET ENDIF ;----------------------------------------------------------------------- START$BOOT: DB CR,LF,'hldr-pp - testing disk communication$' END$BOOT: DB '.. OK$' DBG01: DB CR,LF,'Read Trk=$' DBG02: DB ' Sec=$' DBG03: DB ' @$' DBG04: DB ' LBA=$' SD$FAIL: DB CR,LF,'Communicationwith SD Drive Failed. HALTING!$' @TRK: DS 2 ;2 BYTES FOR NEXT TRACK TO READ OR WRITE @DMA: DS 2 ;2 BYTES FOR NEXT DMA ADDRESS @SECT: DS 2 ;2 BYTES FOR SECTOR @LBA: DS 2 ; OUR COMPUTED SD CARD SECTOR NUMBER ERFLG: DS 1 ;Error Flag. CURRENT$DRIVE: DS 1 ; 0 OR 1 CKSUM: DS 1 ; cksum updated as we read or write bytes ;-------------------------------------------------------- ; BUILD CPM3 DPH'S ETC USING MACROS FOR HDISK AND BY HAND ; ONLY 1 DISK, DISK IMAGE 0 USED FOR BOOTING ;-------------------------------------------------------- ; DISK DRIVE TABLE: @DTBL: DW DPH0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 ; DRIVE A DISK PARAMETER HEADER: DW WRITE ;DCB-II WRITE ROUTINE DW READ ;DCB-II READ ROUTINE DW SELDSK ;DCB-II LOGIN PROCEDURE DW DCBINIT ;DCB-II DRIVE INITIALIZATION ROUTINE DB 0 ;RELATIVE DRIVE 0 ON THIS CONTROLLER DB 0 ;MEDIA TYPE ALWAYS KNOWN FOR HARD DISK DPH0: DW 0 ;TRANSLATION VECTOR DB 0,0,0,0,0,0,0,0,0 DB 0 ;MEDIA FLAG DW HD$DPB ;ADDRESS OF DISK PARAMETER BLOCK DW CSV ;CHECKSUM VECTOR DW ALV ;ALLOCATION VECTOR DW DIRBCB ;DIRECTORY BUFFER CONTROL BLOCK DW DATABCB ;DATA BUFFER CONTROL BLOCK DW 0FFFFH ;NO HASHING DB 0 ;HASH BANK ; SD HARD DISK PARAMETER BLOCK: 512B/SECTOR, 64 SECTORS/TRACK, ; 256 TRACKS = ~8MB DISK IMAGE HD$DPB: DPB 512,64,256,2048,1024,1,8000H ; DIRECTORY BUFFER CONTROL BLOCK: DIRBCB: DB 0FFH ;DRIVE 0 DS 3 DS 1 DS 1 DS 2 DS 2 DW DIRBUF ;POINTER TO DIRECTORY BUFFER ; DATA BUFFER CONTROL BLOCK: DATABCB: DB 0FFH ;DRIVE 0 DS 3 DS 1 DS 1 DS 2 DS 2 DW DATABUF ;POINTER TO DATA BUFFER DS 80 ; MAKE ROOM FOR OUR TEMP STACK NEWSTACK:DW 0 OLDSTACK:DW 0 ; HOPEFULLY KEEP OLD STACK POINTER SAFE ; KEEP BUFFERS ABOVE STACK (PARANOID ABOUT STACK OVERWRITES) ; DIRECTORY BUFFER DIRBUF: DS 512 ;1 PHYSICAL SECTOR ; DATA BUFFER: DATABUF:DS 512 ;1 PHYSICAL SECTOR ; DRIVE ALLOCATION VECTOR: ALV: DS 1000 ;SPACE FOR DOUBLE BIT ALLOCATION VECTORS CSV: ;NO CHECKSUM VECTOR REQUIRED FOR A HDISK DB '<-- END OF LDRBIOS ' ;For debugging ; END