* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Copyright (C) 1985,1986 AMPRO COMPUTERS, INC. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; Assemble with asm.com or equivalent. There is no Z80 code. ; Revision history: ; ; Ver Date Who Description ; --- ----- --- ------------------------------------------ ; 2.0 F2.05 RJB Fixed foreign format display error. ; Changed to allow foreign drive letter swap. ; ; 1.1 E6.17 RJB Removed HD controller name from display. ; ; 1.0 E2.19 RJB Original version ; VER EQU 20 ; version SWIDTH EQU 80 ; Screen width PWIDTH EQU 132 ; Maximum printer width THIS$MONTH EQU 2 ; This version's month THIS$DAY EQU 05 ; . day THIS$YEAR EQU 86 ; . year MIN$VERSION EQU 31 ; Minimum bios version allowed FALSE EQU 0 TRUE EQU NOT FALSE YES EQU 1 NO EQU 0 ; Character equates CTRLC EQU 3 ; CP/M break key TAB EQU 9 ; Tab character (CTRL-I) ESC EQU 27 ; escape key (CTRL-[) CR EQU 13 ; carriage return (CTRL-M) LF EQU 10 ; line feed (CTRL-J) EOF EQU 26 ; CP/M end-of-file (CTRL-Z) EOS EQU '$' ; CP/M end-of-string (dollar sign) ; bdos equates BDOS EQU 5 ; bdos entry RDCON EQU 10 ; read console buffer CONBUF EQU 80h ; location of console buffer ; other equates NDEV EQU 16 ; number of disk devices supported ORG 0100H JMP START SCREEN$WIDTH: DB SWIDTH-1; 1 less than actual # of characters SLOW$TERM: DB 0 ; Set to delay (ms) for slow terminal CMD$LINE$CHRS: DB 0 ; Count of cmd line characters left CMD$LINE$PTR: DW 0 ; Pointer to next cmd line chr NAME$MSG: DB 0DH,'AMPRO ' NAME: DB 'SWAP' NLEN: EQU $-NAME DB ' Utility',CR,LF DB 'Copyright (C) 1985 AMPRO Computers, Inc.',CR,LF DB 'Version ',VER/10+'0','.',VER MOD 10+'0' DB ' [',THIS$YEAR-80+'@',THIS$MONTH+'0','.' DB THIS$DAY/10+'0',THIS$DAY MOD 10 + '0',']' DB CR,LF,LF,'$',CR HELP$MSG: DB 'Usage: SWAP' DB CR,LF,'$',CR,' ',CR BIOS$PLUS: DB 'This program requires AMPRO bios version ' DB MIN$VERSION/10+'0','.',MIN$VERSION MOD 10+'0' DB ' or later.',CR,LF,'$' DB CR,' ',CR,EOF SIGNON$MSG: DB 'The AMPRO SWAP utility allows you to swap the ' DB 'definition of any two CP/M drive letters. For example, ' DB 'if you have two floppy drives at A: and B:, and two ' DB 'hard disk partitions at F: and G:, you could swap pairs ' DB 'A and F, B and G, C and F, and D and G, ' DB 'to place the hard disk partitions at A: and B:, ' DB 'and the floppy drives at C: and D:.',CR,LF,LF DB 'NOTE: Swapping drive A: has these two restrictions:',CR,LF,LF DB ' (1) The A: drive can only be swapped with an existing ' DB 'drive (one of those listed by SWAP under "current ' DB 'assignments"). ',CR,LF,LF DB ' (2) Be sure to use the AMPRO SYSGEN utility to ' DB 'write a system to the drive which is to become drive ' DB 'A:, before swapping drive A: with any other drive. ',CR,LF,LF DB '$' ; ; Initialize the stack and command line input pointer. ; START: LXI H,0 ; Get old stack pointer DAD SP ; . SHLD OLDSP ; Save it for later LXI SP,STACK ; Put SP at our stack LXI H,0080H ; Copy command line to a 'safe' area LXI D,INBUF ; . MVI B,128 ; . CALL MOVE$BLOCK ; . XCHG ; Set up pointers and count of chrs MOV A,M ; . STA CMD$LINE$CHRS ; . INX H ; . INX H ; . SHLD CMD$LINE$PTR ; . ; ; Display the name and signon message ; TOP$MENU: ORA A ; If cmd line, then no signon JNZ WHICH$BIOS ; . CALL CLEAR$SCREEN ; Otherwise, clear the screen LXI D,NAME$MSG ; Display the name, etc. CALL CENTER$OUTPUT ; . LXI D,SIGNON$MSG ; and the initial message CALL JUSTIFY ; . ; ; Copy the bios to a local area (to make direct bios calls easier) ; and check for version 2.0+. If not version 2.0+, display an ; error message and exit. ; WHICH$BIOS: CALL GET$BIOS$VERS ; Copy the bios to a local area ; ; Check the version of the bios against the minimum version allowed. ; If the bios is not at least the minimum, display an error message ; and exit to the operating system. ; CHECK$B$VERS: LDA LB$VERS ; Get bios version # CPI MIN$VERSION ; Check against minimum version JNC MINBIOS ; At least minimum version . . . LXI D,BIOS$PLUS ; Not minimum, display error message CALL JUSTIFY ; . JMP ALL$DONE ; and exit. ; ; Perform any initialization particular to each version of the Ampro ; bios, if necessary. ; MINBIOS: ; ; Copy the device descriptors of the active devices to a local area ; and sort them. If the count of active devices is zero, display ; an error message and exit. ; DO$ANOTHER: CALL GET$ACTIVE$DEV ; Get the active device descriptors CALL SORT$ACTIVE ; and sort them LXI D,S$WHO$MSG ; Prompt for drive to swap LXI H,S$WHO$OKC ; . CALL PROMPT ; . JZ ALL$DONE ; exit if key pressed STA S$WHO$LETTER ; save letter in second prompt STA S$SW1 ; . CPI '?' ; check for help JZ SHOW$CURRENT ; . show current assignments SUI 'A' ; Convert A-P to 00H-0FH MOV C,A ; save swap drive LXI D,S$WITH$MSG ; Prompt for drive to swap with LXI H,S$WITH$OKC ; . PUSH B ; . CALL PROMPT ; . POP B ; . JZ ALL$DONE ; exit if key pressed CPI '?' ; check for help JZ SHOW$CURRENT ; . show current assignments STA S$SW2 ; SUI 'A' ; convert A-P to 00h-0Fh MOV B,A ; save swap with drive ; ORA A ; Is 'swap with' drive A:? JNZ GS$OTHER ; . MOV A,C ; . if so, make sure other is active JMP GS$TESTFORA ; . GS$OTHER: MOV A,C ; ORA A ; Is 'swap' drive A:? JNZ GS$SWAP$EM ; . MOV A,B ; . if so, make sure other is active GS$TESTFORA: CALL LB$GET$LDTE ; Get logical device table entry MOV A,M ; Zero means not active ORA A ; . JNZ GS$SWAP$EM ; . (not zero means ok to swap) LXI D,GS$NOT$ACTIVE ; Display error message if drive CALL JUSTIFY ; . not active CALL RET$TO$CONT ; Wait for a RETURN XRA A ; Clear command line on an error STA CMD$LINE$CHRS ; . JMP SHOW$CURRENT ; and show 'em what's available GS$SWAP$EM: CALL LB$SWAP$DRV ; otherwise, swap the drives. LXI D,S$SWAPPED ; and tell 'em about it CALL JUSTIFY ; . JMP DO$ANOTHER S$WHO$MSG: DB CR,LF DB 'Which drive do you want to swap ' DB '(A-P, ? for list, to quit): ','$' S$WITH$MSG: DB ' Swap ' S$WHO$LETTER: DB 'x: with which drive ' DB '(A-P, ? for list, to quit): ','$' S$SWAPPED: DB TAB,'<<< Drive ' S$SW1: DB 'x: successfully swapped with drive ' S$SW2: DB 'x: >>>',CR,LF,'$' S$WHO$OKC: S$WITH$OKC: DB '?ABCDEFGHIJKLMNOP',0 GS$NOT$ACTIVE: DB CR,LF,'ERROR -- Cannot swap drive A: with a non-existant ' DB 'drive letter.',CR,LF,LF,'$' SHOW$CURRENT: CALL CLEAR$SCREEN ; Clear the screen LXI D,CURRENT$LTR ; "Current CP/M drive letter assignments" CALL CENTER$OUTPUT ; . CALL DISPLAY$FDEV ; Display floppy devices CALL DO$CRLF ; . CALL DISPLAY$EINF ; Display message about E-drive CALL DO$CRLF ; . CALL DISPLAY$SDEV ; Display SCSI devices CALL DO$CRLF ; . JMP DO$ANOTHER CURRENT$LTR: DB '>>> Current CP/M Drive Letter Assignments <<<' DB CR,LF,LF,'$' ; ; Restore the old stack pointer and exit the program. ; ALL$DONE: JMP LB$WBOOT ; Return via warm boot * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Library routines . . . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * CENTER$OUTPUT: ; ; [DC.20] ; ; Automatically centers the output line(s) based on the line width ; stored in SCREEN$WIDTH. Each line is delimited with CR+LF. This ; routine will return to the caller when it encounters the string ; terminator, '$'. Any additional LF characters after a CR+LF pair ; will be passed through. ; ; Entry: ; DE = Pointer to output string(s), terminated with CR+LF. ; ; Exit: ; The output string(s) are sent to the screen ; ; Modifies: DE ; PUSH PSW ; Save registers PUSH B ; . PUSH H ; . CENTER$NEXT$LN: CALL GET$STRLEN ; Get length to next CR or '$' in B MOV A,B ; Check for zero length ORA A ; . JZ NEXT$DELIM ; If so, output the CR, LF, etc. LDA SCREEN$WIDTH ; Compute offset needed to center line STC ; . SBB B ; . (if there are too many chrs, just JC NO$BLANKS ; . print the line as is . . . ) ANI 0FEh ; Clear least significant bit RRC ; . and rotate to divide by two MVI C,' ' ; Output enough blanks to center line CNZ CON$CHR$AC ; . (only if count is non-zero) NO$BLANKS: MOV A,M ; Save CR for later MVI M,'$' ; Plug position with '$' for cp/m CALL CON$MSG ; . print string function MOV M,A ; Restore saved CR NEXT$DELIM: MOV A,M ; Get character CPI CR ; Print it if CR JZ OUTPUT$DELIM ; . CPI LF ; Print it if LF JZ OUTPUT$DELIM ; . CPI EOS ; Stop processing if EOS ('$') JZ CENTER$DONE ; . XCHG ; Put new pointer in DE JMP CENTER$NEXT$LN ; Go & do the next line OUTPUT$DELIM: CALL CON$CHR ; Output delimiter CPI LF ; Wait 10ms if we have a line feed CZ WAIT ; . INX H ; Point to next chr JMP NEXT$DELIM ; and check that one, also CENTER$DONE: POP H ; Restore registers POP B ; . POP PSW ; . RET ; and return CLEAR$SCREEN: ; ; [E1.28] ; ; This routine clears the screen by calling DO$CRLF 26 times. ; ; Entry: ; None ; ; Exit: ; The screen is cleared ; ; Modifies: ; None ; PUSH PSW ; Save just in case MVI A,26 ; 26 CRLF's C$NEXT$LINE: CALL DO$CRLF ; Next line DCR A ; Done? JNZ C$NEXT$LINE ; Nope. POP PSW ; Restore original AF RET ; and return CON$CHR: ; ; [DC.20] ; ; This routine sends the character in the "A" register to the console ; through the BDOS conout call. ; ; Entry: ; A = character to send ; ; Exit: ; character is sent to the console ; ; Modifies: ; None ; PUSH PSW PUSH B PUSH D PUSH H MVI C,2 MOV E,A CALL BDOS POP H POP D POP B POP PSW RET CON$CHR$AC: ; ; [DC.20] ; ; This routine sends the character in the C register to the console ; the number of times in the A register. ; ; Entry: ; A = Number of times to send character ; C = Character to send ; ; Exit: ; Same ; ; Modifies: ; None ; PUSH PSW ; Save all registers PUSH B ; . PUSH D ; . PUSH H ; . MOV B,A ; Move data to accomodate CP/M MOV A,C ; . NEXT$CHR$OUT: CALL CON$CHR ; Send 1 chr DCR B ; Decrement counter JNZ NEXT$CHR$OUT ; Done? POP H ; Restore all registers POP D ; . POP B ; . POP PSW ; . RET ; and return CONIN$NE$XC: ; ; [E1.28] ; ; Console input, no echo, exit on ctrl-c ; ; Entry: ; none ; ; Exit: ; A = character from console, except for ctrl-c, which causes ; an immediate jump to ALL$DONE ; ; Modifies: ; A ; CALL LB$CONIN CPI CTRLC JZ ALL$DONE RET CON$MSG: ; ; [DC.20] ; ; Console message ; ; Entry: ; DE = pointer to message string, terminated with '$' ; ; Exit: ; message printed on console ; ; Modifies: A, BC ; PUSH PSW ; Save registers PUSH B ; . PUSH D ; . PUSH H ; . MVI C,9 ; BDOS print string command CALL BDOS ; . POP H ; Restore registers POP D ; . POP B ; . POP PSW ; . RET ; and return DISPLAY$FDEV: ; ; [E2.15] ; ; LXI D,D$FDEV$HDR ; Print header CALL CENTER$OUTPUT ; . LXI H,ACTIVE$FDEV ; Get pointer to active floppies LXI B,0004H ; Set starting and maximum # D$NEXT$FDEV: MOV A,M ; Get unit # ORA A ; If zero, skip to next JZ D$BUMP$PTR ; . PUSH H ; Copy output line to output buffer PUSH B ; . LXI H,D$FDEV$LIN ; . LXI D,OUTBUF ; . MVI B,D$FDEV$HLEN ; . CALL MOVE$BLOCK ; . POP B ; . POP H ; . PUSH PSW ; update 'current' drive letter ANI 07FH ; . (mask out 'E-disk' bit) STA OUTBUF+D$CURRENT; . POP PSW ; . ANI 080H ; get 'E-disk' bit back JNZ D$EDISK$OK ; 'E-disk' present, leave E: alone PUSH H ; . PUSH B ; . LXI H,D$EBLANK ; . LXI D,OUTBUF+D$EDISK; . MVI B,4 ; . CALL MOVE$BLOCK ; . POP B ; . POP H ; . D$EDISK$OK: PUSH B ; PUSH H ; MOV A,B ; update floppy device number LXI H,FNAMES ; . LXI B,FNAMES$LEN ; . CALL INDEX$TABLE ; . LXI D,OUTBUF+D$FNAME; . MVI B,FNAMES$LEN ; . CALL MOVE$BLOCK ; . POP H ; . POP B ; . LXI D,OUTBUF ; and output the line CALL CENTER$OUTPUT ; . D$BUMP$PTR: INX H ; Bump pointer to next device, INR B ; increment letter count, MOV A,B ; Compare to maximum, CMP C ; . JNZ D$NEXT$FDEV ; Do another if we're not done. RET D$FDEV$HDR: DB '> FLOPPY DISK ASSIGNMENTS <',CR,LF DB 'CP/M drive ' DB 'Floppy disk',CR,LF DB '------------------------',CR,LF,'$' D$FDEV$LIN: DB ' ' D$CURRENT: EQU $-D$FDEV$LIN DB 'x ' D$EDISK: EQU $-D$FDEV$LIN DB '(' E$LETTER$1 DB '*)' D$EBLANK: DB ' ' D$FNAME: EQU $-D$FDEV$LIN DB ' ' DB CR,LF,'$' D$FDEV$HLEN EQU $-D$FDEV$LIN ; Line length FNAMES: DB 'First ' DB 'Second' DB 'Third ' DB 'Fourth' FNAMES$LEN: EQU 6 ; end of DISPLAY$FDEV DISPLAY$EINF: ; ; [F2.05] ; ; Display message about E-drive labeling. ; LXI D,D$EINFO$MSG ; Print the message CALL CENTER$OUTPUT ; . RET ; D$EINFO$MSG: DB '(' E$LETTER$2: DB '*) = Current foreign format drive letter.' DB CR,LF,LF,'$' ; DISPLAY$SDEV: ; ; [E2.15] ; ; Display the hard disk data in the active device table. ; ; Entry ; Active devices in the ACTIVE$SDEV area ; ; Exit ; Devices are displayed on the console ; ; Modifies ; All ; LDA TOTAL$ACTIVE ; Check how many devices ORA A ; . RZ ; Return if no devices active LXI D,FD$HEADER ; Display table header CALL CENTER$OUTPUT ; . LXI D,LAST$SCSI ; Init local data LXI B,04FFH ; . (fill last info with 0FFh's) CALL FILL$BLOCK ; . XRA A ; Current logical D$GET$UNIT: STA LOGICAL$UNIT ; save current unit # CALL INDEX$ACTIVE ; HL = address of active table entry LXI D,LAST$SCSI ; See if same device MVI B,4 ; Compare for 4 bytes CALL STR$COMP ; . JZ D$ADD$LETTER ; Same device -- add CP/M letter LDAX D ; Not same device, check to see ORA A ; . if last device was 'FF'. If so, JM D$DONT$DISP ; . don't display the line. CALL D$SEND$LINE ; Send line to console D$DONT$DISP: MVI B,4 ; Since DE & HL still point to last & CALL MOVE$BLOCK ; . current, move current to last. PUSH H ; save ptr LXI H,DRIVE$INFO ; Copy template to output buffer LXI D,OUTBUF ; . MVI B,DRIVE$LEN ; . CALL MOVE$BLOCK ; . LXI H,OUTBUF+DRIVE$LETTERS ; Setup CP/M letter ptr SHLD CPM$PTR ; . POP H ; Restore ptr to current PUSH H ; . and save it back MOV A,M ; Plug output buffer w/device info CALL SCSI$TO$BIN ; SCSI address ADI '0' ; . (convert to ascii 0-7) STA OUTBUF+DRIVE$ADDRESS ; . INX H ; . INX H ; . INX H MOV A,M ; Logical unit # RLC ; . (move bits 7-5 to 2-0) RLC ; . RLC ; . ANI 07H ; . (mask out other bits) ADI '0' ; . (convert to ascii 0-7) STA OUTBUF+DRIVE$UNIT ; . POP H ; Restore ptr so we agree D$ADD$LETTER: INX H ; Get CP/M letter for this device INX H ; . INX H ; . INX H ; . INX H ; . MOV B,M ; . LHLD CPM$PTR ; Get ptr to CP/M letter area MOV M,B ; Save CP/M drive letter INX H ; Bump pointer INX H ; . SHLD CPM$PTR ; Save CP/M letter area pointer back D$NEXT$UNIT: LDA TOTAL$ACTIVE ; Get count of active units MOV B,A ; Save for a moment LDA LOGICAL$UNIT ; Get unit we're working on INR A ; Bump to next CMP B ; Are we done? JNZ D$GET$UNIT ; No -- go do another CALL D$SEND$LINE ; Send this line out, too. CALL DO$CRLF ; Send out an extra CR+LF. RET ; all done D$SEND$LINE: PUSH PSW ; Save A & flags and PUSH D ; . D just in case LXI D,OUTBUF ; Send line to console CALL CENTER$OUTPUT ; . D$SEND$DONE: POP D ; POP PSW RET LOGICAL$UNIT: DB 0 ; Current logical unit LAST$SCSI: DB 0 ; Last hard disk scsi address LAST$CTRL: DB 0 ; Last hard disk controller LAST$DTYPE: DB 0 ; Last hard disk drive type LAST$DRIVE: DB 0 ; Last hard disk drive CPM$PTR: DW 0 ; Pointer to next CP/M letter pos FD$HEADER: DB '> HARD DISK ASSIGNMENTS <',CR,LF DB 'CP/M drive(s) ' DB 'Addr Unit #',CR,LF DB '----------------------' DB '-------------',CR,LF,'$' DRIVE$INFO: EQU $ DRIVE$LETTERS: EQU $-DRIVE$INFO DB ' ' DRIVE$CTRL: EQU $-DRIVE$INFO DB ' ' DRIVE$ADDRESS: EQU $-DRIVE$INFO DB '0 ' DRIVE$UNIT: EQU $-DRIVE$INFO DB '0' DB CR,LF,'$' DRIVE$LEN EQU $-DRIVE$INFO DO$CRLF: ; ; [DC.27] ; ; This routine sends a carriage return and a line feed to the terminal, ; and then waits 'SLOW$TERM' ms for a slow terminal to catch up. ; ; Entry: ; none ; ; Exit: ; CR + LF is sent to the screen. ; ; Modifies: ; none ; PUSH PSW ; Save AF MVI A,0Dh ; Send carriage return CALL CON$CHR ; . MVI A,0Ah ; and line feed CALL CON$CHR ; . LDA SLOW$TERM ; Check slow flag ORA A ; . CNZ WAIT ; wait for the s-l-o-w terminals POP PSW ; recover original AF RET ; and return FILL$BLOCK: ; ; [E1.29] ; ; Fills the buffer pointed to by DE with the character in C for ; a length of B bytes (max 256). ; ; Entry: ; B = Length to fill ; C = Character to fill with ; DE = Start of buffer ; ; Exit: ; Buffer filled ; ; Modifies: ; Only buffer area affected ; PUSH B ; Save registers PUSH D ; . XCHG ; Set up M register use F$NEXT: MOV M,C ; Stuff a char INX H ; Bump pointer DCR B ; Bounce counter JNZ F$NEXT ; Do it again if not done XCHG ; We are done. restore old HL POP D ; Restore other registers POP B ; . RET ; and return GET$ACTIVE$DEV: ; ; [E2.15] ; ; Get the active devices. Store the floppy and E-disk descriptions ; in the ACTIVE$FDEV table, and store the SCSI descriptions in the ; ACTIVE$SDEV table. ; ; Entry: ; none ; ; Exit: ; The active device descriptions will be in tables. ; ; ACTIVE$FDEV (4 entries, 1 byte each) format: ; ; +0: Current CP/M letter and "E" disk status. ; (Bit 7 set indicates this device is the "E" disk) ; ; ACTIVE$SDEV (11 entries, 8 bytes each) format: ; ; +0: SCSI Address ; +1: Controller type (0-3) ; +2: Drive type (0-3) ; +3: Drive unit (0-7) -- shifted for use with SCSI commands ; +4: Logical partition (0-7) ; +5: Current CP/M letter ; +6: spare ; +7: spare ; ; Calls: ; LB$GET$LDTE Get logical drive table entry address ; ; Modifies: All ; LXI D,ACTIVE$SDEV ; Clear the table LXI B,ACTIVE$TLEN SHL 8 ; CALL FILL$BLOCK XCHG ; SHLD NEXT$POS ; Save pointer for later XRA A ; Clear count of active SCSI devices STA TOTAL$ACTIVE ; . G$GET$INFO: PUSH PSW ; Save current logical unit # CALL LB$GET$LDTE ; Get logical drive table entry addr MOV A,M ; If zero, non-active device ORA A ; . JZ G$NEXT$DEV ; . CPI 8 ; Only driver codes 0-7 supported JP G$NEXT$DEV ; . LXI D,OUTBUF ; Clear scratch area LXI B,0800H ; Fill with 00h for length of 8 bytes CALL FILL$BLOCK ; . XCHG ; Save pointer to drive entry LXI H,G$EXE$TBL ; JMP GO$TABLE ; Jump to proper routine G$EXE$TBL: DW G$NEXT$DEV ; Driver code = 00.html DW G$FLOPPY ; Driver code = 01.html DW G$EDISK ; Driver code = 02.html DW G$HARD ; Driver code = 03.html DW G$NEXT$DEV ; Driver code = 04.html DW G$NEXT$DEV ; Driver code = 05.html DW G$NEXT$DEV ; Driver code = 06.html DW G$NEXT$DEV ; Driver code = 07.html G$F$DRIVE: ; Convert floppy drive to ptr INX H ; Get byte MOV A,M ; Floppy drive byte ANI 03h ; Isolate drive bits LXI H,ACTIVE$FDEV ; Compute table addr MOV C,A ; . MVI B,0 ; . DAD B ; . RET ; and return G$FLOPPY: XCHG ; Get pointer to drive entry back CALL G$F$DRIVE ; Convert floppy drive POP PSW ; Get logical unit PUSH PSW ; . ADI 'A' ; Convert 0-15 to A-P ORA M ; Or in E-disk, if present MOV M,A ; Save new disk letter JMP G$NEXT$DEV ; Get next device info G$EDISK: CALL LB$GETEDSK ; Get pointer to E-disk parameters LXI B,15 ; Bump pointer to E-disk drive DAD B ; . DCX H ; Decrement ptr because next routine bumps it CALL G$F$DRIVE ; Convert floppy drive MOV A,M ; Set high bit for E disk ORI 80h ; . MOV M,A ; . POP PSW ; Get drive letter PUSH PSW ; Save it back ADI 'A' ; Convert 0-15 to A-P STA E$LETTER$1 ; Save in '(*)' location STA E$LETTER$2 ; and in description JMP G$NEXT$DEV ; Get next device info G$HARD: XCHG ; Get pointer to drive entry back INX H ; (+1) Controller, Drive, Partition MOV A,M ; Get controller (0-3) RRC ; . RRC ; . ANI 03H ; . STA CTRL$TYPE ; . MOV A,M ; Get drive type (0-3) ANI 03H ; . STA DRV$TYPE ; . MOV A,M ; Get partition ANI 0F0H ; . STA PARTITION ; . INX H ; (+2) Unit # MOV A,M ; . ANI 0E0H ; . STA DRV$LUN ; . INX H ; (+3) SCSI address MOV A,M ; . STA SCSI$ADDR ; . POP PSW ; PUSH PSW ; Get CP/M letter ADI 'A' ; . STA CPM$LETTER ; . LHLD NEXT$POS ; Get next buffer position LXI D,SCSI$ADDR ; . and current scratch area XCHG ; Now DE=buffer, HL=scratch MVI B,8 ; Save data CALL MOVE$BLOCK ; . LXI H,8 ; Update buffer pointer DAD D ; . SHLD NEXT$POS ; . LXI H,TOTAL$ACTIVE ; Bump count of SCSI devices INR M ; . G$NEXT$DEV: POP PSW ; Get logical back INR A ; Bump to next logical CPI 16 ; Do maximum of 16 logicals JNZ G$GET$INFO ; Not done -- go get more RET DS 16-($ MOD 16) ACTIVE$SDEV: DS 12*8 ; SCSI device info ACTIVE$FDEV: DB 'A','B' ; Floppy CP/M letters DB 'C','D' ; . ACTIVE$TLEN: EQU $-ACTIVE$SDEV ; Active table length ACTIVE$EOT: DB 0,0,0,0 ; End of table, spares TOTAL$ACTIVE: DB 0 ; # of active SCSI devices NEXT$POS: DW 0 ; Next SCSI buffer position SCSI$ADDR: DB 0 ; SCSI Address (0-7) CTRL$TYPE: DB 0 ; Controller type (0-3) DRV$TYPE: DB 0 ; Drive type (0-3) DRV$LUN: DB 0 ; Drive logical unit # (0-7) PARTITION: DB 0 ; Drive partition (0-7) CPM$LETTER: DB 0 ; CP/M letter (0-15) UNIT: DB 0 ; Unit number (1-8) SPARE: DB 0 ; Spare ; end of GET$ACTIVE$DEV routine GET$BIOS$VERS: ; ; [DC.20] ; ; Get bios version -- Copies the current BIOS jump tables (starting ; at warm boot) to a local area for ease of utility access. If the ; BIOS is version 2.0 or greater, the secondary jump table is copied ; also. ; ; Entry: ; none ; ; Exit: ; Z = bios 1.0 - 1.4 (floppy only bios) ; NZ = bios 2.0 or greater (floppy & fixed disk bios) ; ; Modifies: All registers ; LHLD 1 ; Get start of bios jump table LXI D,LB$BIOS$TBL ; Move bios to local storage MVI B,LB$LEN ; . (length of bios area) CALL MOVE$BLOCK ; . (move routine) MVI A,0 ; Test CP/M version CALL LB$GETNXT ; Get next jump table STA LB$VERS ; Save bios version INX H ; See if HL is 0FFFFh MOV A,H ; . ORA L ; . RZ ; If so, then old version DCX H ; Fix HL as it has the table addr LXI D,LB$XTBL ; Move extra table to local storage MVI B,LB$XLEN ; . (length of extra table) CALL MOVE$BLOCK ; . (move routine) MVI A,0FFH ; Set NZ to indicate bios ORA A ; ... version 2.1+ RET ; ... and return. GET$HL$PTR: ; ; [DC.20] ; ; Gets the pointer pointed to by HL and puts it in HL ; ; Entry: ; HL = pointer to put in HL ; ; Exit: ; HL = pointer ; ; Modifies: none ; PUSH PSW ; Save A register MOV A,M ; Get low byte of pointer INX H ; . MOV H,M ; Get high byte of pointer MOV L,A ; Pointer is now together POP PSW ; Restore A register RET ; and return GET$STRLEN ; Searches the string pointed to by HL and returns the string length ; to the next carriage return. The length is returned in B. PUSH D ; Save start of string MVI B,0 ; Clear counter TRY$NEXT$CHR: LDAX D ; Get character CPI CR ; Is it CR? JZ EOS$FOUND ; . CPI '$' ; Is it '$'? JZ EOS$FOUND ; . INR B ; No -- increment count and INX D ; . point to the next character JMP TRY$NEXT$CHR ; . EOS$FOUND: POP H ; CR or '$' found, recall orig ptr XCHG ; DE=orginial, HL=current RET ; and return GO$TABLE: ; ; [E2.05] ; ; Jump to a routine based on a table of pointers ; ; Entry: ; A = index into table ; HL = table base address ; ; Exit: ; Routine at (A*2)+HL is executed ; ; Modifies: ; B, HL ; LXI B,2 ; Compute offset to table of routines CALL INDEX$TABLE ; . CALL GET$HL$PTR ; . PCHL ; Jump to proper routine INDEX$ACTIVE: ; ; [E2.10] ; ; Compute index into active table ; ; Entry: ; A = Table entry number (00h-0fh) ; ; Exit: ; HL = Address of table entry ; ; Modifies: ; PSW, DE, HL ; LXI B,8 ; Length of active device entry LXI H,ACTIVE$SDEV ; Table base address JMP INDEX$TABLE ; Return through INDEX$TABLE INDEX$TABLE: ; ; [E1.30] ; ; Computes offset to table given base address, entry length, and entry ; requested. ; ; Entry: ; A = entry # ; BC = table entry length ; HL = base address ; ; Exit: ; HL = address to entry ; ; Modifies: ; A, BC, HL ; ORA A ; Set up flags for first check I$TBL$ADD: RZ ; If A=0, we're done DAD B ; Otherwise add length to base, DCR A ; . decrement counter, JMP I$TBL$ADD ; . and check again. IS$IT$OK: ; ; [E1.28] ; ; Check the character in A against the list of "OK" chrs pointed ; to by HL. The value 0FFH is ignored in the list of "OK" chrs ; and may be used as a place holder. ; ; Entry: ; A = character to check ; HL = pointer to list of "OK" characters ; ; Exit: ; A = original character if ok, 0ffh if not in list ; B = position of character in list ; ; Modifies: ; BC ; PUSH H ; Save original "OK" pointer MOV C,A ; Save chr to check against MVI B,0 ; Clear counter CPI ESC ; If chr is JZ I$CHR$OK ; . then automatically ok INR B ; . otherwise start counting at 1 I$CHK$NEXT: MOV A,M ; Get chr to check against ORA A ; End of table? JNZ I$NOT$EOT ; No, check chr DCR A ; Decrement to get 0ffh MOV B,A ; Stuff for later move JMP I$CHR$OK ; And exit I$NOT$EOT: CPI 0FFH ; Place holder character? JZ I$NEXT$PTR ; . Yes, bump pointers CMP C ; Chrs match? JZ I$CHR$OK ; . Yes, return I$NEXT$PTR: INX H ; . No, bump pointer INR B ; . . and bump counter JMP I$CHK$NEXT ; . . and check next chr I$CHR$OK: MOV A,B ; Set status based on ORA A ; . position counter MOV A,C ; Get user chr back I$DONE: POP H ; and original "OK" pointer RET ; and return JUSTIFY: ; ; [E1.30] ; ; This routine will send a data stream to the console, with each line ; justified based on the SCREEN$WIDTH value. The stream must terminate ; with the CP/M end of string character ($) and may contain imbedded ; CR,LF pairs to separate paragraphs. ; ; NOTE: To insure proper operation, the LF character should only follow ; a CR character or another LF character, as the CR character is used to ; flush the current line without justification. ; ; Two entry points are provided: ; JUSTIFY Justify output, flush right ; JUSTIFY$RAGGED Justify output, ragged right ; ; As of E1.30, the flush right routine was not installed, so either ; entry point will provide the same results. ; ; Entry: ; DE = pointer to line(s) to output ; ; Exit: ; The data is sent to the screen. ; ; Modifies: All registers ; MVI A,80H ; Set flush right mode JMP E$JUSTIFY ; Jump to routine entry JUSTIFY$RAGGED: MVI A,00H ; Set ragged right mode E$JUSTIFY: STA J$MODE ; Save mode byte MVI A,'$' ; Mark start of buffer STA OUTBUF-1 ; . XCHG ; DE is usually print source J$NEXT$LINE: XRA A ; Clear character counter STA BLANK$LEN ; . MOV B,A ; . LXI D,OUTBUF ; Set up buffer pointer J$CHECK$CHR: MOV A,M ; Get character STAX D ; Save in output buffer CPI CR ; CR? CZ J$FLUSH$LINE ; . Yes, flush output line, CZ J$LITERAL ; . . output CR & bump ptr JZ J$NEXT$LINE ; . . and do another. CPI LF ; LF? CZ J$LITERAL ; . Yes, output LF & bump ptr JZ J$CHECK$CHR ; . . and check next chr. CPI '$' ; End of string? CZ J$FLUSH$LINE ; . Yes, flush output line, RZ ; . . and return to caller CPI ' ' ; Blank? JNZ J$NOT$A$BLANK ; . No, don't save position SHLD BLANK$POS ; Save position for later XCHG ; and save corresponding position SHLD OUTBUF$BLANK ; . of the blank we just saved XCHG ; . in the output buffer MOV A,B ; . STA BLANK$LEN ; Save current length also J$NOT$A$BLANK: INR B ; Increment counter INX H ; . and pointer INX D ; . and output pointer LDA SCREEN$WIDTH ; Compare counter against screen width SUB B ; . JP J$CHECK$CHR ; And continue checking if not past end ; ; Screen width exceeded, send this line to the screen. ; LHLD OUTBUF$BLANK ; Get pos of last blank in output buf MVI M,'$' ; and plug with eos ('$') LDA J$MODE ; Justify right edge only if the CM J$ADD$BLANKS ; . right-justify flag is non-zero CALL J$SEND$BUFFER ; Output the line to the screen CALL DO$CRLF ; and a CR / LF LHLD BLANK$POS ; Get pointer to where we left off J$SKIP$BLANKS: INX H ; Bump pointer past blank(s) MOV A,M ; . CPI ' ' ; . JZ J$SKIP$BLANKS ; . JMP J$NEXT$LINE ; and check next segment J$FLUSH$LINE: ; Flush line when CR or EOS encountered PUSH PSW ; Save chr & zero flag MVI A,'$' ; Plug current position with EOS ($) STAX D ; . CALL J$SEND$BUFFER ; Send this line of data POP PSW ; Restore chr & zero flag RET ; and return J$LITERAL: ; Send the chr in A PUSH PSW ; Save zero flag CALL CON$CHR ; Console chr out through CP/M INX H ; Bump chr pointer POP PSW ; Restore zero flag RET J$ADD$BLANKS: RET ; At a later time, this routine will justify ; the right margin by inserting extra blanks ; in the output line. J$SEND$BUFFER: MOV A,B ; If line to output is of zero length, ORA A ; . then don't output the line. RZ ; . LXI D,OUTBUF ; Get address of output buffer CALL CON$MSG ; and call our print message routine RET ; return J$MODE DB 0 ; Current right justify mode BLANK$POS DW 0 ; Last blank on this line OUTBUF$BLANK DW 0 ; Last blank in the output buffer BLANK$LEN DB 0 ; Length of line to the blank ; NOTE: OUTBUF is defined to be after the stack and before the heap. ; end of justify$output data area MOVE$BLOCK: ; ; [DC.20] ; ; Move a block of memory (up to 256 bytes) ; ; Entry: ; HL = source ; DE = destination ; B = count ; Exit: ; data moved ; ; Modifies: ; none ; PUSH PSW ; Save registers PUSH B ; . PUSH D ; . PUSH H ; . M$NEXT$CHR: MOV A,M ; Get next byte, STAX D ; Save in destination. INX H ; Increment pointer to source. INX D ; Increment pointer to dest. DCR B ; Decrement counter. JNZ M$NEXT$CHR ; Loop back if we're not done. POP H ; Restore registers POP D ; . POP B ; . POP PSW ; . RET ; No more to do -- return. PROMPT: ; ; [E2.19] ; ; Prompt the user or the command line for input. ; ; Two entry points are provided: ; PROMPT standard entry, CRLF after chr from user ; PROMPT$NOLF special entry, No CRLF after chr from user ; ; When the command line is used for input, the following characters ; are translated to new values or new functions: ; ; Character New character or new function ; -------------- ------------------------------------ ; (space) Ignored ; , (comma) key ; . (period) key ; @ (at-sign) repeat existing command line ; _ (underscore) Prompt and get character from user ; ; Entry: ; DE = pointer to prompt string ; HL = pointer to list of valid chars (terminated with 00H) ; ; Exit: ; A = char from the user ; B = position of this character (0, 1, 2, ... n) ; ; Z = char was the escape key ; NZ = char was not the escape key ; ; Modifies: ; A, BC ; MVI A,01H ; Set CRLF after chr JMP E$PROMPT ; Jump to entry point PROMPT$NOLF: MVI A,00H ; Set no CRLF after chr E$PROMPT: STA PROMPT$MODE ; Save prompt mode flag RE$PROMPT: LDA CMD$LINE$CHRS ; Are there any characters left from ORA A ; . the command line? JZ P$DISP ; No -- display & get chr from bios PUSH H ; Yes, save oklist pointer LHLD CMD$LINE$PTR ; and get chr from command line DCR A ; . Reduce count of chrs by one STA CMD$LINE$CHRS ; . . MOV A,M ; . Get command line character INX H ; . Increment pointer SHLD CMD$LINE$PTR ; . . POP H ; . Restore oklist pointer CPI '@' ; Repeat existing cmd line? JNZ P$NOT$REPEAT ; . (check other chrs if not) PUSH H ; . Save oklist pointer LXI H,INBUF ; . Set command line pointer back INX H ; . . to the beginning SHLD CMD$LINE$PTR ; . . POP H ; . Restore oklist pointer MVI A,07FH ; . Set the count of chars to 127 STA CMD$LINE$CHRS ; . . (the most it could be) JMP RE$PROMPT ; . and get the next character P$NOT$REPEAT: CPI ' ' ; Ignore spaces JZ RE$PROMPT ; . CPI ',' ; Change ',' to CR JNZ P$NOT$COMMA ; . MVI A,CR ; . P$NOT$COMMA: CPI '.' ; Change '.' to ESC JNZ P$NOT$DOT ; . MVI A,ESC ; . P$NOT$DOT: CPI '_' ; Underline means prompt & get chr JZ P$DISP ; . from bios anyway (user input) CALL TO$UPPER ; Convert the chr to upper case CALL IS$IT$OK ; Check the chr against the ok list RP ; If ok, return XRA A ; Otherwise, cancel the cmd line STA CMD$LINE$CHRS ; . buffer and fall through to p$disp P$DISP: PUSH H ; Save pointer to ok-chrs CALL JUSTIFY ; and call justify routine POP H ; . P$TRY$AGAIN: CALL CONIN$NE$XC ; Console input, no echo, except ^C CALL TO$UPPER ; Convert the chr to upper case CALL IS$IT$OK ; If the character is not "OK" JM P$TRY$AGAIN ; . get another CNZ CON$CHR ; . otherwise display it PUSH PSW ; Check mode flag in case we need LDA PROMPT$MODE ; . to send a CR+LF after the ANI 01H ; . user's input CNZ DO$CRLF ; . POP PSW ; . RET ; and return PROMPT$MODE: DB 0 ; Prompt mode flag RET$TO$CONT: ; ; [E2.19] ; ; Prompts and waits for the RETURN key to be pressed. ; ; Entry: ; none ; ; Exit: ; Display message and wait for a RETURN key. ; ; Modifies: ; all ; LXI D,RTC$MSG ; Press RETURN to continue ... CALL CENTER$OUTPUT ; . LXI D,NO$MSG ; . LXI H,RTC$OKC ; . CALL PROMPT ; . [ CR ] RET RTC$MSG: DB 'Press the RETURN key to continue ...' NO$MSG: DB '$' RTC$OKC: DB CR,0 ; end of RET$TO$CONT routine SCSI$TO$BIN: ; ; [DC.27] ; ; Converts SCSI address to binary 0-7 ; ; Entry: ; A = SCSI address to convert ; ; Exit: ; A = converted address (0-7) [0FFH = error] ; ; Modifies: B ; MVI B,0FFH ; Set up B register for possible error ORA A ; If A=0, error JZ SCSI$CONVERTED ; . SCSI$NEXT$BIT: INR B ; Increment count RRC ; Shift address right 1 bit JNC SCSI$NEXT$BIT ; Bit shifted to cary means we're done SCSI$CONVERTED: MOV A,B ; Move converted addr to A RET SORT$ACTIVE: ; ; [E2.21] ; ; Sort active device table entries ; ; Entry: ; none ; ; Exit: ; Active device table is sorted. ; ; Modifies: ; All ; LDA TOTAL$ACTIVE ; Get # of items to sort CPI 02 ; Less than two items? RM ; . yes -- no sort necessary S$NEXT$BLOCK: ; . STA SORT$MAX ; Save # to sort LXI B,0001H ; Setup initial compare pointers S$NEXT$ELEM: PUSH B ; Save compare pointers MOV A,C ; Convert pointers to address CALL INDEX$ACTIVE ; . in DE and HL PUSH H ; . MOV A,B ; . CALL INDEX$ACTIVE ; . POP D ; . MVI B,8 ; Compare length = 8 chrs CALL STR$COMP ; Compare string in [DE] to [HL] MVI B,8 ; Swap length = 8 chrs CM STR$SWAP ; Swap if first is less than second POP B ; Get pointers back INR B ; Point to next item INR C ; LDA SORT$MAX ; Compare with max CMP C ; . JNZ S$NEXT$ELEM ; Not done with this pass ... DCR A ; Make sort limit one smaller CPI 03H ; Done if only 2 elements JP S$NEXT$BLOCK ; Not done if > 2 elements RET SORTMAX: DW 0 ; Sort max range (0-255 elements) STR$COMP: ; ; [E1.18] ; ; Compare two strings ; ; Entry: ; HL = source ; DE = destination ; B = count ; Exit: ; Z = two strings equal ; M = source < dest ; P = source >= dest ; ; Modifies: all ; PUSH D PUSH H NEXT$COMPARE: MOV C,M INX H LDAX D INX D CMP C JNZ NOT$EQUAL DCR B JNZ NEXT$COMPARE NOT$EQUAL: POP H POP D RET STR$SWAP: ; Swap two strings ; ; [E1.18] ; ; Entry: ; HL = source ; DE = destination ; B = count ; Exit: ; data moved ; ; Modifies: all ; MOV C,M LDAX D MOV M,A MOV A,C STAX D INX H INX D DCR B JNZ STR$SWAP RET TO$UPPER: ; ; [E1.08] ; ; Convert the character in A to upper case. ; ; Entry: ; A = character to convert ; ; Exit: ; A = upper case character (if alpha) ; ; Modifies: ; A ; CPI 'z'+1 ; Convert to upper case JP UPPER$ALREADY ; . CPI 'a' ; . JM UPPER$ALREADY ; . ANI 5FH ; . UPPER$ALREADY: RET ; and return WAIT: ; Wait A ms ; ; Entry: ; milliseconds in A ; ; Exit: ; time waited ; ; Modifies: A ; PUSH PSW MVI A,221 WAIT$2: DCR A JNZ WAIT$2 POP PSW DCR A JNZ WAIT RET * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * Data area . . . * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * ; ; All data after this point is after the end of the program ; ; Replicated BIOS to make direct calls easier . . . LB$BIOS$TBL: LB$WBOOT DS 3 ; Warm boot LB$CONST DS 3 ; Console status LB$CONIN DS 3 ; Console input LB$CONOUT DS 3 ; Console output LB$LISTOUT DS 3 ; List output LB$PUNCH DS 3 ; Punch output LB$READER DS 3 ; Reader input LB$HOMDSK DS 3 ; Home disk (move to track 00) LB$SELDSK DS 3 ; Select disk drive LB$SETTRK DS 3 ; Select track number LB$SETSEC DS 3 ; Select sector number LB$SETDMA DS 3 ; Set DMA address LB$DSKREAD DS 3 ; Disk read LB$DSKWRITE DS 3 ; Disk write LB$LISTST DS 3 ; List status LB$SECTRN DS 3 ; Sector translate routine ; AMPRO-specific BIOS calls LB$GETNXT DS 3 ; Get bios ver & next tbl address LB$GETEDSK DS 3 ; Get pointer to E-disk storage LB$IOINIT DS 3 ; Set new I/O parameters LB$SCSIDRV DS 3 ; SCSI direct driver LB$LEN EQU $-LB$BIOS$TBL ; Length of bios table (20 entries) LB$XTBL: ; 'Extra' table definitions ... LB$SWAP$DRV DS 3 ; Swap two logical drives LB$GET$WDP DS 3 ; Set/get win drive parameters LB$PHYTAB DS 3 ; Set/get phytab access LB$GET$LDTE DS 3 ; Get logical device table entry LB$RESERVED DS 3 ; Reserved entry LB$XLEN EQU $-LB$XTBL ; Length of extra table (5 entries) LB$VERS DS 1 ; Bios version number ; ; Stack, I/O buffers, HEAP ; OLDSP: DS 2 ; old stack pointer DS 63 ; STACK: DS 1 ; 31-level stack INBUF: DS 128 ; Command line input buffer INBUFL: EQU $-INBUF ; Input buffer length OBPLUG: DS 1 ; Start of outbuf ('$') OUTBUF: DS PWIDTH ; Output buffer OBUFL: EQU PWIDTH ; Output buffer length HEAP: EQU $ ; Next available area for data END