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.
 
 
 
 
 
 

636 lines
18 KiB

TITLE "Write B/P Bios System to system tracks of a disk"
;************************************************************************
;* B P S Y S G E N *
;* Copy B/P Bios based Operating System to system tracks *
;* by Harold F. Bower and Cameron W. Cotrill *
;*----------------------------------------------------------------------*
;* Disassembly: jxl Dec 2024 *
;* public release 1.0 Apr 2025 *
;* see remarks at the end *
;*----------------------------------------------------------------------*
;* LINK with Version 4 libraries: VLIB, Z3LIB, SYSLIB *
;* *
;* A>Z80ASM BPSYSGEN/RS *
;* A>SLRNK BPSYSGEN/N,/A:100,/D:08CD,BPSYSGEN,Z3LIBS/S,SYSLIBS/S,/E *
;************************************************************************
VER EQU 10
REV EQU ' '
DATE MACRO
DEFB '31 Aug 92'
ENDM
CTRLC EQU 03H ; Control-C character
BEL EQU 07H ; Bell character
LF EQU 0AH ; Line Feed character
CR EQU 0DH ; Carriage Return character
CPMBIOS EQU 0 ; CP/M BIOS warm boot (JP)
CPMBDOS EQU 5 ; CP/M BDOS entry point (JP)
CPMFCB EQU 5CH ; CP/M standard FCB #1 (+1 filename, +9 filetype)
CPMFCB2 EQU 6CH ; CP/M standard FCB #2
; From Z3LIB Get..
EXTRN GETNAME, PRTNAME, Z3INIT, WHRENV
; From SYSLIB Get..
EXTRN PUTUD, GETUD, SUA, EPRINT, CRLF, CAPINE, CIN, COUT
;::::: PROGRAM START
ORG 100H
CSEG
BPSYSGEN: JP START ; bypass header
DEFB 'Z3ENV' ; this is a ZCPR3 utility
DEFB 1 ; show external environment
ENVADR: DEFW 0 ; addr of Z3 environment
START: LD HL,(CPMBDOS+1) ; BDOS entry as starting point for Z3ENV search
CALL WHRENV ; get Z3 Environment
LD (ENVADR),HL ; ..store it
CALL Z3INIT ; init ENV ptr for Z3LIB modules
CALL GETNAME ; get actual program name
CALL GETQFLG ; check ENV quiet flag
AND A ; zero means 'verbose' i.e. not quiet
JR NZ,START1 ; ..if quiet mode, skip msg
START0: CALL EPRINT
DEFB 'B/P SYSGEN Utility V',VER/10+'0','.',VER MOD 10 + '0',REV
DATE
DEFB CR,LF,LF
DEFB 0
START1: CALL PUTUD ; currently logged in drive/user
LD HL,RESDISK ; prepare exit with resetting disk system
PUSH HL ; by putting addr of 'RESDISK' on stack
LD HL,(CPMBIOS+1) ; get BIOS entry addr
LD A,8*3 ; move forward to fn #9 SELDSK
CALL ADDHLA
LD DE,BIOSELD ; ptr to target addr
LD BC,8*3 ; bytes to copy (8 JP instructions, 3 bytes each)
LDIR ; ..copy
; Evaluate command line (if invalid parameters, switch to interactive mode)
EVALCMD: LD HL,CPMFCB ; set ptr to standard FCB #1
LD A,(HL) ; get drive
LD (SRCDRV),A ; ..and store it (SRC)
INC HL ; move ptr forard
LD A,(HL) ; get byte/char
CP '/' ; is this a help request ?
JP Z,HELP ; ..if so, jump display help
; ..and quit (addr of exit routine on stack)
; syntax: BPSYSGEN [d:]fn[.ft] [d:]
; drive #1 = source, drive #2 = destination
LD A,(CPMFCB2) ; get first byte of standard FCB #2
LD (DSTDRV),A ; store drive # (DEST)
LD (DSTDR2),A ; ..and a copy (as indicator for cmdline input)
LD B,A ; remember value
LD A,(HL) ; get first char of filename in FCB #1
CP ' ' ; is it <SP> ?
JP NZ,SRCRD0 ; ..if not, jump read sys file
LD A,B ; else, restore char (from FCB #2)
AND A ; is it <NUL> ?
JP NZ,SRCREAD ; ..if not, jump read sys tracks
; else, no source specified in command line
; (switch to interactive mode)
LD A,(SRCDRV) ; get source drive number
LD (DSTDRV),A ; ..and overwrite destination drive number
;::::: SOURCE DRIVE
; interactive mode
SRCINP: CALL EPRINT
DEFB 'Source Drive (CR to skip)? '
DEFB 0
CALL CAPINE ; get user input
CALL CRLF
CP CR ; is it <CR> ?
JP Z,SRCRD1 ; ..if so, skip
SUB 40H ; else, convert ascii to number
LD (SRCDRV),A ; ..and store it
CALL EPRINT
DEFB 'Place source disk in drive '
DEFB 0
LD A,(SRCDRV) ; get source drive number
ADD A,40H ; convert to ascii
CALL COUT ; ..and display it
CALL EPRINT
DEFB ': and press return to continue...'
DEFB 0
SRCINP0: CALL CIN ; get input
CP CTRLC ; is it <Ctrl-C> ?
RET Z ; ..if so, return
CP CR ; <CR> ?
JR NZ,SRCINP0 ; ..if not, loop ask for new input
CALL CRLF
; start reading
SRCREAD: CALL RDTRACK ; read system tracks of source disk
JR SRCRD1 ; ..and skip over
SRCRD0: CALL RDFILE ; read system file
SRCRD1: CALL CHKSYS ; check if a valid system was loaded/read
; (fn _not_ implemented, simply returns)
JP NZ,E$NOSYS ; ..if not, jump error and exit
LD A,(DSTDRV) ; get # of destination disk
AND A
JP NZ,DSTINP0 ; ..if not empty (= zero), jump to continue
; else, fall through and ask user
;::::: DESTINATION DRIVE
; interactive mode
DSTINP: CALL EPRINT
DEFB CR,LF,'Destination Drive (^C quits)? '
DEFB 0
CALL CAPINE ; get user input
CP CTRLC ; is it <Ctrl-C> ?
RET Z ; ..if so, return
SUB 40H ; else, convert ascii to number
LD (DSTDRV),A ; ..and store it
CALL CRLF
DSTINP0: LD A,(DSTDR2) ; get copy of # destination disk
AND A ; check if valid
JR NZ,DSTWRIT ; ..if so, running in command line mode
; ..continue writing to destination immediately
; else, fall through and ask user for input
CALL EPRINT
DEFB 'Place destination disk in drive '
DEFB 0
LD A,(DSTDRV) ; get destination drive number
ADD A,40H ; convert to ascii
CALL COUT ; ..and display it
CALL EPRINT
DEFB ': and press return to continue...'
DEFB 0
DSTINP1: CALL CIN ; get input
CP CTRLC ; is it <Ctrl-C> ?
RET Z ; ..if so, return
CP CR ; <CR> ?
JR NZ,DSTINP1 ; ..if not, loop ask for new input
CALL CRLF
; start writing
; exit through "RET", addr of RESDISK routine is on stack
DSTWRIT: CALL WRTRACK
CALL GETQFLG
AND A ; check if quiet flag is set
RET NZ ; ..if not (= verbose), exit program
LD A,(DSTDR2) ; else, get copy of # dest. disk (indicator cmdline mode)
AND A ; check if valid
RET NZ ; ..if not, exit program
JP DSTINP ; else, loop ask for input
; initiate a reset of disk system when returning to system
RESDISK: LD C,13 ; BDOS fn #13 (reset disk system)
CALL CPMBDOS
JP GETUD ; set Drive/User and let return from there
;::::: HELP SCREEN
HELP: CALL PRGNAME
CALL EPRINT
DEFB ' Places a copy of the operating '
DEFB 'system onto the system',CR,LF
DEFB ' tracks of a drive on the system.',CR,LF,LF
DEFB ' Syntax: '
DEFB 0
CALL PRGNAME
CALL EPRINT
DEFB ' [DIR:[Ufn.Ft]] [D:]',CR,LF,LF
DEFB ' Examples:',CR,LF,LF
DEFB ' '
DEFB 0
CALL PRGNAME
CALL EPRINT
DEFB ' - Execute in Interactive Mode',CR,LF
DEFB ' '
DEFB 0
CALL PRGNAME
CALL EPRINT
DEFB ' A: - Prompt for Source, '
DEFB 'Place System onto A',CR,LF
DEFB ' '
DEFB 0
CALL PRGNAME
CALL EPRINT
DEFB ' B:ZSDOS64.COM - Get System from File, '
DEFB 'Prompt for Drive',CR,LF
DEFB ' '
DEFB 0
CALL PRGNAME
CALL EPRINT
DEFB ' A: B: - Copy System from Drive A '
DEFB 'to Drive B',CR,LF
DEFB ' '
DEFB 0
CALL PRGNAME
CALL EPRINT
DEFB ' // - display this help',CR,LF
DEFB 0
RET
;::::: SUPPORT FUNCTIONS
; get Quiet Flag from Z3 Environment
; in: -
; out: A= Quiet Flag, defaults to A= 0 (not quiet)
GETQFLG: LD HL,(ENVADR) ; get local ENVPTR
LD A,H ; check if invalid (= zero)
OR L
RET Z ; ..if so, return
LD A,40 ; else, move ptr forward
CALL ADDHLA ; to Quiet Flag
LD A,(HL) ; get value
RET ; ..and return
; print program name on CON: device
; (either the actual name, or fallback to default)
; only used by HELP
PRGNAME: LD A,(ENVADR+1) ; get high byte of ENVPTR
OR A ; check if valid (<> zero)
JP NZ,PRTNAME ; ..if so, display actual name
; and let return from there
CALL EPRINT ; else, display default
DEFB 'BPSYSGEN'
DEFB 0
RET
; Read system tracks - source
RDTRACK: LD A,(SRCDRV) ; get source drive
CALL SELDRV ; and select it
JP Z,E$SRC ; ..if error, jump
LD (SRCDPH),HL ; store addr of DPH
LD A,10 ; move forward to DPB addr
CALL ADDHLA ; at DPH+10
LD E,(HL) ; get DPB addr in DE
INC HL
LD D,(HL)
EX DE,HL ; swap regs
LD (SRCDPB),HL ; ..and store DPB addr
LD E,(HL) ; get sectors per track in DE
INC HL ; at DPB+0
LD D,(HL)
LD (SECTTRK),DE ; store value
LD A,12 ; move forward to track offset
CALL ADDHLA ; (beginning of directory) at DPB+13
LD E,(HL) ; get track offset in DE
INC HL
LD D,(HL)
LD A,D ; check upper nybble
AND A ; is it zero ?
JP NZ,RDTRK0 ; ..if not, jump to adjust
OR E ; check lower nybble
JP Z,E$NOSYS ; ..if also zero, jump to error and exit
CP 4 ; check upper limit for # of system tracks
JR C,RDTRK1 ; ..if within boundaries, skip over
RDTRK0: LD DE,2 ; set (default) # of system tracks
RDTRK1: LD B,E ; trk offset in B (counter)
LD DE,(SECTTRK) ; get sect/trk
LD HL,0 ; set initial value
RDTRK2: ADD HL,DE ; multiply by addition
DJNZ RDTRK2 ; loop till done
XOR A ; nullify A
OR H ; check if H is zero
JP NZ,E$NOSYS ; ..if not, jump error and exit
PUSH HL ; save regs
LD BC,0
CALL BIOSTTR ; set track # 0
POP HL ; restore regs
LD C,H ; move # of sectors containing system
LD B,L ; to BC (as counter)
LD DE,0 ; set intial value
LD HL,FILEBUF ; set target addr to file buffer
; (at 0x0900, page-aligned after end of program)
RDTRK3: PUSH DE ; save regs
PUSH BC
PUSH HL
LD HL,(SRCDPH) ; get addr of DPH
LD E,(HL) ; get skew table ptr in DE
INC HL
LD D,(HL)
CALL BIOSTRN ; translate logical sector # in BC
LD B,H ; move physical sector # to BC
LD C,L
CALL BIOSTSE ; ..and set (physical) sector
POP BC ; restore target addr
PUSH BC
CALL BIOSTDM ; set as DMA buffer addr
CALL BIOREAD ; read one sector
OR A ; check for error (A <> 0)
JP NZ,E$READ ; ..if error, jump
POP HL ; restore target addr
LD DE,128 ; increase by 128 bytes (1 sector)
ADD HL,DE
POP BC ; restore regs / clear stack
POP DE
DEC B ; decrease counter
RET Z ; ..if finished, return
INC C
LD A,C
AND 00000011b ; mask lower 2 bits
LD A,'.'
CALL Z,COUT ; display progress every 4 sectors (0.5 kB)
LD A,(SECTTRK)
CP C ; max. # sect/trk reached ?
JR NZ,RDTRK3 ; ..if not, loop
INC DE ; increase trk counter
LD C,0 ; reset sect counter
PUSH DE ; save regs
PUSH BC
PUSH HL
LD B,D ; copy trk # in BC
LD C,E
CALL BIOSTTR ; ..and set track #
POP HL ; restore regs
POP BC
POP DE
JR RDTRK3 ; loop
; Read system file (img) - source
RDFILE: LD A,(ENVADR+1) ; get base addr of ENV
AND A ; check if invalid (= zero)
JR Z,RDFIL0 ; ..if no ENV, skip over
LD A,(CPMFCB+0DH) ; else, get user no from standard FCB #1
CALL SUA ; ..and log in
RDFIL0: LD DE,CPMFCB ; set ptr to standard FCB #1
LD C,15 ; BDOS fn #15 Open File
CALL BDOSSV
JP Z,E$SOPEN
LD HL,32 ; ptr to current record
ADD HL,DE
LD (HL),16 ; set # of current record
; (skip 16 records = 2kB, MOVSYS boot loader code)
LD HL,FILEBUF-128 ; set addr of file buffer (-128 ahead of loop)
RDFIL1: LD A,128 ; move forward by 128 bytes (1 sector)
CALL ADDHLA
EX DE,HL ; swap regs
LD C,26 ; BDOS fn #26 Set DMA Address
CALL BDOSSV
EX DE,HL ; swap regs back
LD C,20 ; BDOS fn #20 Read Sequentially
CALL BDOSSV
DEC A ; A= 1 returned means EOF, so decrease A
JR Z,RDFIL1 ; ..if zero, continue with next sector
LD C,16 ; BDOS fn #16 Close File
JP BDOSSV
; Write system tracks - destination
WRTRACK: LD A,(DSTDRV) ; get destination drive
CALL SELDRV ; and select it
JP Z,E$DEST ; ..if error, jump and exit
LD (DSTDPH),HL ; store addr of DPH
LD A,10 ; move forward to DPB addr
CALL ADDHLA ; at DPH+10
LD E,(HL) ; get DPB addr in DE
INC HL
LD D,(HL)
EX DE,HL ; swap regs
LD (DSTDPB),HL ; ..and store DPB addr
LD E,(HL) ; get sectors per track in DE
INC HL ; at DPB+0
LD D,(HL)
LD (SECTTRK),DE ; store value
LD A,12 ; move forward to track offset
CALL ADDHLA ; (beginning of directory) at DPB+13
LD E,(HL) ; get track offset in DE
INC HL
LD D,(HL)
LD A,D ; check upper nybble
AND A ; is it zero ?
JP NZ,WRTRK0 ; ..if not, jump to adjust
OR E ; check lower nybble
JP Z,E$NOSYS ; ..if also zero, jump to error and exit
CP 4 ; check upper limit for # of system tracks
JR C,WRTRK1 ; ..if within boundaries, skip over
WRTRK0: LD DE,2 ; set (default) # of system tracks
WRTRK1: LD B,E ; trk offset in B (counter)
LD DE,(SECTTRK) ; get sect/trk
LD HL,0 ; set initial value
WRTRK2: ADD HL,DE ; multiply by addition
DJNZ WRTRK2 ; loop till done
XOR A ; nullify A
OR H ; check if H is zero
JP NZ,E$NOSYS ; ..if not, jump error and exit
PUSH HL ; save regs
LD BC,0
CALL BIOSTTR ; set track # 0
POP HL ; save regs
LD C,H ; move # of sectors containing system
LD B,L ; to BC (as counter)
LD DE,0 ; set initial value
LD HL,FILEBUF ; set origin addr (file buffer)
WRTRK3: PUSH DE ; save regs
PUSH BC
PUSH HL
LD HL,(DSTDPH) ; get addr of DPH
LD E,(HL) ; get skew table ptr in DE
INC HL
LD D,(HL)
CALL BIOSTRN ; translate logical sector # in BC
LD B,H ; move physical sector # to BC
LD C,L
CALL BIOSTSE ; ..and set (physical) sector
POP BC ; restore origin addr
PUSH BC
CALL BIOSTDM ; set as DMA buffer addr
LD C,0
CALL BIOWRIT ; write one sector
OR A ; check for error (A <> 0)
JP NZ,E$WRITE ; ..if error, jump
POP HL ; restore origin addr
LD DE,128 ; ..and increase by 128 bytes (1 sector)
ADD HL,DE
POP BC ; restore regs / clear stack
POP DE
DEC B ; decrease counter
JR NZ,WRTRK4 ; ..if not finished, continue
LD C,1 ; else, force write (flush to disk)
JP BIOWRIT ; ..and let return from there
WRTRK4: INC C
LD A,C
AND 00000011b ; mask lower 2 bits
LD A,'.'
CALL Z,COUT ; display progress every 4 sectors (0.5 kB)
LD A,(SECTTRK)
CP C ; max. # sect/trk reached ?
JR NZ,WRTRK3 ; ..if not, loop
INC DE ; increase trk counter
LD C,0 ; reset sect counter
PUSH DE ; save regs
PUSH BC
PUSH HL
LD B,D ; copy trk # in BC
LD C,E
CALL BIOSTTR ; ..and set track
POP HL ; restore regs
POP BC
POP DE
JR WRTRK3 ; loop
; check if a valid B/P Bios was loaded
; *** function not implemented ***
; in: -
; out: Z-Flag set if ok, NZ= error
CHKSYS: XOR A ; always return Z-Flag set
RET
; select disk drive
; in: A= drive number (one-based)
; out: Z-Flag set if error
SELDRV: DEC A ; -1 to comply with CP/M BIOS standards
LD C,A
LD E,0
CALL BIOSELD ; call BIOS fn #9 directly
LD A,H
OR L
RET
; call BDOS saving regs BC, DE, HL
; out: A= 0 and Z-Flag set if not found
BDOSSV: PUSH BC
PUSH DE
PUSH HL
CALL CPMBDOS
INC A ; 0xFF --> 0x00 if not found
POP HL
POP DE
POP BC
RET
; add A to HL (result in HL)
ADDHLA: ADD A,L ; add L
LD L,A ; store result in L
RET NC ; ..if no overflow, return
INC H ; else, increment H
RET
;::::: ERROR MESSAGES
; display msg on CON: then exit with warm boot
E$READ: CALL EPRINT
DEFB BEL,'*** Read error'
DEFB 0
JP EXIT
E$SRC: CALL EPRINT
DEFB BEL,'*** Bad source!'
DEFB 0
JP EXIT
E$WRITE: CALL EPRINT
DEFB BEL,'*** Write error'
DEFB 0
JP EXIT
E$DEST: CALL EPRINT
DEFB BEL,'*** Bad destination!'
DEFB 0
JP EXIT
E$NOSYS: CALL EPRINT
DEFB BEL,'*** No system!'
DEFB 0
JP EXIT
E$SOPEN: CALL EPRINT
DEFB BEL,"*** Can't open source file!"
DEFB 0
;::::: EXIT PROGRAM
EXIT: CALL CRLF
LD HL,0 ; set addr
JP (HL) ; and jump to CP/M WBOOT
;::::: BIOS JUMPS (for direct calls)
; area is filled with actual jumps at runtime
; to call BIOS fn's directly
BIOSELD: JP 0 ; fn #9 SELDSK select disk
BIOSTTR: JP 0 ; fn #10 SETTRK set track
BIOSTSE: JP 0 ; fn #11 SETSEC set sector
BIOSTDM: JP 0 ; fn #12 SETDMA set buffer addr
BIOREAD: JP 0 ; fn #13 READ read one sector
BIOWRIT: JP 0 ; fn #14 WRITE write one sector
BIOLIST: JP 0 ; fn #15 LISTST list status (not used)
BIOSTRN: JP 0 ; fn #16 SECTRN sector translation
;::::: RAM STORAGE (not in DSEG !)
SECTTRK: DEFW 0 ; sectors per track (sect/trk), used for src+dst
SRCDPH: DEFW 0 ; source: addr of Disk Parameter Header (DPH)
SRCDPB: DEFW 0 ; source: addr of Disk Parameter Block (DPB)
DSTDPH: DEFW 0 ; destination: addr of DPH
DSTDPB: DEFW 0 ; destination: addr of DPB
SRCDRV: DEFB 0 ; source: drive # (from standard FCB #1)
DSTDRV: DEFB 0 ; destination drive #
DSTDR2: DEFB 0 ; destination drive # (copy)
; extracted from cmdline, used as indicator for run mode
;:::::::::::::::::::::::::::::::::::::::::::::::::::::
; Z3LIB - 0x0735
; SYSLIB - 0x07e3
; end addr 0x08cc (DSEG Z3+SYS = 4 bytes)
;:::::::::::::::::::::::::::::::::::::::::::::::::::::
; buffer start addr = 0x0900
FILEBUF: EQU $+512-($-BPSYSGEN AND 255)
DSEG
END
;************************************************************************
; Remarks jxl:
; BPSYSGEN.COM, included in available B/P Bios package(s), was dis-
; assembled and extensively commented. Labels are up to seven chars long
; to comply with M-REL standards. However, it is recommended to use SLR
; tools that support labels up to sixteen chars.
; In its current state, the compiled/linked file matches exactly the
; original BPSYSGEN.COM, i.e. no changes to the source were made.
; The program is pretty straightforward. It supports a command line
; mode and an interactive mode. (Code portions for the latter are pretty
; short.) Functionality to check if the running system is valid, was
; not implemented. Since other B/P Bios tools perform such checks, this
; is rather surprising.
; An interesting approach was used to end the program and literally
; return to the system. The address of RESDISK routine is pushed on the
; stack at the very beginning.
;************************************************************************