CP/M Plus (CP/M® Version 3) Operating System Programmer's Guide


Section 4

Programming Examples

The programs presented in this section illustrate how to use the BDOS functions described in the previous section. The examples show how to copy a file, how to dump a file, how to create or access a random access file, and how to write an RSX program.


A Sample File-To-File Copy Program

The following program illustrates simple file operations. You can create the program source file, COPY.ASM, using ED or another editor, and then assemble COPY.ASM using MAC. MAC produces the file COPY.HEX. Use the utility HEXCOM to produce a COPY.COM file that can execute under CP/M 3.

The COPY program first sets the stack pointer to a local area, then moves the second name from the default area at 006CH to a 33-byte file control block named DFCB. The DFCB is then prepared for file operations by clearing the current record field. Because the CCP sets up the source FCB at 005CH upon entry to the COPY program, the source and destination FCBs are now ready for processing. To prepare the source FCB, the CCP places the first name into the default FCB, with the proper fields zeroed, including the current record field at 007CH.

COPY continues by opening the source file, deleting any existing destination file, and then creating the destination file. If each of these operations is successful, the COPY program loops at the label COPY until each record is read from the source file and placed into the destination file. Upon completion of the data transfer, the destination file is closed, and the program returns to the CCP command level by jumping to BOOT.

               ;         sample file-to-file copy program
               ;         at the ccp level, the command
               ;         copy a:x.y b:u.v
               ;         copies the file named x.y from drive
               ;         a to a file named u.v on drive b.
0000 =         boot      equ 0000h      ; system reboot
0005 =         bdos      equ 0005h      ; bdos entry point
005c =         fcb1      equ 005ch      ; first filename
005c =         sfcb      equ fcb1       ; source fcb
006c =         fcb2      equ 006ch      ; second filename
0080 =         dbuff     equ 0080h      ; default buffer
0100 =         tpa       equ 0100h      ; beginning of tpa
0009 =         printf    equ 9          ; print buffer func*
000f =         openf     equ 15         ; open file func*
0010 =         closef    equ 16         ; close file func*
0013 =         deletef   equ 19         ; delete file func*
0014 =         readf     equ 20         ; read file func*
0015 =         writef    equ 21         ; write file func*
0016 =         makef     equ 22         ; make file func*
0100                     org tpa        ; beginning of tpa
0100 311b02              lxi sp,stack   ; local stack
               ;         move second filename to dfcb
0103 0e10                mvi c,16       ; half an fcb
0105 116c00              lxi d,fcb2     ; source of move
0108 21da01              lxi h,dfcb     ; destination
010b 1a        mfcb:     ldax d         ; source fcb
010c 13                  inx d          ; ready next
010d 77                  mov m,a        ; dest fcb
010e 23                  inx h          ; ready next
010f 0d                  dcr c          ; count 16...0
0110 c20b01              jnz mfcb       ; loop 16 times
               ;         name has been moved, zero cr
0113 af                  xra a          ; a = 00h
0114 32fa01              sta dfcbcr     ; current rec = 0
               ;         source and destination fcbs ready
0117 115c00              lxi d,sfcb     ; source file
011a cd6901              call open      ; error if 255
011d 118701              lxi d,nofile   ; ready message
0120 3c                  inr a          ; 255 becomes 0
0121 cc6101              cz finis       ; done if no file
               ;         source file open, prep destination
0124 11da01              lxi d,dfcb     ; destination
0127 cd7301              call delete    ; remove if present
012a 11da01              lxi d,dfcb     ; destination
012d cd8201              call make      ; create the file
0130 119601              lxi d,nodir    ; ready message
0133 3c                  inr a          ; 255 becomes 0
0134 cc6101              cz finis       ; done if no dir space
               ;         source file open, dest file open
               ;         copy until end of file on source
0137 115c00    copy:     lxi d,sfcb     ; source
013a cd7801              call read      ; read next record
013d b7                  ora a          ; end of file?
013e c25101              jnz eofile     ; skip write if so
               ;         not end of file, write the record
0141 11da01              lxi d,dfcb     ; destination
0144 cd7d01              call write     ; write record
0147 11a901              lxi d,space    ; ready message
014a b7                  ora a          ; 0 if write ok
014b c46101              cnz finis      ; end if not so
014e c33701              jmp copy       ; loop until eof
               ;         end of file, close destination
