TITLE   RAMDRIV5.MAC
;
;***********************************************************************
;
;       RAMDRIV - A program that permits extended memory to function as
;       a fast disk drive.  This program does not require modification
;       of the existing BIOS.
;
;       Copyright 1982, by
;       Herbert B. Shore
;       Department of Physics
;       San Diego State University
;       San Diego, CA 92182
;       (619) 265-6159
;
;       Released to the public domain for non-commercial use.
;
;       RAMDRIV has been extensively modified by Paul J. Gans.  Changes
;       are copyright (c) by Paul J. Gans, Department of Chemistry, New
;       York University, New York, NY  10003 (212) 598-2515
;
;       New or modified features include:
;
;       For RAMDRIV5:
;
;       1.  Code has been converted to Z80 code throughout.
;       2.  The target assembler is now Microsoft's M80.
;       3.  The code is now self-loading.  The original RAMLD program is
;               no longer required.
;       4.  It is no longer required to have contiguous memory banks of
;               the same size.  Available memory is controlled by the
;               BLEN table described below.
;       5.  Track size is now a variable multiple of 1K and can be
;               changed through an equate.
;       6.  The original ALV parameter has been dropped in favor of
;               TRAKLEN, the number of kilobytes per track.
;       7.  The DRAM parameter has been dropped.  Extended (bank)
;               addresses are now contained in the BLEN table.
;       8.  The program now uses both the directory buffer and the host
;               buffer in the local BIOS instead of its own buffer.
;               This requires that the user know the addresses of both
;               of these buffers in his or her BIOS.  To accomodate
;               those who choose not to use the BIOS buffers, two DS
;               definitions are required a few lines after the label
;               WBOOT1.  The proper spot is commented in the code
;               below.
;
;
;       INSTALLATION of RAMDRIV5:
;
;       RAMDRIV assumes that the user's system has "extra" memory
;       available beyond the normal 64K maximum directly addressable
;       in Z80 systems.  This memory may be either "bank select" or
;       S-100 extended addressing.
;
;       RAMDRIV is installed by setting a number of equates and by
;       properly configuring the BLEN table located near the end of
;       the code.  After the proper values have been set by equates and
;       the BLEN table has been set up, the code should be assembled
;       using M80.  The commands for so doing are:
;
;       >M80 =RAMDRIV5/L        <== /L optionally produces a .PRN file
;       >L80 /P:100,RAMDRIV5,RAMDRIV5/N/E
;
;       The equates for DIRBUF and RAMBUF must be set to reflect the
;       location of these buffers in the user's BIOS.  If this is not
;       feasible, see the comments at the label WBOOT1.
;
;       Equates that must be set are defined and explained below.
;
;       In normal use the user must also configure the BLEN table for
;       his or her system and recompile.  There is one BLEN table entry
;       for each bank of memory to be used by RAMDRIV.  It is assumed
;       that each of these banks starts at address 0000 and proceeds
;       upward to some value without any holes.  It is also assumed that
;       at least the upper 1K of system memory is global, thus no bank
;       can exceed 63K.
;
;       A BLEN table entry consists of two bytes for each bank of memory
;       to be used by RAMDRIV.  The first byte is the number of kilo-
;       bytes available for RAMDRIV in that bank.  This value is placed
;       into the BLEN table as the value divided by TRAKLEN.  The actual
;       number of kilobytes of that bank used by RAMDRIV is the integral
;       part of the division.  For instance a 30K bank of memory would
;       be indicated to the system as 30/TRAKLEN.
;
;       The second byte in the BLEN table entry is the extended (or
;       bank) address byte for that bank of memory.  There is one such
;       entry pair for each bank.  The table is terminated by a single
;       zero byte.
;
;       Thus a system with 20K at a bank addressed as 0DEH and 8K at a
;       bank addressed as 70H could have a BLEN table as:
;
;               BLEN:   DB      20/TRAKLN,0DEH
;                       DB      08/TRAKLN,70H
;                       DB      0
;
;       while the same system in which the user wanted to reserve 4K on
;       the board at 0DEH for some other purpose could have a BLEN table
;       as:
;
;               BLEN:   DB      16/TRAKLN,0DEH
;                       DB      08/TRAKLN,70H
;                       DB      0
;
;
;************************************************************************
;
;  The code and tables in this file will reside in high memory, above the
;  existing BIOS.  The system is self-loading.  Typing RAMDRIV3 from the
;  console is sufficient to initiate RAMDRIV3.
;
       .Z80
