;---------------------------------------- ; ; LSI-M3 HD Monitor Prom ; ; Steve Hunt ; version 1.7 ; 2015-12-09 ; ; This simple monitor resides in the unused hard disk prom socket ; on the dual floppy LSI M-Three system. It takes advantage of the ; fact that the boot prom checks for the presence of the hard disk ; prom and if found asks if you want to boot from floppy (defaults ; to booting from hard disk prom). ; ;---------------------------------------- ; ; ASCII equates ; bell equ 07h lf equ 0Ah cr equ 0Dh esc equ 1bh space equ 20h comma equ 2Ch delete equ 7fh ; instruction opcodes ; op_jp equ 0C3h op_rst7 equ 0ffh ; 8251 universal synchronous/asyncronous receiver/transmitter ports ; USART_DATA equ 8ch ; data register USART_CNTL equ 8dh ; control register USART_STAT equ 8dh ; status register ; memory addresses ; RST7VEC equ 038h ; rst 7 vector STACK equ 0100h ; default stack, as used by standard prom SAVREG equ 0E7F9h ; [MUST BE IN RAM] top of saved registers SAVSTK equ 0E7F9h ; [MUST BE IN RAM] saved stack BRKCODE equ 0E7FBh ; [MUST BE IN RAM] break point code BRKADDR equ 0E7FCh ; [MUST BE IN RAM] break point address DMPADDR equ 0E7FEh ; [MUST BE IN RAM] default dump address ; standard prom routines ; BOOT equ 0F000h ; boot from floppy CSTS equ 0F007h ; console status CINW equ 0F00Ah ; console char input (with wait) COUT equ 0F00Dh ; console char output ; hard disk prom addresses HDPROM equ 0F800h ; prom start HDBOOT equ 0FFFCh ; boot from hard disk ;---------------------------------------- ; .z80 aseg .phase HDPROM ; ; hard disk prom jump table - referenced by the standard prom ; ; simply trap all functions, display the return address ; (it may prove useful) and then enter the monitor ; JMPTBL: JP HDTRAP ; (F800) read hd JP HDTRAP ; (F803) write hd JP HDTRAP ; (F806) write/read-back hd JP HDTRAP ; (F809) select hd JP HDTRAP ; (F80C) return hd status JP HDTRAP ; (F80F) unknown hd function HDTRAP: ; LD HL,TRAPTXT ; 'hd function trap' CALL DSPMSG POP HL ; get and display PUSH HL CALL DSPWORD ; the return address JR MONINIT ; enter monitor TRAPTXT:db cr,lf,'HD function trap, ret addr: $' ;---------------------------------------- ; ; initialise monitor ; MONINIT: ; LD HL,0 LD (DMPADDR),HL ; set default dump address LD A,op_jp LD (RST7VEC),A ; patch rst 7 vector LD HL,MONBRKP LD (RST7VEC +1),HL ; to jump to monitor break point LD SP,STACK ; set new stack LD HL,MONTXT ; 'lsi m-three monitor' CALL DSPMSG MONLIST: ; LD HL,CMDTXT ; monitor command list CALL DSPMSG MONPROMPT: ; LD SP,STACK ; reset stack CALL DSPCRLF ; start new line LD C,'>' CALL COUT ; display monitor prompt CALL CONINUP ; get command (upper case) CP 'S' JP Z,SETMEM ; set memory CP 'D' JP Z,DUMPMEM ; dump memory CP 'G' JP Z,GOTOMEM ; goto memory CP 'F' JP Z,FILLMEM ; fill memory CP 'C' JP Z,COPYMEM ; copy memory CP 'I' JP Z,INPORT ; input from port CP 'O' JP Z,OUTPORT ; output to port CP 'L' JP Z,LOADHEX ; load hex file CP 'B' JP Z,BOOT ; reboot CP cr JP Z,DUMPNXT ; dump next memory block CP '?' JR Z,MONLIST ; display command list MONERR: ; LD C,'?' CALL COUT ; display error char MONCLRKEY: ; CALL CSTS ; q. anything in keyboard buffer? JR Z,MONPROMPT ; no - restart the monitor CALL CINW ; remove keypress JR MONCLRKEY ; check again MONTXT: db cr,lf,lf,'LSI M-THREE Monitor v1.7, SAH 09/12/2015',cr,lf,'$' CMDTXT: db cr,lf,'D[,] = Dump memory' db cr,lf,'Enter = Dump next memory block' db cr,lf,'S = Set memory' db cr,lf,'F,, = Fill memory with byte' db cr,lf,'C,, = Copy memory block' db cr,lf,'G[,] = Goto address [until break point]' db cr,lf,'I = Input from port' db cr,lf,'O, = Output byte to port' db cr,lf,'L = Load Intel hex file' db cr,lf,'B = re-Boot' db cr,lf,'$' ;---------------------------------------- ; ;---------------------------------------- ; ; set memory ; ; S ; SETMEM: ; CALL GETADDR ; get address in HL JP C,MONERR ; invalid address - exit JP Z,MONERR ; nothing entered - exit SETMEM2: ; CALL DSPCRLF ; new line CALL DSPWORD ; display address CALL DSPSPACE ; space LD A,(HL) ; get current byte CALL DSPBYTE ; display in hex format CALL DSPSPACE ; space CALL GETBYTE ; enter new byte JP C,MONERR ; invalid byte - exit JR Z,SETMEM4 ; nothing entered - skip set LD (HL),A ; replace byte in memory CP (HL) ; q. byte changed (i.e. ram)? JR Z,SETMEM4 ; yes CALL DSPSPACE ; space LD A,'R' CALL CONOUT ; display R, indicate rom SETMEM4: ; INC HL ; point to next byte JR SETMEM2 ; loop ;---------------------------------------- ; ; dump next 128 bytes of memory ; ; ; DUMPNXT: ; LD HL,(DMPADDR) ; last starting point JR DUMPMEM4 ; display default range ;---------------------------------------- ; ; dump memory ; ; D [[,] ; DUMPMEM: ; CALL GETADDR ; get start address in HL JP C,MONERR ; invalid address - exit JR NZ,DUMPMEM2 ; address entered LD HL,(DMPADDR) ; nothing entered - continue from last dump JR DUMPMEM4 DUMPMEM2: ; LD (DMPADDR),HL ; store starting address CP cr ; q. enter pressed? JR Z,DUMPMEM4 ; yes - assume default range CALL GETADDR ; no - get end address in HL JP C,MONERR ; invalid address - exit JR NZ,DUMPMEM6 ; address entered - calc range DUMPMEM4: ; LD DE,128 ; default dump range ADD HL,DE DUMPMEM6: ; EX DE,HL ; DE = ending address DUMPMEM8: ; CALL CSTS ; q. any key pressed (abort dump)? JP NZ,MONCLRKEY ; yes - back to monitor LD HL,(DMPADDR) ; get start address CALL DSPCRLF ; new line CALL DSPWORD ; display address ; ; display 16 bytes in hex format ; DUMPMEM10: ; CALL DSPSPACE ; space LD A,(HL) ; get a byte CALL DSPBYTE ; display in hex format INC HL ; point to next byte LD A,L AND 0FH ; q. multiple of 16? JR NZ,DUMPMEM10 ; no - display next byte CALL DSPSPACE ; space LD HL,(DMPADDR) ; ; display the same 16 bytes in ascii format ; DUMPMEM12: ; LD A,(HL) ; get a byte CALL DSPCHAR ; display ascii character INC HL ; point to next byte LD A,L AND 0FH ; q. multiple of 16? JR NZ,DUMPMEM12 ; no - display next byte ; ; loop if more to display ; LD (DMPADDR),HL ; save last address reached LD A,H OR L ; q. address looped to zero? JP Z,MONPROMPT ; yes - back to monitor OR A ; clear carry SBC HL,DE ; subtract ending address JR C,DUMPMEM8 ; loop again if more to display JP MONPROMPT ; otherwise - back to monitor ;---------------------------------------- ; ; goto memory address, until optional break point ; ; G [,] ; GOTOMEM: ; LD HL,0FFFFh ; default break address to rom LD (BRKADDR),HL ; so as no update unless break pointer set CALL GETADDR ; get address in HL JP C,MONERR ; invalid - exit JP Z,MONERR ; nothing entered - exit CP cr ; q. enter pressed? JR Z,GOTOMEM4 ; yes - prepare goto ; ; get/set break point ; EX DE,HL ; save goto address in DE CALL GETADDR ; get break point address in HL JP C,MONERR ; invalid address - exit JR Z,GOTOMEM2 ; nothing entered - prepare goto LD A,(HL) LD (BRKCODE),A ; save existing byte LD (BRKADDR),HL ; and address LD A,op_rst7 LD (HL),A ; set rst7 instruction at break point GOTOMEM2: ; EX DE,HL ; get goto address back in HL GOTOMEM4: ; LD DE,MONRTRN PUSH DE ; provide none rst7 return address JP (HL) ; jump to goto address ;---------------------------------------- ; ; monitor return or monitor break point ; ; display registers and then remove break point ; MONRTRN: MONBRKP: ; ; first save the flags and registers ; ; ignore the alt register set as the standard ; lsi-m3 prom's vdu interrupt routine uses them ; LD (SAVSTK),SP ; save current stack pointer LD SP,SAVREG PUSH AF ; and standard registers PUSH BC PUSH DE PUSH HL PUSH IX PUSH IY LD SP,(SAVSTK) ; restore stack pointer ; ; display flags ; CALL DSPCRLF ; new line LD BC,8053H ; mask + 'S'ign CALL DSPFLG LD BC,405AH ; mask + 'Z'ero CALL DSPFLG LD BC,1048H ; mask + 'H'alf carry CALL DSPFLG LD BC,0456H ; mask + o'V'erflow CALL DSPFLG LD BC,024EH ; mask + Add/Subtract i'N'dicator CALL DSPFLG LD BC,0143H ; mask + 'C'arry CALL DSPFLG ; ; display registers ; LD HL,ATXT ; A CALL DSPMSG LD A,(SAVREG -1) CALL DSPBYTE LD HL,BCTXT ; BC CALL DSPMSG LD HL,(SAVREG -4) CALL DSPWORD LD HL,DETXT ; DE CALL DSPMSG LD HL,(SAVREG -6) CALL DSPWORD LD HL,HLTXT ; HL CALL DSPMSG LD HL,(SAVREG -8) CALL DSPWORD LD HL,IXTXT ; IX CALL DSPMSG LD HL,(SAVREG -10) CALL DSPWORD LD HL,IYTXT ; IY CALL DSPMSG LD HL,(SAVREG -12) CALL DSPWORD LD HL,SPTXT ; SP CALL DSPMSG LD HL,(SAVSTK) CALL DSPWORD ; ; clear break point (if encountered) ; LD A,op_rst7 LD HL,(BRKADDR) CP (HL) ; q. rst7 instruction at break address? JP NZ,MONPROMPT ; no - back to monitor LD A,(BRKCODE) ; replace rst7 instruction LD (HL),A ; with original instruction byte JP MONPROMPT ; now back to monitor ATXT: db ' A=$' BCTXT: db ' BC=$' DETXT: db ' DE=$' HLTXT: db ' HL=$' IXTXT: db ' IX=$' IYTXT: db ' IY=$' SPTXT: db ' SP=$' ;---------------------------------------- ; ; display individual flag ; ; entry B = flag bit mask, C = flag letter ; DSPFLG: ; LD A,(SAVREG -2) ; saved flags AND B LD A,'-' JR Z,DSPFLG2 LD A,C DSPFLG2: ; JP CONOUT ;---------------------------------------- ; ; fill memory block ; ; F ; FILLMEM: ; CALL GETADDR ; enter start address in HL JP C,MONERR ; invalid address - exit JP Z,MONERR ; nothing entered - exit EX DE,HL ; start address now in DE CALL GETADDR ; enter end address in HL JP C,MONERR ; invalid end address - exit JP Z,MONERR ; nothing entered - exit CALL GETBYTE ; get filler byte JP C,MONERR ; invalid byte - exit JP Z,MONERR ; nothing entered - exit ; ; de = start address ; hl = end address ; a = filler byte ; OR A ; clear carry SBC HL,DE ; HL = HL - DE JP C,MONERR ; overflow, exit invalid LD C,L LD B,H ; BC = byte count LD L,E LD H,D ; HL = source address LD (HL),A ; put filler byte into memory JR Z,FILLMEM2 ; all done if start address = end address INC DE ; DE = destination address LDIR ; copy block FILLMEM2: ; JP MONPROMPT ; back to monitor ;---------------------------------------- ; ; copy memory block ; ; C ; COPYMEM: ; CALL GETADDR ; enter source address in HL JP C,MONERR ; invalid address - exit JP Z,MONERR ; nothing entered - exit EX DE,HL ; source address now in DE CALL GETADDR ; enter dest address in HL JP C,MONERR ; invalid dest address - exit JP Z,MONERR ; nothing entered - exit PUSH HL PUSH DE CALL GETADDR ; enter length in HL LD B,H LD C,L ; length in BC POP HL ; source address in HL POP DE ; dest address in DE JP C,MONERR ; invalid length - exit JP Z,MONERR ; nothing entered - exit LD A,B OR C ; q. zero length? JP Z,MONERR ; yes - exit invalid ; ; no other checking, it's up to the user to get source, dest and length correct ; LDIR ; copy memory block JP MONPROMPT ; back to monitor ;---------------------------------------- ; ; input from port ; ; I ; INPORT: ; CALL GETBYTE ; enter port number JP C,MONERR ; invalid - exit JP Z,MONERR ; nothing entered - exit LD C,A IN A,(C) ; input from port LD L,A ; save it CALL DSPCRLF ; new line LD A,L CALL DSPBYTE ; display byte in hex format ; ; display input byte in bit format ; CALL DSPSPACE ; space LD E,L ; e = input byte LD B,8 ; 8 bits INPORT2: ; SLA E ; shift left into carry LD A,'0' ADC A,0 ; add carry to ascii zero CALL DSPNIBBLE ; display digit DJNZ INPORT2 ; loop for next bit CALL DSPSPACE ; space LD A,L CALL DSPCHAR ; display ascii char JP MONPROMPT ; back to monitor ;---------------------------------------- ; ; output to port ; ; O ; OUTPORT: ; CALL GETBYTE ; enter port number JP C,MONERR ; invalid - exit JP Z,MONERR ; nothing entered - exit LD L,A ; save port number CALL GETBYTE ; enter byte JP C,MONERR ; invalid - exit JP Z,MONERR ; nothing entered - exit LD C,L ; restore port number OUT (C),A ; output byte to port JP MONPROMPT ; back to monitor ;---------------------------------------- ; ; load intel hex formatted file via the 8251 usart ; ; notes: ; 1. assumes default usart settings - 9600, 8, N, 1. ; 2. all record types, except eof, are assumed to be data. ; 3. any key press or invalid hex input aborts the process. ; 4. the start of line character is echoed to screen as a simple ; progress indicator. however, this can cause loss of serial ; input if the screen scrolls due to the time taken by the ; crt scroll routine and the fact we don't use any form of ; handshaking. our workaround is to first clear the screen ; to reduce the likelyhood of screen scroll. ; LOADHEX: ; LD HL,STRLTXT ; 'start load' CALL DSPMSG LOADHEX2: ; CALL SINCHAR ; get start of record char JP C,MONERR ; user abort - back to monitor CP ':' ; q. start of intel hex format? JR NZ,LOADHEX2 ; no - get another char LD C,A CALL COUT ; indicate progress LD E,0 ; reset the checksum calculation CALL SINBYTE ; get record length JP C,MONERR ; invalid or user abort - back to monitor LD B,A CALL SINBYTE ; get record address high byte JP C,MONERR ; invalid or user abort - back to monitor LD H,A CALL SINBYTE ; get record address low byte JP C,MONERR ; invalid or user abort - back to monitor LD L,A CALL SINBYTE ; get record type JP C,MONERR ; invalid or user abort - back to monitor CP 01 ; q. end of file marker? JR NZ,LOADHEX4 ; no - assume data LD HL,ENDLTXT ; 'end load' CALL DSPMSG JP MONPROMPT ; yes - back to monitor LOADHEX4: ; CALL SINBYTE ; get record byte JP C,MONERR ; invalid or user abort - back to monitor LD (HL),A ; save to memory INC HL ; bump pointer DJNZ LOADHEX4 ; loop CALL SINBYTE ; get record checksum byte JP C,MONERR ; invalid or user abort - back to monitor JR Z,LOADHEX2 ; calculated checksum valid - get next record LD HL,CERRTXT ; 'checksum error' CALL DSPMSG JP MONPROMPT ; back to monitor STRLTXT:db esc,'E','Start HEX load. Press any key to abort.',cr,lf,'$' ENDLTXT:db cr,lf,'HEX load complete.',cr,lf,'$' CERRTXT:db cr,lf,'Checksum error.',cr,lf,'$' ;---------------------------------------- ; ; *** general purpose routines *** ; ;---------------------------------------- ; ; console input, with echo and forced upper case ; CONINUP: ; PUSH BC CALL CINW ; wait for input via prom POP BC PUSH AF CP space ; q. below space? JR C,CONINUP2 ; yes - skip it CP delete ; q. below delete? CALL C,CONOUT ; yes - echo char CONINUP2: ; POP AF CP 'a' ; q. below 'a'? RET C ; yes - exit CP 'z'+1 ; q. above 'z'? RET NC ; yes - exit AND 05Fh ; force upper case RET ;---------------------------------------- ; ; enter a byte from the console ; Regs in: none ; Regs out: A = byte read ; CF set = error ; ZF set = nothing entered ; GETBYTE: ; PUSH HL PUSH BC PUSH DE LD C,0 ; indicate 2 digits max CALL GETHEXN ; get hex number LD A,L ; return value POP DE POP BC POP HL RET ;---------------------------------------- ; ; enter an address from the console ; Regs in: none ; Regs out: HL = address read ; A = termination char entered ; CF set = error ; ZF set = nothing entererd ; GETADDR: ; PUSH DE LD C,1 ; indicate 4 digits max CALL GETHEXN ; get hex number POP DE RET ;---------------------------------------- ; ; enter a hex number from the console ; ; A hex number consists of zero or more spaces ; followed by one or more hex digits (max of 2 or 4) ; and is terminated by a , or ; ; Regs in: C = 0 = maximum of 2 digits ; C = 1 = maximum of 4 digits ; Regs Out: HL = address ; A = termination char entered ; CF set = error ; ZF set = nothing entererd ; Destroyed: B, DE ; GETHEXN: ; LD HL,0 ; default return value LD B,L LD D,L GETHEXN2: ; CALL CONINUP ; get input CP space JR Z,GETHEXN2 ; skip leading spaces GETHEXN4: ; CP cr ; q. carriage return? JR Z,GETHEXN12 ; yes - check number entered CALL CHR2NIBBLE ; convert char to nibble RET C ; exit if invalid ADD HL,HL ; shift HL left 4 bits ADD HL,HL ADD HL,HL ADD HL,HL LD E,A ; DE = current HEX digit ADD HL,DE ; add current digit to number RLC B ; rotate previous 'number entered' flag SET 0,B ; flag 'number entered' BIT 0,C ; q. max 2 or 4 digits allowed? JR NZ,GETHEXN6 ; check max of 4 BIT 2,B ; q. more than 2 digits enetered? JR NZ,GETHEXN8 ; yes - invalid GETHEXN6: ; BIT 4,B ; q. more than 4 digits entered? JR Z,GETHEXN10 ; no - check trailing terminator GETHEXN8: ; SCF ; too many digits - flag invalid RET ; exit GETHEXN10: ; CALL CONINUP ; get next input CP space ; q. trailing space? JR Z,GETHEXN12 ; yes - check number entered CP comma ; q. trailing comma? JR NZ,GETHEXN4 ; no - check next character GETHEXN12: ; BIT 0,B ; q. number entered? RET ; return with Z set or cleared ;---------------------------------------- ; ; convert hex character to nibble ; ; Regs in: A = character ; Regs Out: A = nibble in lower 4 bits ; CF set = error ; CHR2NIBBLE: ; SUB '0' ; qualify and convert RET C ; return invalid if <0 CP 'G'-'0' ; q. result > F? CCF ; reverse carry RET C ; yes - return invalid CP 10 ; q. number? CCF ; reverse carry RET NC ; yes - return ok SUB 'A'-'9'-1 ; adjust CP 0AH ; filter ":" through "@" RET ;---------------------------------------- ; ; display new line, preserves HL ; DSPCRLF: ; LD A,cr CALL CONOUT ; display carriage return LD A,lf JP CONOUT ; display line feed and return ;---------------------------------------- ; ; display space, preserves HL ; DSPSPACE: ; LD A,space JP CONOUT ; display space and return ;---------------------------------------- ; ; display ascii character ; DSPCHAR: ; CP space ; q. below space char? JR C,DSPCHAR2 ; yes - display a dot CP delete ; q. below delete char? JR C,DSPCHAR4 ; yes - display it DSPCHAR2: ; LD A,'.' ; no - replace with a dot DSPCHAR4: ; JP CONOUT ; display char and return ;---------------------------------------- ; ; display hex word (HL = word), ; DSPWORD: ; LD A,H ; high nibble CALL DSPBYTE LD A,L ; low nibble ; ; display hex byte (A = byte), ; DSPBYTE: ; PUSH AF RRCA RRCA RRCA RRCA CALL DSPNIBBLE ; display high nibble POP AF ; restore and display low nibble ; ; dislay hex nibble (A = nibble) ; DSPNIBBLE: ; AND 0FH ; clear high nibble CP 0AH SBC A,69H DAA ; low nibble now ascii hex JP CONOUT ; display character and return ;---------------------------------------- ; ; display $ terminated message (HL = message address) ; DSPMSG: ; LD A,(HL) ; get char CP '$' ; q. end of string? RET Z ; yes - exit LD C,A PUSH HL CALL COUT ; char output via prom POP HL INC HL ; bump pointer JR DSPMSG ; loop for next char ;---------------------------------------- ; ; console output via prom routine ; ; preserves all registers ; CONOUT: ; PUSH BC PUSH DE PUSH HL LD C,A CALL COUT ; char output via prom POP HL POP DE POP BC RET ;---------------------------------------- ; ; serial character input from 8251 usart ; ; first checks for user abort (any key pressed) ; SINCHAR: ; CALL CSTS ; console input status via prom JR Z,SINCHAR2 SCF ; flag key pressed RET SINCHAR2: ; IN A,(USART_CNTL) ; get 8251 status AND 02 ; q. char received? JR Z,SINCHAR ; no - loop IN A,(USART_DATA) ; get data RET ;---------------------------------------- ; ; serial ascii byte input from 8251 usart ; ; also updates checksum byte in e ; final checksum should set z flag ; SINBYTE: ; CALL SINCHAR ; get serial input CALL NC,CHR2NIBBLE ; convert char to nibble RET C ; user abort or invalid ADD A,A ; shift to upper nibble ADD A,A ADD A,A ADD A,A LD D,A ; save it CALL SINCHAR ; get serial input CALL NC,CHR2NIBBLE ; convert char to nibble RET C ; user abort or invalid OR D ; merge in high nibble LD D,A ; save input byte LD A,E SUB D ; update checksum OR A ; set final checksum flag and clear carry LD E,A LD A,D ; retrieve input byte RET ;---------------------------------------- ; ; entry point to HD prom from standard prom DS HDBOOT-$,0 HDENTRY:JP MONINIT CKSUM: DB 0 ;---------------------------------------- ; ;---------------------------------------- END