0151 11da01    eofile:   lxi d,dfcb     ; destination
0154 cd6e01              call close     ; 255 if error
0157 21bb01              lxi h,wrprot   ; ready message
015a 3c                  inr a          ; 255 becomes 0
015b cc6101              cz finis       ; should not happen
               ;         copy operation complete, end
015e 11cc01              lxi d,normal   ; ready message
               ;         write message given by de, reboot
0161 0e09      finis:    mvi c,printf
0163 cd0500              call bdos      ; write message
0166 c30000              jmp boot       ; reboot system
               ;         system interface subroutines
               ;         (all return directly from bdos)
0169 0e0f      open:     mvi c,openf
016b c30500              jmp bdos
016e 0e10      close:    mvi c,closef
0170 c30500              jmp bdos
0173 0e13      delete:   mvi c,deletef
0175 c30500              jmp bdos
0178 0e14      read:     mvi c,readf
017a c30500              jmp bdos
017d 0e15      write:    mvi c,writef
017f c30500              jmp bdos
0182 0e16      make:     mvi c,makef
0184 c30500              jmp bdos
               ;         console messages
0187 6e6f20736fnofile:   db 'no source file$'
0196 6e6f206469nodir:    db 'no directory space$'
01a9 6f7574206fspace:    db 'out of data space$'
01bb 7772697465wrprot:   db 'write protected?$'
01cc 636f707920normal:   db 'copy complete$'
               ;         data area
01da           dfcb:     ds 33
01fa =         dfcbcr    equ dfcb+32
01fb                     ds 32          ; 16 level stack
021b                     end

Note that this program makes several simplifications and could be enhanced. First, it does not check for invalid filenames that could, for example, contain ambiguous references. This situation could be detected by scanning the 32-byte default area starting at location 005CH for ASCII question marks. To check that the filenames have, in fact, been included, COPY could check locations 005DH and 006DH for nonblank ASCII characters. Finally, a check should be made to ensure that the source and destination filenames are different. Speed could be improved by buffering more data on each read operation. For example, you could determine the size of memory by fetching FBASE from location 0006H, and use the entire remaining portion of memory for a data buffer. You could also use CP/M 3's Multi-Sector I/O facility to read and write data in up to 16K units.


A Sample File Dump Utility

The following dump program reads an input file specified in the CCP command line, and then displays the content of each record in hexadecimal format at the console. DUMP Program reads input file and displays hex data

               ;         dump program reads input file
               ;         and displays hex data
0100                     org 100h
0005 =         bdos      equ 0005h      ;bdos entry point
0001 =         cons      equ 1          ;read console
0002 =         typef     equ 2          ;type function
0009 =         printf    equ 9          ;buffer print entry
000b =         brkf      equ 11         ;break key function
                                        ;(true if char)
000f =         openf     equ 15         ;file open
0014 =         readf     equ 20         ;read function
005c =         fcb       equ 5ch        ;file control block address
0080 =         buff      equ 80h        ;input disk buffer address
               ;         non graphic characters
000d =         cr        equ 0dh        ;carriage return
000a =         lf        equ 0ah        ;line feed
               ;         file control block definitions
005c =         fcbdn     equ fcb+0      ;disk name
005d =         fcbfn     equ fcb+1      ;file name
0065 =         fcbft     equ fcb+9      ;disk file type (3 characters)
0068 =         fcbrl     equ fcb+12     ;file's current reel number
006b =         fcbrc     equ fcb+15     ;file's record count (0 to 128)
007c =         fcbcr     equ fcb+32     ;current (next) record number
                                        ;(0 to 128)
007d =         fcbln     equ fcb+33     ;fcb length
               ;         set up stack
0100 210000              lxi h,0
0103 39                  dad sp
               ;         entry stack pointer in hl from the ccp
0104 221502              shld oldsp
               ;         set sp to local stack area (restored at finis)
0107 315702              lxi sp,stktop
               ;         read and print successive buffers
010a cdc101              call setup     ;set up input file
010d feff                cpi 255        ;255 if file not present
010f c21b01              jnz openok     ;skip if open is ok
               ;         file not there, give error message and return
0112 11f301              lxi d,opnmsg
0115 cd9c01              call err
0118 c35101              jmp finis      ;to return
               ;         open operation ok, set buffer index to end
011b 3e80      openok:   mvi a,80h
011d 321302              sta ibp        ;set buffer pointer to 80h
               ;         hl contains next address to print