;
FALSE   EQU     0
TRUE    EQU     NOT FALSE
;
;  The following equates are system dependent.
;
;  DIRBUF:  This is the address of the CP/M directory buffer in the
;    user's BIOS.  CP/M 2.x systems are required to have such a buffer
;    available.  The address will vary in different implementations.
;
;  RAMBUF:  This is the address of the CP/M host buffer in the user's
;    BIOS.  In a normal CP/M 2.x system this buffer will be at least 1K
;    long.  The address will vary in different implementations.
;
;  PANEL:  If you have an (IMSAI-type) front panel, set PANEL=TRUE.  The
;    lights will display the extended address, simulating the LED on the
;    door of the disk drive.  Flipping switch D0 up will "write protect"
;    the RAM drive.
;
;  RAMDRIV:  Set to a memory location above BIOS.  Less than 1K of
;    GLOBAL memory is required at this location; i.e. the same block
;    of memory must be accessible independent of the setting of the ex-
;    tended address bus or any bank byte.
;
;  PRAM:  The extended address of regular program RAM.  Usually this is
;    00H, but need not be.
;
;  XPORT:  The output port that sets the extended address bus or bank
;    byte.
;
;  DNAME:  The alphabetic designation of the RAM drive.
;
;  LIGHTS:  The output port for front panel lights.  This is signif-
;    icant only if PANEL is TRUE.
;
;  SWITCH:  The input port for front panel switches.  This is signif-
;    icant only if PANEL is TRUE.
;
;  TRAKLN:  The number of kilobytes per track.  This must be a minimum
;    of 1K and a maximum no greater than the smallest bank of memory to
;    be made available to RAMDRIV.  A good value to use is the greatest
;    common divisor of each of the memory banks to be used by RAMDRIV.
;
;  DIRECT:  The number of directory entries permitted on the RAM drive.
;    The number specified must be a multiple of 32.  I used 32 to leave
;    the maximum space for program files.
;
       .Z80
;
DIRBUF  EQU     0F77FH          ; CP/M BIOS directory buffer address
RAMBUF  EQU     0F7FFH          ; CP/M BIOS host buffer
PANEL   EQU     FALSE           ;IMSAI FRONT PANEL
RAMDRIV EQU     0FC00H          ;START OF PROGRAM.
PRAM    EQU     00H             ;EXTENDED ADDR. OF MAIN RAM
XPORT   EQU     0FDH            ;PORT TO SET EXT. ADDR.
DNAME   EQU     'C'             ;DRIVE NAME OF RAMDRIV.
       IF      PANEL
LIGHTS  EQU     0FFH            ;FRONT PANEL LIGHTS.
SWITCH  EQU     0FFH            ;FRONT PANEL SWITCHES.
       ENDIF
TRAKLN  EQU      4              ; kilobytes per track
DIRECT  EQU     32              ;NO. OF DIRECTORY ENTRIES.
;
;  These equates will normally not need to be changed.
;
BDOS    EQU     5
DELCHR  EQU     0E5H            ;DELETED DIRECTORY ENTRY.
DNUMB   EQU     DNAME - 'A'     ;RAMDRIV DRIVE NUMBER
CR      EQU     0DH
LF      EQU     0AH
;
;
;  The code below replaces the original loader code, since that
;  mechanism will not work on a program compiled under M80.  The
;  M80 .PHASE commands have been used rather than the original
;  ORG's.
;
       LD      DE,SIGNON       ; print signon message from loader
       LD      C,9
       CALL    BDOS
;
       LD      HL,(0001)       ; check to see if BIOS modified
       INC     HL              ; point to jump vector
       LD      E,(HL)
       INC     HL
       LD      D,(HL)
       EX      DE,HL
       LD      DE,RAMDRIV
       AND     A               ; clear carry
       SBC     HL,DE           ; WBOOT minus RAMDRIV
       JP      C,CONTIN        ; if carry we are OK
       LD      DE,NOWAY
       LD      C,9
       CALL    BDOS
       JP      0
