;----------------------------------------------------------------------------- ; ; Overlay for ZMP (Z-Modem Program) ; ; Name ZMO-WBW.Z80 ; ; Dated April 14, 2025 ; ; Written by - ; Phil Summers, Wayne Warthen ; ; ROMWBW version using HBIOS and https://github.com/mecparts/zmp ; version of zmodem ; ; - All modem/serial i/o is through RomWBW HBIOS. ; ; - Data bits, stop bits, and parity are determined by HBIOS. ; The ZMP settings will have no effect. Use RomWBW MODE ; command to set serial port config before starting ZMP. ; ; - Timing delay calculations based on HBIOS reported CPU speed. ; ; - The pcfg equate determines whether the overlay will ; implement port initialization. Normally, pcfg will be ; set to false because ZMP does not allow configuring ; many HBIOS speeds (notably the 115200 baud hardwired ; into many RCBus systems). In this case, you must ; configure the modem port using the RomWBW ; MODE command as desired before starting ZMP. ; ; - The modem port is assigned to an HBIOS character unit. By ; default, the modem is assigned to HBIOS character unit 1. ; An alternate HBIOS character unit may be specified as a ; parameter on the command line as a single number. For ; example, the following will assign HBIOS port 3 as the ; ZMP modem port: ; ; ZMP 3 ; ; - The original version of ZMP from Ron Murray allows you to ; select from 2 logical modem ports. This overlay ignores ; any attempt to select ports from within ZMP. The desired ; modem port should be assigned using the command line ; parameter as described above. ; ; - Teraterm users may need to change the ZmodemWinSize value ; to 1024 in the teraterm.ini configuration file. ; ;----------------------------------------------------------------------------- ; ; ; System-dependent code overlay for ZMODEM ; ; ; ; Insert your own code as necessary in this file. Code contained herein ; has been written in Z80 code for use with M80 or SLR. Assemble as follows: ; ; SLR ZMO-xx01/h ; MLOAD ZMP.COM=ZMODEM.COM,ZMO-xx01.HEX ; or ; M80 =ZMO-xx01.Z80 ; RELHEX ZMO-xx01 ; MLOAD ZMP.COM=ZMODEM.COM,ZMO-xx01.HEX ; ; ; (Don't use L80 without changing the source for assembly as a ; cseg file.) ; ;----------------------------------------------------------------------------- ; ; ; Notes on modifying this file: ; ; C requires that functions do not change either index register (IX or IY). ; If your overlay requires either of these to be changed, ensure they are ; restored to the original values on return. ; Since collecting parameters from C functions can be tricky, only change ; the parts marked 'Insert your own code here'. Do NOT modify the jump ; table at the start. Do NOT modify the entry/exit sections of each ; function. Do NOT pass 'GO'. Do NOT collect $200. ; Apart from defining modem functions, this file also defines terminal ; characteristics. Examples provided are for ADM-3A (with a few of my own ; additions). Modify to suit your own terminal. An inline print routine ; is provided for printing strings in the usual way: usage is ; ; call print ; db 'required string',0 ; ;----------------------------------------------------------------------------- ; ; ; Don't forget to set your clock speed at the clkspd variable. ; ; ; If you find your overlay exceeds the maximum size (currently 0400h), ; you will have to contact me for another version. If too many people need ; to do it, we haven't allowed enough room. ; ; Ron Murray 15/8/88 ; ; ; ;--------------------------------------------------------------------------- false equ 0 true equ not false ;------------------------------------------------------------------------------ ; User-set variables: debug equ false ; to allow debugging of overlay with Z8E etc. clkspd equ 8 ; Processor clock speed in MHz pcfg equ false ; Allow ZMP to configure port ; ;Set the following two equates to the drive and user area which will contain ; ZMP's .OVR files, .CFG file, .FON file and .HLP file. Set both to zero ; (null) to locate them on the drive from which ZMP was invoked. overdrive equ 0 ; Drive to find overlay files on ('A'-'P') overuser equ 0 ; User area to find files ;------------------------------------------------------------------------------ ; NOT user-set variables userdef equ 0145h ; origin of this overlay ; This address should not change with ; subsequent revisions. mspeed equ 03ch ; location of current baud rate. ovsize equ 0400h ; max size of this overlay fcb equ 05ch ; primary command line CP/M fcb .z80 ; use z80 code aseg ; absolute if debug org 100h ; so you can debug it with cebug, zsid, etc else org userdef endif esc equ 1bh ctrlq equ 11h cr equ 0dh lf equ 0ah bdos equ 5 codebgn equ $ ;Jump table for the overlay: do NOT change this jump_tab: jp scrnpr ; screen print jp mrd ; modem read with timeout jp mchin ; get a character from modem jp mchout ; send a character to the modem jp mordy ; test for tx buffer empty jp mirdy ; test for character received jp sndbrk ; send break jp cursadd ; cursor addressing jp cls ; clear screen jp invon ; inverse video on jp invoff ; inverse video off jp hide ; hide cursor jp show ; show cursor jp savecu ; save cursor position jp rescu ; restore cursor position jp mint ; service modem interrupt jp invec ; initialise interrupt vectors jp dinvec ; de-initialise interrupt vectors jp mdmerr ; test uart flags for error jp dtron ; turn DTR on jp dtroff ; turn DTR OFF jp init ; initialise uart jp wait ; wait seconds jp mswait ; wait milliseconds jp userin ; user-defined entry routine jp userout ; user-defined exit routine jp getvars ; get system variables jp setport ; set port (0 or 1) ; Spare jumps for compatibility with future versions jp spare ; spare for later use jp spare ; spare for later use jp spare ; spare for later use jp spare ; spare for later use jp spare ; spare for later use jp spare ; spare for later use ; Local storage hbunit db 1 ; Active HBIOS unit for modem I/O cpumhz db clkspd ; CPU clock speed in MHz ; ; Main code starts here ; ;Screen print function scrnpr: ; <== Insert your own code here call print db 'Screen print not supported.',cr,lf,0 ; <== End of your own code spare: ret ; User-defined entry routine: leave empty if not needed userin: push bc push de push hl call print db cr,lf,'ZMP Overlay for RomWBW HBIOS v1.0',cr,lf,0 ; Scan and parse default FCB to initialize HBIOS unit if ; specified. ld a,(fcb + 1) ; get parm from fcb cp ' ' ; anything there? jr z,userin1 ; if empty, done sub '0' ; ASCII to binary jr c,parmerr ; less than 0, parm error cp 10 ; >= 10? jr nc,parmerr ; greater than 9, parm error ld (hbunit),a ; save it userin1: call showcom ; show com port info ; check that unit is actually available in HBIOS ld bc,0f800h ; get HBIOS char unit count rst 8 ; do it ld a,(hbunit) ; get active modem unit cp e ; compare to units available jr nc,porterr ; unit too high, port error if pcfg ; Force port initialization by setting an invalid ; baud rate (mspeed). ; speed to an arbitrary value of 8 (9600 baud). ld a,-1 ld (mspeed),a else ; We don't support port configuration. Here we set the modem ; speed to an arbitrary value of 8 (9600 baud). ld a,8 ld (mspeed),a endif ld bc,0f8f0h ; HBIOS func get CPU info rst 08 ; do it, l = CPU speed in MHz ld a,l ; to accum ld (cpumhz),a ; and save it for wait loop ld hl,2 call waithls pop hl pop de pop bc ret parmerr: ; Handle a bad parameter call print ; print error message db cr,lf,'ZMP parameter error!',cr,lf,0 rst 0 ; bail out to OS porterr: ; Handle a bad port call print ; print error message db cr,lf,'Invalid HBIOS unit for ZMP Modem port!',cr,lf,0 rst 0 ; bail out to OS showcom: ; Display the HBIOS unit to be used for the ZMP Modem call print db cr,lf,'ZMP Modem on HBIOS Unit #',0 ld a,(hbunit) add a,'0' call cout call print db cr,lf,0 ; User-defined exit routine: leave empty if not needed userout: ret ;Get a character from the modem: return in HL mchin: push bc ; <== Insert your own code here ld a,(hbunit) ld c,a ld b,00h rst 08 ld a,e ; <== End of your own code ld l,a ; put in HL ld h,0 or a ; set/clear Z pop bc ret ;Send a character to the modem mchout: ld hl,2 ; get the character add hl,sp ld a,(hl) ; <== Insert your own code here push bc ld e,a ld a,(hbunit) ld c,a ld b,01h rst 08 pop bc ; <== End of your own code ret ; done ;Test for output ready: return TRUE (1) in HL if ok mordy: ; <== Insert your own code here push bc ld a,(hbunit) ld c,a ld b,03h rst 08 ld h,0 ld l,a pop bc ; <== End of your own code ld a,l ; set/clear Z or a ret ;Test for character at modem: return TRUE (1) in HL if so mirdy: ; <== Insert your own code here push bc ld a,(hbunit) ld c,a ld b,02h rst 08 ld h,0 ld l,a pop bc ; <== End of your own code ld a,l ; set/clear Z or a ret ;Send a break to the modem: leave empty if your system can't do it sndbrk: ; <== Insert your own code here ld hl,300 ; wait 300 mS call waithlms ; <== End of your own code ret ; ;Test UART flags for error: return TRUE (1) in HL if error. mdmerr: ; <== Insert your own code here xor a ; not implemented ; <== End of your own code ld a,l ; set/clear Z or a ret ;Turn DTR ON dtron: ; <== Insert your own code here ; <== End of your own code ret ;Turn DTR OFF dtroff: ; <== Insert your own code here ; <== End of your own code ret ;Initialise the uart init: ld hl,2 ; get parameters add hl,sp ex de,hl call getparm ; in HL ld (brate),hl ; baud rate call getparm ld (parity),hl ; parity call getparm ld (data),hl ; data bits (BINARY 7 or 8) call getparm ld (stop),hl ; stop bits (BINARY 1 or 2) ; <== Insert your own code here ; using values below ; don't forget to load mspeed with the ; current brate value if the new rate is ; valid. See table of values below. push bc ; If pcfg is true, attempt to initialize the active ; HBIOS unit. If false, initialization if bypassed with ; the assumption that the RomWBW MODE command was used ; to initialize the port priot to running ZMP. if pcfg call print db 'Initializing device: ',0 call diport ld a,(hbunit) ; get device type ld c,a ld b,06h rst 08 or a ; check if valid jr nz,initerr ld a,(brate) ; get baud rate to set ld c,a ld b,0 ld hl,baudtbl add hl,bc ld a,(hl) ; convert to encoded hbios cp a,-1 jr z,initerr push af ld a,(hbunit) ; get line characteristics ld c,a ld b,05h rst 08 ld a,d ; mask out exisitng and 11100000b ; replace with rate ld d,a pop af or d ld d,a ld b,04h ; set new ld a,(hbunit) ; speed ld c,a rst 08 or a jr nz,initerr ld a,(brate) ; load mspeed with the current brate value if ld (mspeed),a ; the new rate is valid. See table of values below. call print db lf,lf,'Initization completed, device: ',0 call diport jr init_z initerr:call print db lf,lf,'Initization failed, device: ',0 call diport jr init_z diport: ld a,(hbunit) ; Display port diport1:add a,'0' call cout call print db cr,lf,0 ld hl,2 call waithls ret else ;call print ;db cr,lf,'Modem port initialization...',0 ;ld hl,2 ;call waithls ld b,05h ; HBIOS port reset function ld a,(hbunit) ; get active modem port ld c,a ; and put in accum rst 8 ; reset port or a ; check result jr nz,init1 ; if error, handle it ld a,8 ; dummy value for speed ld (mspeed),a ; save it jr init_z ; done init1: ; Handle error return from initialization call print db cr,lf,'Modem port initialization failed!',cr,lf,0 ld hl,2 call waithls jr init_z endif init_z: pop bc ; <== End of your own code ret ;-------------------------------------------------------------------------- stop: dw 1 ; stop bits parity: dw 'N' ; parity data: dw 8 ; data bits brate: dw 7 ; baud rate: ;-------------------------------------------------------------------------- ; ;Values of brate for each baud rate ; ; hb encode baud rate brate ; baudtbl: db -1 ; 110 0 not supported db 2 ; 300 1 db 17 ; 450 2 db 3 ; 600 3 db -1 ; 710 4 not supported db 4 ; 1200 5 db 5 ; 2400 6 db 6 ; 4800 7 db 7 ; 9600 8 db 8 ; 19200 9 db 9 ; 38400 10 db 24 ; 57600 11 db 10 ; 76800 12 ; ; Set the port. ZMP supplies either 0 or 1 as a parameter. You're on your ; own here -- your system is bound to be different from any other! You may ; implement a software switch on all the modem-dependent routines, or perhaps ; you can have one or two centralised routines for accessing the UARTs and ; modify the code from this routine to select one or the other. (Who said ; there was anything wrong with self-modifying code?). If you have only one ; UART port, or if you don't want to go through all the hassles, just have ; this routine returning with no changes made. Note that ZMP calls this ; routine with both values for the port on initialisation. ; ; Only originl ZMP calls setport. MECPARTS variant does not. ; ; We ignore this call. Since we are using a command line parameter ; to specify the desired modem port, it makes no sense. ; setport: ld hl,2 ; get port number add hl,sp ex de,hl call getparm ; in HL (values are 0 and 1) ; <== Insert your own code here ; <== End of your own code ret ; ;**************************************************************************** ;Video terminal sequences: these are for VT-100: Modify as you wish ;Cursor addressing: cursadd: ld hl,2 ; get parameters add hl,sp ex de,hl call getparm ; in HL inc hl ld (row),hl ; row call getparm inc hl ld (col),hl ; column ; push bc call print db esc,'[',0 ld a,(row) ; row first call cursconv ld a,';' call cout ld a,(col) ; same for column call cursconv ld a,'H' call cout pop bc ret ; cursconv: ld b,a xor a ca1: add a,1 daa djnz ca1 ld (num),a and 0f0h jr z,ca2 srl a srl a srl a srl a or '0' call cout ca2: ld a,(num) and 0fh or '0' call cout ret ; row: ds 2 ; row col: ds 2 ; column num: ds 1 ; ;Clear screen: cls: call print db esc,"[H",esc,"[2J",0 ret ; ;Inverse video on: invon: call print db esc,"[7m",0 ret ; ;Inverse video off: invoff: call print db esc,"[m",0 ret ; ;Turn off cursor: hide: call print db esc,'[?25l',0 ret ; ;Turn on cursor: show: call print db esc,'[?25h',0 ret ; ;Save cursor position: savecu: call print db esc,'[7',0 ret ; ;Restore cursor position: rescu: call print db esc,'[8',0 ret ;**************************************************************************** ;Service modem interrupt: mint: ret ; handled in HBIOS ;Initialise interrupt vectors: invec: ret ; ditto ;De-initialise interrupt vectors: dinvec: ret ; ditto ;****************** End of user-defined code ******************************** ; Do not change anything below here. ;Modem character test for 100 ms mrd: push bc ; save bc ld bc,100 ; set limit mrd1: call mirdy ; char at modem? jr nz,mrd2 ; yes, exit ld hl,1 ; else wait 1ms call waithlms dec bc ; loop till done ld a,b or c jr nz,mrd1 ld hl,0 ; none there, result=0 xor a mrd2: pop bc ret ; Inline print routine: destroys A and HL print: ex (sp),hl ; get address of string ploop: ld a,(hl) ; get next inc hl ; bump pointer or a ; done if zero jr z,pdone call cout ; else print jr ploop ; and loop pdone: ex (sp),hl ; restore return address ret ; and quit ; ;Output a character in A to the console ; cout: push bc ; save regs push de push hl ld e,a ; character to E ld c,2 call bdos ; print it pop hl pop de pop bc ret ;Wait seconds wait: ld hl,2 add hl,sp ex de,hl ; get delay size call getparm ; fall thru to.. ;Wait seconds in HL waithls: push hl ; save loop control ld hl,1000 ; 1000ms = 1 second call waithlms pop hl ; restore loop control dec hl ld a,h or l jr nz,waithls ret ;Wait milliseconds mswait: ld hl,2 add hl,sp ex de,hl ; get delay size call getparm ; fall thru to.. ;Wait milliseconds in HL waithlms: ; burn 1000us (1ms) ld a,(cpumhz) ld e,a hlms1: ; burn 1000 t-states ; 50 * 20 = 1000 t-states ld d,50 hlms2: nop ; 4 dec d ; 4 jr nz,hlms2 ; 12 20 dec e jr nz,hlms1 dec hl ld a,h or l jr nz,waithlms ret ;Get next parameter from (de) into hl getparm: ex de,hl ; get address into hl ld e,(hl) ; get lo inc hl ld d,(hl) ; then hi inc hl ; bump for next ex de,hl ; result in hl, address still in de ret ;Get address of user-defined variables getvars: ld hl,uservars ret uservars: dw overdrive ; .OVR etc. drive/user dw overuser if ($ - codebgn) gt ovsize toobig: jp errval ; Overlay too large! endif end