0120 210000              lxi h,0        ;start with 0000
0123 e5        gloop:    push h         ;save line position
0124 cda201              call gnb
0127 e1                  pop h          ;recall line position
0128 da5101              jc finis       ;carry set by gnb if end file
012b 47                  mov b,a
               ;         print hex values
               ;         check for line fold
012c 7d                  mov a,l
012d e60f                ani 0fh        ;check low 4 bits
012f c24401              jnz nonum
               ;         print line number
0132 cd7201              call crlf
               ;         check for break key
0135 cd5901              call break
               ;         accum lsb = 1 if character ready
0138 0f                  rrc            ;into carry
0139 da5101              jc finis       ;do not print any more
013c 7c                  mov a,h
013d cd8f01              call phex
0140 7d                  mov a,l
0141 cd8f01              call phex
0144 23        nonum:    inx h          ;to next line number
0145 3e20                mvi a,' '
0147 cd6501              call pchar
014a 78                  mov a,b
014b cd8f01              call phex
014e c32301              jmp gloop
               ;         end of dump
0151 cd7201    finis:    call crlf
0154 2a1502              lhld oldsp
0157 f9                  sphl
               ;         stack pointer contains ccp's stack location
0158 c9                  ret            ;to the ccp
               ;         subroutines
               ;         check break key (actually any key will do)
0159 e5d5c5    break:    push h! push d! push b ; environment saved
015c 0e0b                mvi c,brkf
015e cd0500              call bdos
0161 c1d1e1              pop b! pop d! pop h; environment restored
0164 c9                  ret
               ;         print a character
0165 e5d5c5    pchar:    push h! push d! push b; saved
0168 0e02                mvi c,typef
016a 5f                  mov e ,a
016b cd0500              call bdos
016e c1d1e1              pop b! pop d! pop h; restored
0171 c9                  ret
               ;         move to start of new line
0172 3e0d      crlf:     mvi a,cr
0174 cd6501              call pchar
0177 3e0a                mvi a,lf
0179 cd6501              call pchar
017c c9                  ret
               ;         print nibble in reg a
017d e60f      pnib:     ani 0fh ;low 4 bits
017f fe0a                cpi 10
0181 d28901              jnc p10
               ;         less than or equal to 9
0184 c630                adi '0'
0186 c38b01              jmp prn
               ;         greater or equal to 10
0189 c657      p10:      adi 'a' - 10
018b cd6501    prn:      call pchar
018e c9                  ret
               ;         print hex char in reg a
018f f5        phex:     push psw
0190 0f                  rrc
0191 0f                  rrc
0192 0f                  rrc
0193 0f                  rrc
0194 cd7d01              call pnib      ;print nibble
0197 f1                  pop psw
0198 cd7d01              call pnib
019b c9                  ret
               ;         print error message
               ;         d,e addresses message ending with "$"
019c 0e09      err:      mvi c,printf ;print buffer function
019e cd0500              call bdos
01a1 c9                  ret
               ;         get next byte
01a2 3a1302    gnb:      lda ibp
01a5 fe80                cpi 80h
01a7 c2b301              jnz g0
               ;         read another buffer
01aa cdce01              call diskr
01ad b7                  ora a          ;zero value if read ok
01ae cab301              jz g0          ;for another byte
               ;         end of data, return with carry set for eof
01b1 37        stc
01b2 c9        ret
               ;         read the byte at buff+reg a
01b3 5f        g0:       mov e,a        ;is byte of buffer index
01b4 1600                mvi d,0        ;double precision index to de
01b6 3c                  inr a          ;index=index+1
01b7 321302              sta ibp        ;back to memory
               ;         pointer is incremented
               ;         save the current file address
01ba 218000              lxi h,buff
01bd 19                  dad d
               ;         absolute character address is in hl
01be 7e                  mov a,m
               ;         byte is in the accumulator
01bf b7                  ora a          ;reset carry bit
01c0 c9                  ret
               ;         set up file
               ;         open the file for input
01c1 af        setup:    xra a          ;zero to accum
01c2 327c00              sta fcbcr      ;clear current record
01c5 115c00              lxi d,fcb
01c8 0e0f                mvi c,openf
01ca cd0500              call bdos
               ;         255 in accum if open error
01cd c9                  ret
               ;         read disk file record