;
SIGNON: DB      'RAMDRIV5'
       DB      CR,LF,'$'
NOWAY:  DB      'Cannot load RAMDRIV',CR,LF
       DB      'Cold boot system and try again'
       DB      CR,LF,'$'
;
CONTIN: LD      HL,SETUP
       LD      DE,RAMDRIV
       LD      BC,1023
       LDIR
       JP      RAMDRIV
;
       .PHASE  RAMDRIV
;
;  ORIGINAL JUMP TABLE FROM BIOS
;
WBOOT1: DS      48
WCK:                            ;END OF BIOS TABLE.
;
CSV:    DS      DIRECT/4        ; this should probably be zero since no
;                                   checking is done
ALV:    DS      72              ; this is allocation vector space which
;                                   must have 1 bit available per block.
;                                   this value corresponds to 576 1K
;                                   blocks.
;
;  If the user does not wish to (or cannot) use the directory and host
;  buffers in the system BIOS, the buffers can alternatively be defined
;  here.  In this case the following lines should be uncommented and the
;  corresponding equates above commented.
;
;DIRBUF: DS     128             ; directory buffer
;RAMBUF: DS     128             ; bank communication buffer
;
ENDTBL:                         ;END OF DATA TABLES.
;
       .DEPHASE
;
;  The following code is executed once to set up the RAMDRIV system.  In
;  order to conserve space it will be overwritten by data buffers during
;  actual access to RAMDRIV.
;
;
SETUP:
;
       .PHASE  RAMDRIV
;
;  Determine the amount of available RAMDRIV memory
;
       XOR     A
       LD      C,A
       LD      HL,BLEN         ; RAMDRIV memory table
       LD      DE,0002         ; table entry increment
BCHEK0: LD      A,(HL)          ; get bank kilobytesa
       OR      A               ; zero means end of table
       JR      Z,BCHEK1
       ADD     A,C             ; accumulate count
       LD      C,A             ; and save it
       ADD     HL,DE           ; point to next BLEN entry
       JR      BCHEK0
;
BCHEK1: CP      C               ; is there any ram available at all?
       JR      NZ,RAMOK        ; jump if there is
       LD      DE,NORAM        ; no ram, tell the user
       LD      C,9
       CALL    BDOS
       RET                     ; avoid warm boots!
;
NORAM:  DB      'Cannot activate RAMDRIV.  No available RAM.'
       DB      CR,LF,'$'
;
RAMOK:  XOR     A               ; clear counter
       LD      B,TRAKLN        ; the count must be multiplied
;                                   by TRAKLN to yeild kilobytes
RAMOK1: ADD     A,C             ; multiply by repeated adding
       DJNZ    RAMOK1
;
       DEC     A               ; RAMDRIV kilobytes minus 1
       LD      (DSM),A         ; maximum block number to DPB
;
;  MODIFY BIOS JUMP TABLES
;
       IF      $ LT WCK        ;MAKE SURE THAT WBOOT1 TABLE
       JP      BJUMP           ;WILL NOT OVERWRITE
       ENDIF                   ;BJUMP ROUTINE.
       IF      $ LT WCK
       DEFS    WCK - $
       ENDIF
;
BJUMP:  LD      HL,(1)          ;GET ADDRESS OF WARM BOOT
       LD      DE,WBOOT1       ;TRANSFER TABLE FROM BIOS UP HERE
       LD      BC,48           ;48 BYTES
       LDIR
       LD      HL,WBOOT2       ;TRANSFER OUR TABLE TO BIOS
       LD      DE,(0001)
       LD      BC,48
       LDIR
;
;  PRINT MESSAGE AND ASK QUESTION
;
       LD      DE,QUEST
       LD      C,9             ;PRINT STRING
       CALL    BDOS
       LD      C,1             ;GET CHARACTER
       CALL    BDOS
       RES     5,A             ;CONVERT TO UPPER CASE.
       CP      'Y'
       JP      NZ,QUIT