01ce e5d5c5    diskr:    push h! push d! push b
01d1 115c00              lxi d,fcb
01d4 0e14                mvi c,readf
01d6 cd0500              call bdos
01d9 c1d1e1              pop b! pop d! pop h
01dc c9                  ret
               ;         fixed message area
01dd 66696c6520signon:   db 'file dump version 2.0$'
01f3 0d0a6e6f20opnmsg:   db cr,lf ,'no input file present on disk$'
               ;         variable area
0213           ibp:      ds 2           ;input buffer pointer
0215           oldsp:    ds 2           ;entry sp value from ccp
               ;         stack area
0217                     ds 64          ;reserve 32 level stack
0257                     end


A Sample Random Access Program

This example is an extensive but complete example of random access operation. The following program reads or writes random records upon command from the terminal. When the program has been created, assembled, and placed into a file labeled RANDOM.COM, the CCP level command


can start the test program. In this case, the RANDOM program looks for a file X.DAT and, if it finds it, prompts the console for input. If X.DAT is not found, RANDOM creates the file before displaying the prompt. Each prompt takes the form:

next command?

and is followed by operator input, terminated by a carriage return. The input commands take the form:

nW nR nF Q

where n is an integer value in the range 0 to 262143, and W, R, F, and Q are simple command characters corresponding to random write, W, random read, R, random write with zero fill, F, and quit processing, Q. If you enter a W or F command, the RANDOM program issues the prompt:

type data:

You then respond by typing up to 127 characters, followed by a carriage return. RANDOM then writes the character string into the X.DAT file at record n. If you enter an F command, the RANDOM program fills previously unallocated data blocks with zeros before writing record n. If you enter the R command, RANDOM reads record number n and displays the string value at the console. If you enter the Q command, the X.DAT file is closed, and the program returns to the console command processor. In the interest of brevity, the only error message is:

err, try again

The program begins with an initialization section where the input file is opened or created, followed by a continuous loop at the label ready where the individual commands are interpreted. The program uses the default file control block at 005CH and the default buffer at 0080H in all disk operations. The utility subroutines that follow contain the principal input line processor, called readc. This particular program shows the elements of random access processing and can be used as the basis for further program development.

               ;*                                         *
               ;* sample random access program for cp/m 3 *
               ;*                                         *
0100                     org 100h       ; base of tpa
0000 =         reboot    equ 0000h      ; system reboot
0005 =         bdos      equ 0005h      ; bdos entry point
0001 =         coninp    equ 1          ; console input function
0002 =         conout    equ 2          ; console output function
0009 =         pstring   equ 9          ; print string until '$'
000a =         rstring   equ 10         ; read console buffer
000c =         version   equ 12         ; return version number
000f =         openf     equ 15         ; file open function
0010 =         closef    equ 16         ; close function
0016 =         makef     equ 22         ; make file function
0021 =         readr     equ 33         ; read random
0022 =         writer    equ 34         ; write random
0028 =         wrtrzf    equ 40         ; write random zero fill
0098 =         parsef    equ 152        ; parse function
005c =         fcb       equ 005ch      ; default file control block
007d =         ranrec    equ fcb+33     ; random record position
007f =         ranovf    equ fcb+35     ; high order (overflow) byte
0080 =         buff      equ 0080h      ; buffer address
000d =         cr        equ 0dh        ; carriage return
000a =         lf        equ 0ah        ; line feed
               ;*                                        *
               ;* load sp, set-up file for random access *
               ;*                                        *
0100 313603              lxi sp,stack
               ;         version 3.1?
0103 0e0c                mvi c,version
0105 cd0500              call bdos
0108 fe31                cpi 31h        ; version 3.1 or better?
010a d21601              jnc versok
               ;         bad version, message and go back
010d 118102              lxi d,badver
0110 cd3102              call print
0113 c30000              jmp reboot
               ;         correct version for random access
0116 0e0f      versok:   mvi c,openf    ; open default fcb
0118 3a5d00    rdname:   lda fcb+1
011b fe20                cpi ' '
011d c22c01              jnz opfile
0120 11df02              lxi d,entmsg
0123 cd3102              call print
0126 cd2002              call parse
0129 c31601              jmp versok
012c 115c00    opfile:   lxi d,fcb
012f cd0500              call bdos
0132 3c                  inr a          ; err 255 becomes zero
0133 c24b01              jnz ready
               ;         cannot open file, so create it
0136 0e16                mvi c,makef
0138 115c00              lxi d,fcb
013b cd0500              call bdos
013e 3c                  inr a          ; err 255 becomes zero
013f c24b01              jnz ready
               ;         cannot create file, directory full
0142 11a002              lxi d,nospace
0145 cd3102              call print
0148 c30000              jmp reboot     ; back to ccp
               ;*                                         *
               ;* loop back to "ready" after each command *
               ;*                                         *
               ;         file is ready for processing
014b cd3c02    ready:    call readcom   ; read next command
014e 227d00              shld ranrec    ; store input record
0151 217f00              lxi h,ranovf
0154 71                  mov m,c        ; set ranrec high byte
0155 fe51                cpi 'Q'        ; quit?
0157 c26901              jnz notq
               ;         quit processing, close file
015a 0e10                mvi c,closef
015c 115c00              lxi d,fcb
015f cd0500              call bdos
0162 3c                  inr a          ; err 255 becomes 0
0163 caff01              jz error       ; error message 'retry'
0166 c30000              jmp reboot     ; back to ccp
               ;*                                    *
               ;* end of quit command, process write *
               ;*                                    *
               ;         not the quit command, random write?
0169 fe57      notq:     cpi 'W'
016b c29c01              jnz notw
               ;         this is a random write,
               ;         fill buffer until cr
016e 11b302              lxi d,datmsg
0171 cd3102              call print     ; data prompt
0174 0e7f                mvi c,127      ; up to 127 characters
0176 218000              lxi h,buff     ; destination
               ;         read next character to buff
0179 c5        rloop:    push b         ; save counter
017a e5                  push h         ; next destination
017b cd0802              call getchr    ; character to a
017e e1                  pop h          ; restore counter
017f c1                  pop b          ; restore next to fill
0180 fe0d                cpi cr         ; end of line?
0182 ca8b01              jz erloop
               ;         not end; store character
0185 77                  mov m,a
0186 23                  inx h          ; next to fill
0187 0d                  dcr c          ; counter goes down
0188 c27901              jnz rloop      ; end of buffer?
               ;         end of read loop, store 00h
018b 3600      erloop:   mvi m,0
               ;         write the record to selected record number
018d 0e22                mvi c,writer
018f 115c00              lxi d,fcb
0192 cd0500              call bdos
0195 b7                  ora a          ; error code zero?
0196 c2ff01              jnz error      ; message if not
0199 c34b01              jmp ready      ; for another record
               ;*                                *
               ;* end of write command,          *
               ;* process write random zero fill *
               ;*                                *
               ;         not the quit command; random write zero fill?
019c fe46      notw:     cpi 'F'
019e c2cf01              jnz notf
               ;         this is a random write; fill buffer until cr
01a1 11b302              lxi d,datmsg
01a4 cd3102              call print     ; data prompt
01a7 0e7f                mvi c,127      ; up to 127 characters
01a9 218000              lxi h,buff     ; destination
               ;         read next character to buff
01ac c5        rloop1:   push b         ; save counter
01ad e5                  push h         ; next destination
01ae cd0802              call getchr    ; character to a
01b1 e1                  pop h          ; restore counter
01b2 c1                  pop b          ; restore next to fill
01b3 fe0d                cpi cr         ; end of line?
01b5 cabe01              jz erloop1
               ;         not end, store character
01b8 77                  mov m,a
01b9 23                  inx h          ; next to fill
01ba 0d                  dcr c          ; counter goes down
01bb c2ac01              jnz rloop1     ; end of buffer?
               ;         end of read loop, store 00h
01be 3600      erloop1:  mvi m,0
               ;         write the record to selected record number
01c0 0e28                mvi c,wrtrzf
01c2 115c00              lxi d,fcb
01c5 cd0500              call bdos
01c8 b7                  ora a          ; error code zero?
01c9 c2ff01              jnz error      ; message if not
01cc c34b01              jmp ready      ; for another record
               ;*                                     *
               ;* end of write commands; process read *
               ;*                                     *
               ;         not a write command, read record?
01cf fe52      notf:     cpi 'R'
01d1 c2ff01              jnz error      ; skip if not
               ;         read random record
01d4 0e21                mvi c,readr
01d6 115c00              lxi d,fcb
01d9 cd0500              call bdos
01dc b7                  ora a          ; return code 00h?
01dd c2ff01              jnz error
               ;         read was successful, write to console