;
;  CLEAR DIRECTORY OF RAMDRIV
;
       LD      BC,0000         ; directory on track 0 sector 0
       CALL    MAP
       OUT     (XPORT),A
       IF      PANEL
       CPL
       OUT     (LIGHTS),A      ;TO IMSAI FRONT PANEL
       ENDIF
       EX      DE,HL           ; HL = base location of directory
       LD      DE,32           ;EVERY 32 BYTES
       LD      B,DIRECT        ;NO. OF ENTRIES.
       LD      A,DELCHR
CLEAR:  LD      (HL),A
       ADD     HL,DE
       DJNZ    CLEAR
       LD      A,PRAM          ;RESET EXT. ADDR.
       OUT     (XPORT),A
       IF      PANEL
       CPL
       OUT     (LIGHTS),A
       ENDIF
;
QUIT:   LD      DE,CRLF
       LD      C,9
       CALL    BDOS
       JP      0               ;BACK TO CP/M.
;
QUEST:  DB      CR,LF
       DB      'RAMDRIV active on drive ',DNAME
       DB      CR,LF
       DB      'Should directory of drive ',DNAME
       DB      ' be cleared (Y/N)? $'
CRLF:   DB      CR,LF,'$'
;
;  NEW TABLE COPIED INTO BIOS
;
WBOOT2: JP      WBOOT
       JP      WBOOT1+3
       JP      WBOOT1+6
       JP      WBOOT1+9
       JP      WBOOT1+12
       JP      WBOOT1+15
       JP      WBOOT1+18
       JP      HOME            ;21
       JP      SELDSK          ;24
       JP      SETTRK          ;27
       JP      SETSEC          ;30
       JP      SETDMA          ;33
       JP      READ            ;36
       JP      WRITE           ;39
       JP      WBOOT1+42
       JP      SECTRN          ;45
;
;  IMPLEMENT BIOS FUNCTIONS
;
       IF      $ LT ENDTBL     ;MAKE SURE THAT BIOS
       DEFS    ENDTBL - $      ; FUNCTIONS BEGIN
       ENDIF                   ; AFTER TABLES.
;
WBOOT:  LD      HL,80H          ;SET INITIAL DMA ADDR.
       LD      (DMAADR),HL
       JP      WBOOT1          ;BACK TO BIOS
;
;
HOME:   LD      A,(DISK)        ;CHECK DISK NUMBER.
       CP      DNUMB           ;IS IT THE RAMDRIV?
       JP      NZ,WBOOT1+21    ;IF NOT, LET BIOS HANDLE IT.
       LD      A,0             ;SET "TRACK" TO 0
       LD      (XTRAK),A
       RET
;
;
SELDSK: LD      A,C             ;CHECK REQUESTED DISK,
       LD      (DISK),A
       CP      DNUMB
       JP      NZ,WBOOT1+24
       LD      HL,DPH          ;RETURN DISK PAR. HEADER.
       RET
;
;  DISK PARAMETER HEADER FOR RAMDRIV
;
DPH:
       DW      0               ;NO SECTOR TRANSLATION
       DW      0,0,0           ;SCRATCH
       DW      DIRBUF          ;LOC. OF SCRATCHPAD AREA.
       DW      DPB             ;DISK PARAMETER BLOCK
       DW      CSV             ;DIR. CHECK AREA
       DW      ALV             ;ALLOC. VECTOR.
;
;  DISK PARAMETER BLOCK
;
DPB:
       DW      (1024*TRAKLN)/128       ;SECTORS PER TRACK
       DB      3               ;BSH
       DB      7               ;BLM
       DB      0               ;EXM
DSM:    DW      62              ;MAX BLOCK NO.
       DW      DIRECT-1        ;HIGHEST DIRECT. NO.
;
AL0     EQU     LOW (0FFH SHL (8-DIRECT/32))
AL1     EQU     0
;
       DB      AL0,AL1         ;DIRECTORY BLOCKS.
;
CKS     EQU     0               ;CHECK DIRECT. ENTRIES.
;
       DW      CKS
       DW      0               ;NO SKIPPED TRACKS.
;
;
SETTRK: LD      A,(DISK)
       CP      DNUMB
       JP      NZ,WBOOT1+27
       LD      A,C             ;TRACK NO.
       LD      (XTRAK),A
       RET