01e0 cd1502              call crlf      ; new line
01e3 0e80                mvi c,128      ; max 128 characters
01e5 218000              lxi h,buff     ; next to get
01e8 7e        wloop:    mov a,m        ; next character
01e9 23                  inx h          ; next to get
01ea e67f                ani 7fh        ; mask parity
01ec ca4b01              jz ready       ; for another command if 00h
01ef c5                  push b         ; save counter
01f0 e5                  push h         ; save next to get
01f1 fe20                cpi ' '        ; graphic?
01f3 d40e02              cnc putchr     ; skip output if not
01f6 e1                  pop h
01f7 c1                  pop b
01f8 0d                  dcr c          ; count=count-1
01f9 c2e801              jnz wloop
01fc c34b01              jmp ready
               ;*                                             *
               ;* end of read command, all errors end-up here *
               ;*                                             *
01ff 11bf02    error:    lxi d,errmsg
0202 cd3102              call print
0205 c34b01              jmp ready
               ;*                                     *
               ;* utility subroutines for console i/o *
               ;*                                     *
               ;         read next console character to a
0208 0e01      getchr:   mvi c,coninp
020a cd0500              call bdos
020d c9                  ret
               ;write character a to console
020e 0e02      putchr:   mvi c,conout
0210 5f                  mov e,a        ; character to send
0211 cd0500              call bdos      ; send character
0214 c9                  ret
               ;         send carriage return line feed
0215 3e0d      crlf:     mvi a,cr       ; carriage return
0217 cd0e02              call putchr
021a 3e0a                mvi a,lf       ; line feed
021c cd0e02              call putchr
021f c9                  ret
               ;         read and parse filespec
0220 11f002    parse:    lxi d,conbuf
0223 0e0a                mvi c,rstring
0225 cd0500              call bdos
0228 111203              lxi d,pfncb
022b 0e98                mvi c,parsef
022d cd0500              call bdos
0230 c9                  ret
               ;         print the buffer addressed by de until $
0231 d5        print:    push d
0232 cd1502              call crlf      ; new line
0235 d1                  pop d
0236 0e09                mvi c,pstring
0238 cd0500              call bdos      ; print the string
023b c9                  ret
               ;         read the next command line to the conbuf
023c 11d002    readcom:  lxi d,prompt
023f cd3102              call print     ; command?
0242 0e0a                mvi c,rstring
0244 11f002              lxi d,conbuf
0247 cd0500              call bdos      ; read command line
               ;         command line is present, scan it
024a 0e00                mvi c,0        ; start with 00h
024c 210000              lxi h,0        ; 0000h
024f 11f202              lxi d,conlin   ; command line
0252 1a        readc:    ldax d         ; next command character
0253 13                  inx d          ; to next command position
0254 b7                  ora a          ; cannot be end of command
0255 c8                  rz
               ;         not zero, numeric?
0256 d630                sui '0'
0258 fe0a                cpi 10         ; carry if numeric
025a d27902              jnc endrd
               ;         add-in next digit
025d f5                  push psw
025e 79                  mov a,c        ; value in ahl
025f 29                  dad h
0260 8f                  adc a          ; i*2
0261 f5                  push a         ; save value * 2
0262 e5                  push h
0263 29                  dad h          ; *4
0264 8f                  adc a
0265 29                  dad h          ; *8
0266 8f                  adc a
0267 c1                  pop b          ; i*2 + *8 = *10
0268 09                  dad b
0269 c1                  pop b
026a 88                  adc b
026b c1                  pop b          ; +digit
026c 48                  mov c,b
026d 0600                mvi b,0
026f 09                  dad b
0270 ce00                aci 0
0272 4f                  mov c,a
0273 d25202              jnc readc
0276 c33c02              jmp readcom
               ;         end of read, restore value in a
0279 c630      endrd:    adi '0'        ; command
027b fe61                cpi 'a'        ; translate case?
027d d8                  rc
               ;         lower case, mask lower case bits
027e e6bf                ani 10111111b
0280 c9                  ret            ; return with value in chl
               ;*                                       *
               ;* string data area for console messages *
               ;*                                       *
0281 736f727279badver:   db 'sorry, you need cp/m version 3$'
02a0 6e6f206469nospace:  db 'no directory space$'
02b3 7479706520datmsg:   db 'type data: $'
02bf 6572726f72errmsg:   db 'error, try again$'
02d0 6e65787420prompt:   db 'next command? $'
02df 656e746572entmsg:   db 'enter filename: $'
               ;*                              *
               ;* fixed and variable data area *
               ;*                              *