;
;
SETSEC: LD      A,(DISK)
       CP      DNUMB
       JP      NZ,WBOOT1+30
       LD      (SECTOR),BC
       RET
;
;
SETDMA: LD      (DMAADR),BC     ;SAVE DMA ADDR. BOTH HERE
       JP      WBOOT1+33       ;AND IN BIOS.
;
;
READ:   LD      A,(DISK)
       CP      DNUMB
       JP      NZ,WBOOT1+36
       LD      BC,(SECTOR)     ; but we asssume sector fits in one byte
       LD      A,(XTRAK)       ;SET EXTENDED ADDRESS.
       LD      B,A
       CALL    MAP             ; to compute bank and offset
       OUT     (XPORT),A
       IF      PANEL
       CPL
       OUT     (LIGHTS),A
       ENDIF
       EX      DE,HL
       LD      DE,RAMBUF       ;TEMPORARY BUFFER.
       LD      BC,128          ;SECTOR=128 BYTES.
       LDIR
       LD      A,PRAM          ;RESET ADDR.
       OUT     (XPORT),A
       IF      PANEL
       CPL
       OUT     (LIGHTS),A
       ENDIF
       LD      HL,RAMBUF       ;MOVE FROM BUFFER
       LD      DE,(DMAADR)     ; back to main ram
       LD      BC,128
       LDIR
       XOR     A               ; show no errors
       RET
;
;
WRITE:  LD      A,(DISK)
       CP      DNUMB
       JP      NZ,WBOOT1+39
       IF      PANEL
       IN      A,(SWITCH)      ;CHECK D0 OF FRONT PANEL
       AND     1
       RET     NZ              ;RETURN IF WRITE PROTECT.
       ENDIF
       LD      HL,(DMAADR)     ;DMA --> BUFFER
       LD      DE,RAMBUF
       LD      BC,128
       LDIR
;
       LD      BC,(SECTOR)     ; but we assume sector < 256
       LD      A,(XTRAK)
       LD      B,A
       CALL    MAP
       OUT     (XPORT),A
       IF      PANEL
       CPL
       OUT     (LIGHTS),A
       ENDIF
       LD      HL,RAMBUF       ;BUFFER --> RAMDRIV.
       LD      BC,128
       LDIR
;
       LD      A,PRAM
       OUT     (XPORT),A
       IF      PANEL
       CPL
       OUT     (LIGHTS),A
       ENDIF
       XOR     A               ; show no error
       RET
;
;
SECTRN: LD      A,(DISK)
       CP      DNUMB
       JP      NZ,WBOOT1+45
       LD      H,B
       LD      L,C
       RET
;
;
;  Subroutine to calculate RAM address and bank
;
;       called with  B = track number (starts from 0)
;                    C = sector number (starts from 0)
;
;       returns with A = extended address byte
;                   DE = offset into bank for start of sector
;
MAP:    XOR     A               ; clear A
       LD      A,B             ; track number to A
       LD      DE,2            ; increment for BLEN table
       LD      HL,BLEN
;
MAP1:   SUB     (HL)            ; subtract bank length in K's
       JR      C,MAP2          ; jump if track in this bank
       ADD     HL,DE           ; point to next BLEN entry
       JR      MAP1            ; repeat till done
;
MAP2:   ADD     A,(HL)          ; A = relative track number in bank
       RLCA                    ; multiply by 4K
       RLCA
       RLCA
       RLCA
       LD      E,D             ; clear E
       LD      D,C             ; multiply sector number by 128
       RR      D
       RR      E
       ADD     A,D             ; add to track starting address
       LD      D,A             ; DE = sector start in bank
       INC     HL
       LD      A,(HL)          ; A = extended (bank) address
       RET
;
BLEN:   DB      32/TRAKLN,0FFH  ; first bank has 32K at FF
       DB      32/TRAKLN,0FEH  ; second bank has 32K at FE
       DB      04/TRAKLN,001H  ; third bank has 4K at 01
       DB      0               ; end of table
;
;  DATA STORAGE AREA
;
DISK:   DB      0
XTRAK:  DB      0
DMAADR: DW      80H
SECTOR: DW      0
;
       .DEPHASE
;
       END