02f0 21        conbuf:   db conlen      ; length of console buffer
02f1           consiz:   ds 1           ; resulting size after read
02f2           conlin:   ds 32          ; length 32 buffer
0021 =         conlen    equ $-consiz
0312 f202      pfncb:    dw conlin
0314 5c00                dw fcb
0316                     ds 32          ; 16 level stack
0336                     end

You could make the following major improvements to this program to enhance its operation. With some work, this program could evolve into a simple data base management system. You could, for example, assume a standard record size of 128 bytes, consisting of arbitrary fields within the record. You could develop a program called GETKEY that first reads a sequential file and extracts a specific field defined by the operator. For example, the command


would cause GETKEY to read the data base file NAMES.DAT and extract the "LASTNAME" field from each record, starting at position 10 and ending at character 20. GETKEY builds a table in memory consisting of each particular LASTNAME field, along with its 16-bit record number location within the file. The GETKEY program then sorts this list and writes a new file, called LASTNAME.KEY. This list, sometimes called an inverted index, is an alphabetical list of LASTNAME fields with their corresponding record numbers.

You could rename the program shown above to QUERY, and modify it so that it reads a sorted key file into memory. The command line might appear as QUERY NAMES.DAT LASTNAME.KEY

Instead of reading a number, the QUERY program reads an alphanumeric string which is a particular key to find in the NAMES.DAT data base. Because the LASTNAME.KEY list is sorted, you can find a particular entry quickly by performing a binary search, similar to looking up a name in the telephone directory. Start at both ends of the list and examine the entry halfway in between and, if not matched, split either the upper half or the lower half for the next search. You will quickly reach the item you are looking for, in log2(n) steps, where you will find the corresponding record number. Fetch and display this record at the console as the program illustrates.

At this point, you are just getting started. With a little more work, you can allow a fixed grouping size, which differs from the 128-byte record shown above. You can accomplish this by keeping track of the record number as well as the byte offset within the record. Knowing the group size, you can randomly access the record containing the proper group, offset to the beginning of the group within the record, and read sequentially until the group size has been exhausted.

Finally, you can improve QUERY considerably by allowing Boolean expressions that compute the set of records that satisfy several relationships, such as a LASTNAME between HARDY and LAUREL and an AGE less than 45. Display all the records that fit this description. Finally, if your lists are getting too big to fit into memory, randomly access your key files from the disk as well.


Construction of an RSX Program

This section describes the standard prefix of a Resident System Extension (RSX) and illustrates the construction of an RSX with an example. (See Section 1.6.4 for a discussion of how RSXs operate under CP/M 3.) RSX programs are usually written in assembler, but you can use other languages if the interface between the language and the calling conventions of the BDOS are set up properly.


The RSX Prefix

The first 27 bytes of an RSX program contain a standard data structure called the RSX prefix. The RSX prefix has the following format:

serial:   db 0,0,0,0,0,0
start:    jmp ftest      ; start of program
next:     db 0c3h        ; jump instruction to
          dw 0           ; next module in line
prev:     dw 0           ; previous module
remove:   db 0ffh        ; remove flag
nonbank:  db 0           ; nonbank flag
name:     db '123456713' ; any 8-character name
loader:   db 0           ; loader flag
          db 0,0         ; reserved area

The only fields of the RSX prefix that you must initialize are the remove: flag, the nonbank: flag, and the name: of the RSX.

For compatibility with previous releases of CP/M, the serial: field of the prefix is set to the serial number of the operating system by the LOADER module when the RSX is loaded into memory. Thus, the address in location 6 locates the byte following the serial number of the operating system with or without RSXs in memory.

The start: field contains a jump instruction to the beginning of the RSX code where the RSX tests to see if this BDOS function call is to be intercepted or passed on to the next module in line.

The next: field contains a jump instruction to the next module in the chain or the LOADER module if the RSX is the oldest one in memory. The RSX program must make its own BDOS function calls by calling the next: entry point.

The prev: field contains the address of the preceding RSX in memory or location 5 if the RSX is the first RSX in the chain.

The remove: field controls whether the RSX is removed from memory by the next call to the LOADER module via BDOS function 59. If the remove: flag is 0FFH, the LOADER removes the RSX from memory. Note that the CCP always calls the LOADER module during a warm start operation. An RSX that remains in memory past warm start because its remove: flag is zero, must set the flag at its termination to ensure its removal from memory at the following warm start.

The nonbank: field controls when the RSX is loaded. If the field is 0FFH, the LOADER only loads the module into memory on nonbanked CP/M 3 systems.

Otherwise, the RSX is loaded into memory under both banked and nonbanked versions of CP/M 3.

The loader: flag identifies the LOADER RSX. When the LOADER module loads an RSX into memory, it sets this prefix flag of the loaded RSX to zero. However, the loader: flag in the LOADER's prefix contains 0FFH. Thus, this flag identifies the last RSX in the chain, which is always the LOADER.


Example of RSX Use

These two sample programs illustrate the use of an RSX program. The first program, CALLVERS, prints a message to the console and then makes a BDOS Function 12 call to obtain the CP/M 3 version number. CALLVERS repeats this sequence five times before terminating. The second program, ECHOVERS, is an RSX that intercepts the BDOS Function 12 call made by CALLVERS, prints a second message, and returns the version 0031H to CALLVERS. Although this example is simple, it illustrates BDOS function interception, stack swapping, and BDOS function calls within an RSX.

               ;         callvers program
0005 =         bdos      equ 5          ; entry point for bdos
0009 =         prtstr    equ 9          ; print string function
000c =         vers      equ 12         ; get version function
000d =         cr        equ 0dh        ; carriage return
000a =         lf        equ 0ah        ; line feed
0100                     org 100h
0100 1605                mvi d,5        ; perform 5 times
0102 d5        loop:     push d         ; save counter
0103 0e09                mvi c,prtstr
0105 111e01              lxi d,call$msg ; print call message
0108 cd0500              call bdos
010b 0e0c                mvi c,vers
010d cd0500              call bdos ; try to get version
               ;         callvers will intercept
0110 7d                  mov a,l
0111 323401              sta curvers
0114 d1                  pop d
0115 15                  dcr d          ; decrement counter
0116 c20201              jnz loop
0119 0e00                mvi c,0
011b c30500              jmp bdos
011e 0d0a2a2a2acall$msg: db cr,lf,'**** callvers **** $'
0134 00        curvers:  db 0
0135                     end

               ;         echovers rsx
0009 =         pstring   equ 9          ; string print function
000d =         cr        equ 0dh
000a =         lf        equ 0ah
               ;         rsx prefix structure
0000 0000000000          db 0,0,0,0,0,0 ; room for serial number
0006 c31b00              jmp ftest      ; begin of program
0009 c3        next:     db 0c3h        ; jump
000a 0000                dw 0           ; next module in line
000c 0000      prev:     dw 0           ; previous module
000e ff        remov:    db 0ffh        ; remove flag set
000f 00        nonbnk:   db 0
0010 4543484f56          db 'echovers'
0018 000000              db 0,0,0
               ;         is this function 12?
001b 79        ftest:    mov a,c
001c fe0c                cpi 12
001e ca2400              jz begin       ; yes - intercept
0021 c30900              jmp next       ; some other function
0024 210000    begin:    lxi h,0
0027 39                  dad sp         ; save stack
0028 225300              shld ret$stack
002b 317500              lxi sp,loc$stack
002e 0e09                mvi c,pstring
0030 113e00              lxi d,test$msg ; print message
0033 cd0900              call next      ; call bdos
0036 2a5300              lhld ret$stack ; restore user stack
0039 f9                  sphl
003a 213100              lxi h,0031h    ; return version number
003d c9                  ret
003e 0d0a2a2a2atest$msg: db cr,lf,'**** echovers ****$'
0053 0000      ret$stack: dw 0
0055                     ds 32          ; 16 level stack
0075                     end

You can prepare the above programs for execution as follows:

1. Assemble the CALLVERS program using MAC as follows:


2. Generate a COM file for CALLVERS with HEXCOM:


3. Assemble the RSX program ECHOVERS using RMAC:


4. Generate a PRL file using the LINK command:


5. Rename the PRL file to an RSX file:


6. Generate a COM file with an attached RSX using the GENCOM command:


7. Run the CALLVERS.COM module:


The message


followed by the message

**** ECHOVERS ****

appears on the screen five times if the RSX program works.

End of Section 4

Continue in...
Top of this document
Appendix A
Section 0

page URL: www.bigfoot.com/~c128page/cpm3-prg/cpm3prg4.htm
contact: c128page@bigfoot.com