home *** CD-ROM | disk | FTP | other *** search
/ ftp.barnyard.co.uk / 2015.02.ftp.barnyard.co.uk.tar / ftp.barnyard.co.uk / cpm / walnut-creek-CDROM / JSAGE / ZSUS / PROGPACK / MEYERTUT.LBR / MEYER06.TZT / MEYER06.TXT
Text File  |  2000-06-30  |  18KB  |  478 lines

  1.              CP/M Assembly Language
  2.                Part VI: Disk Files
  3.               by Eric Meyer
  4.  
  5.      By now you have a good understanding of the basics of 8080
  6. assembly language. It's time to learn to do something truly
  7. useful with it: how to read and write disk files.
  8.  
  9.  
  10. 1. BDOS File Access
  11.      The CP/M BDOS is set up to make access to disk files as easy
  12. as possible. You don't have to worry about where the file is on
  13. the disk, or any of the mechanics of reading and writing blocks
  14. of data. A small handful of BDOS functions take care of all these
  15. tasks:
  16.  
  17. 1.  SET DMA -- designate a 128 byte buffer to hold data;
  18. 2.  OPEN a file -- locate file and prepare to read or write; or
  19.      MAKE a file -- create a new file to write;
  20. 3.  READ or WRITE -- a record (128 bytes) of data (repeat as many
  21.      times as needed);
  22. 4.  CLOSE a file -- finish updating a file written to.
  23.  
  24.      Involved here are two "data structures", used to communicate
  25. with these BDOS functions: the DMA and the FCB.
  26.      The DMA (Direct Memory Access) is simply a small area of
  27. memory where the BDOS is to put (or find) the data that will be
  28. read (or written) when the file is accessed.
  29.      Data is read in units of 128 bytes, called "records". There
  30. are eight of these in 1K. You simply declare a region of memory
  31. to be the DMA area, by passing its address to the BDOS with the
  32. appropriate function call.
  33.      As it happens, the default CP/M DMA is at address 0080h near
  34. the bottom of memory, and you can just use that if you only need
  35. one DMA at a time; otherwise, you need to set your own DMA, which
  36. is as simple as:
  37.  
  38.     LXI    D,MYDMA ; Point to the region you want
  39.     MVI    C,26    ; Function 26 sets the DMA
  40.     CALL    BDOS    ; Ask BDOS to do it
  41.  
  42.      (Remember declare BDOS EQU 0005H.)  You can reserve space
  43. for this (or any other) purpose with the assembler instruction
  44. DS, which we may not have mentioned before, e.g.,:
  45.  
  46. MYDMA:    DS    128    ; One record for DMA
  47.  
  48. sets up a 128-byte buffer at that point for use as a DMA. The
  49. initial contents of a DS block are undefined (unpredictable).
  50.  
  51.  
  52. 2. The FCB
  53.      The FCB (File Control Block) is a structure used by the BDOS
  54. to keep track of your position in a file when it's open.
  55. Basically it is a working copy of the directory entry for the
  56. file, and if you've ever snooped around a disk directory with DU
  57. or some such utility, it will look very familiar:
  58.  
  59. FCB:    d F I L E N A M E T Y P e x x x
  60.     x x x x x x x x x x x x x x x x
  61.     c r r r
  62.  
  63.      The FCB is 36 bytes long. The first byte ("d") is the number
  64. of the drive (00=logged drive, 01=A:, 02=B:, etc). The next 11
  65. bytes are the filename and type. The next byte ("e") is the
  66. current extent number (an "extent" is a unit of 16K; long files
  67. have several extents).
  68.      All the "x" bytes are used by the BDOS to keep track of the
  69. physical location of data on the disk. Their values don't concern
  70. us. The next byte ("c") is the current record number, within the
  71. current extent. And finally, the three bytes "rrr" are for a
  72. random record address (not ordinarily used when accessing a file
  73. sequentially, as we will be doing here).
  74.      You can set up any number of FCBs in your own program, to
  75. read and write files as needed. There is also a default CP/M FCB
  76. at address 005Ch, which is set up when you invoke a program with
  77. the name of any argument you give. For example, when you enter
  78. WordStar with a command like A>ws mytext.fil<cr>, CP/M sets up
  79. the default FCB at 005Ch in the following way:
  80.  
  81. 005C:    0 M Y T E X T _ _ F I L 0 x x x
  82.     x x x x x x x x x x x x x x x x
  83.     0 0 0 0
  84.  
  85.      Thus WordStar can look at this location to see whether you
  86. have given it a filename to open and edit. In this case it sees
  87. that you have: on the logged drive ("00"), in this case A:, the
  88. file MYTEXT.FIL. It can read this file from the beginning (note
  89. extent 0, record 0) just by pointing to this FCB and asking the
  90. BDOS to open the file.
  91.      A further complication arises when you run a program with
  92. two arguments, typically an input and an output file, for
  93. example:
  94.  
  95. A>pip newfil.txt=b:oldfil.txt<cr>
  96.  
  97.      The first file goes into the FCB at 005Ch; the second name,
  98. if present, is put at 006Ch.
  99.  
  100. 005C:    0 N E W F I L _ _ T X T 0 x x x
  101. 006C:    2 O L D F I L _ _ T X T 0 x x x
  102.     0 0 0 0
  103.  
  104.      As you can see, the second name is sitting in the middle of
  105. the default FCB!  What PIP has to do is to copy the second
  106. filename out to another FCB, which it constructs elsewhere. Then
  107. the default FCB at 005Ch can be used to write NEWFIL.TXT, and the
  108. other FCB (in PIP someplace) can be used to read B:OLDFIL.TXT.
  109.  
  110.  
  111. 3. Opening an Existing File
  112.      In order to read an existing disk file, all you have to do
  113. is "open" it. After constructing an appropriate FCB (or using the
  114. default one, if possible), you simply:
  115.  
  116.     LXI    D,FCB    ; Point to the FCB with D-E reg.
  117.     MVI    C,15    ; Function 15 opens a file
  118.     CALL    BDOS    ; Ask BDOS to do it
  119.  
  120.      At this point, if the specified file existed, it can now be
  121. accessed with BDOS read/write functions, described below.
  122. However, it's also possible that the file didn't exist, in which
  123. case you do not want to go on and try to read or write to it!
  124.      All the BDOS file functions leave a "return code" in the A
  125. register, which you can then examine. For the OPEN, CLOSE, and
  126. MAKE functions, a code from 00-03 indicates success, while FFh
  127. indicates an error. So immediately after the BDOS call you should
  128. check this return code, and if you see FFh, exit with an error
  129. message of some sort. For example, you could do this:
  130.  
  131.     CPI    0FFH    ; Is the error code FF?
  132.     JZ    IOERR    ; Jump to i/o error routine if so
  133.  
  134.      In the case of the OPEN function, an error generally means
  135. the file was not found.
  136.  
  137.  
  138. 4. Making a New File
  139.      If you're creating a new file, the process is very similar:
  140. after creating the appropriate FCB, you simply:
  141.  
  142.     LXI    D,FCB    ; Point to the FCB with D-E
  143.     MVI    C,22    ; Function 22 makes a new file
  144.     CALL    BDOS    ; Ask BDOS to do it
  145.  
  146.      Again, you had better check to see that the return code is
  147. not FFh before continuing to write data to your new file. An
  148. error means there was no room in the directory for another
  149. filename.
  150.  
  151.  
  152. 5. Closing a File
  153.      This is a bit out of order here, but: after you have
  154. finished writing data to a file, you need to "close" the file, to
  155. ensure that all the new data has actually been written to disk,
  156. and the directory has been updated accordingly. By now you can
  157. probably guess how this is done:
  158.  
  159.     LXI    D,FCB    ; Point to the FCB with D-E
  160.     MVI    C,16    ; Function 16 closes a file
  161.     CALL    BDOS    ; Ask BDOS to do it
  162.  
  163.      Afterwards, check the return code. If it was FFh, the disk
  164. (or possibly the directory) filled up and some of your data could
  165. not be written.
  166.      NOTE: you only need to close a file that has been written
  167. to. Files opened for reading only need not (and probably
  168. shouldn't) be closed.
  169.  
  170.  
  171. 6. Reading and Writing Sequentially
  172.      Once a file is "open" (or "made") it can be read or written
  173. to. This can be done either randomly or sequentially. Random
  174. access is used when quick access to data is needed, and you are
  175. going to maintain some table or index to tell you in what record
  176. in the file a given individual entry can be found. (This is often
  177. done by database programs.)  Sequential access is far more
  178. common, though, and just goes from beginning to end of the file,
  179. reading or writing the whole thing. All you do is:
  180.  
  181.     LXI    D,FCB            LXI    D,FCB
  182.     MVI    C,20   ; Read Seq.  OR    MVI    C,21    ; Write Seq.
  183.     CALL    BDOS            CALL    BDOS
  184.  
  185.      Each time the record of data in the current DMA (for the
  186. moment, at 0080h) is read from (or written to) the file, and the
  187. record count is updated to point to the next.
  188.      Again there are return codes. A return code of 00 indicates
  189. success. For READ, an error code of 01 means you've passed the
  190. end of the file, and there are no more records to read. Any other
  191. error code indicates a physical error (such as disk full).
  192.  
  193.  
  194. 7. Character Input: GETCH
  195.      Although the BDOS organizes disk i/o in terms of records of
  196. 128 bytes, this is almost never the unit of interest to you. If
  197. you are reading a text file, for example, you will be interested
  198. in the individual character, or possibly line, of text. Thus it
  199. will be convenient to write a pair of functions to "hide" the
  200. BDOS i/o activity, and just pretend that you're reading and
  201. writing one character at a time. We will call them GETCH and
  202. PUTCH. The usage will be:
  203.  
  204. ; Reading a character        ; Writing a character
  205. ;
  206.     CALL    GETCH    ; Get char in A    MVI     A,xx    ; Put char in A
  207.     JC    IOERR    ; Jump if error    CALL  PUTCH    ; Write to file
  208.     CPI    1AH    ; Is it EOF?       JC     IOERR    ; Jump if error
  209.     JZ    ISEOF    ; Jump if EOF
  210.  
  211.      For now, both routines can only be used to read/write ONE
  212. file at a time. Here is the read routine:
  213.  
  214. ; Routine to get character from open file at GCFCB
  215. ; Returns char or EOF in A, and C for Error
  216. ;
  217. GETCH:    PUSH    H    ; Save registers
  218.     PUSH    D    ;   (so GETCH will be easy
  219.     PUSH    B    ;   to use)
  220.     LDA    GCFLG    ; Check EOF flag
  221.     ORA    A    ; Is it clear?
  222.     JNZ    GCEOF    ; If not, at EOF
  223.     LDA    GCPOS    ; Get position count
  224.     CPI    80H    ; Is it up to 128?
  225.     JC    GCCHR    ; No, just go get char
  226.     LXI    D,GCDMA ; Yes, need to read another record
  227.     MVI    C,26    ; Set the DMA to GCDMA
  228.     CALL    BDOS    ; Ask BDOS to do it
  229.     LXI    D,GCFCB ; Use the FCB at GCFCB
  230.     MVI    C,20    ; Read a record sequentially
  231.     CALL    BDOS    ; Ask BDOS to do it
  232.     CPI    1    ; Check return code
  233.     JZ    GCEOF    ; Oops, 1: no more (end of file)
  234.     JNC    GCERR    ; Argh, >1: physical error
  235.     STA    GCPOS    ; 0: read successful, set GCPOS to 0
  236. ;
  237. GCCHR:    LXI    H,GCDMA ; Point to DMA with H-L
  238.     MOV    E,A    ; Move GCPOS into E
  239.     MVI    D,0    ; Now D-E is 16-bit version of GCPOS
  240.     DAD    D    ; GCPOS+DMA points to next char
  241.     MOV    A,M    ; Get that char into A
  242.     LXI    H,GCPOS ; Point to GCPOS with H-L
  243.     INR    M    ; Increment GCPOS to point to next
  244.     CPI    1AH    ; Is it ^Z?
  245.     JZ    GCEOF    ; If so, record it
  246.     STC
  247.     CMC        ; Clear Carry
  248.     JMP    GCRET    ; And return the character
  249. ;
  250. GCEOF:    MVI    A,1AH    ; EOF, get a ^Z
  251.     STA    GCFLG    ; Set flag for future reference
  252.     STC
  253.     CMC        ; Clear Carry
  254.     JMP    GCRET    ; Return the ^Z
  255. ;
  256. GCERR:    MVI    A,1AH    ; ERROR, get a ^Z
  257.     STA    GCFLG    ; Set flag
  258.     STC        ; And set Carry
  259. ;
  260. GCRET:    POP    B    ; Restore the registers
  261.     POP    D
  262.     POP    H
  263.     RET        ; And return
  264. ;
  265. GCFLG:    DS    1    ; Flag says EOF reached
  266. GCPOS:    DS    1    ; Keep track of position in record
  267. GCDMA:    DS    128    ; DMA for GETCH, 128 bytes
  268. GCFCB:    DS    36    ; FCB for GETCH
  269.  
  270.      Let's discuss how this works. The key is the byte variable
  271. GCPOS, which runs from 00. . .7FH to keep track of which is the
  272. next byte in the record (in the DMA) to be read. GCPOS starts out
  273. at 80H, which indicates that a new record must be read in.
  274.      Once this is done, GCPOS is reset to 0. Then each time a
  275. character is needed, it is found by adding GCPOS to the DMA
  276. address, and GCPOS is incremented to point to the next.
  277.      When GCPOS reaches 80H again, the whole record has been
  278. used, and a new one must once again be read in.
  279.      Ordinarily, GETCH simply returns with the character read in
  280. A, and the C flag clear.
  281.      But once the end of the file has been reached (either no
  282. more records, or the EOF character 1Ah), GETCH returns with an
  283. EOF. (Note the variable GCFLG, a flag that is set non-zero once
  284. this occurs, so that GETCH will continue to return EOFs
  285. thereafter.)  And if an error is encountered trying to read the
  286. file, the C flag is returned.
  287.      You have seen most of these instructions before, but there
  288. are a couple of new ones here.
  289.      LDA and STA are rather like MOV A,M and MOV M,A:  they fetch
  290. or store the value in A to a memory address, but to the address
  291. you specify directly, instead of to the address in H-L.
  292.      Thus STA GCPOS stores the value in A to address GCPOS, and
  293. LDA GCPOS gets it back.
  294.      STC and CMC are the instructions to "set Carry" and
  295. "complement Carry"; they only affect the Carry flag.
  296.      There is no simple "clear Carry" instruction, which is what
  297. we really want to do here; you have to first set it, then
  298. complement it. ("Complement" means, roughly, "reverse".)
  299.  
  300.  
  301. 8. Character Output: PUTCH
  302.      Here now is the complementary character output routine:
  303.  
  304. ; Routine to write character in A to open file at PCFCB
  305. ; Returns C for write Error
  306. ;
  307. PUTCH:    PUSH    H    ; Save registers
  308.     PUSH    D    ;   (so PUTCH will be
  309.     PUSH    B    ;   easy to use)
  310.     MOV    C,A    ; Save the character in C
  311.     LDA    PCPOS    ; Get position count
  312.     CPI    80H    ; Is it up to 128?
  313.     JC    PCCHR    ; No, just go write char
  314.     PUSH    B    ; Preserve character in C
  315.     LXI    D,PCDMA ; Need to write the record out
  316.     MVI    C,26    ; Set the DMA to PCDMA
  317.     CALL    BDOS    ; Aask BDOS to do it
  318.     LXI    D,PCFCB ; Use the FCB at PCFCB
  319.     MVI    C,21    ; Write a record sequentially
  320.     CALL    BDOS    ; Ask BDOS to do it
  321.     POP    B    ; Restore character in C
  322.     CPI    0    ; Check return code
  323.     JNZ    PCERR    ; Argh, >0: physical error
  324.     STA    PCPOS    ; 0: write successful, set PCPOS to 0
  325. ;
  326. PCCHR:    LXI    H,PCDMA ; Point to DMA with H-L
  327.     MOV    E,A    ; Move PCPOS into E
  328.     MVI    D,0    ; Now D-E is 16-bit version of PCPOS
  329.     DAD    D    ; PCPOS+DMA points to next char
  330.     MOV    M,C    ; Put outgoing char in its place
  331.     LXI    H,PCPOS ; Point to PCPOS with H-L
  332.     INR    M    ; Increment PCPOS to point to next
  333.     SUB    A    ; Clear Carry flag, all is OK
  334.     JMP    PCRET
  335. ;
  336. PCERR:    STC        ; Write error, set Carry
  337. ;
  338. PCRET:    POP    B    ; Restore the registers
  339.     POP    D
  340.     POP    H
  341.     RET        ; And return
  342. ;
  343. PCPOS:    DS    1    ; Keep track of position in record
  344. PCDMA:    DS    128    ; DMA for PUTCH, 128 bytes
  345. PCFCB:    DS    36    ; FCB for PUTCH
  346. ;
  347.  
  348.      By and large, this is very similar to GETCH.
  349.      Note the need to PUSH and POP the outgoing character, so it
  350. isn't lost when we do the BDOS calls to write a record
  351. periodically.
  352.      Also note that PCPOS starts off at 0, not 80H, because you
  353. don't need to write a record until it's full (whereas in GETCH,
  354. we needed to begin by reading a record).
  355.  
  356.  
  357. 9. Using GETCH and PUTCH
  358.      To use these routines properly, we will need a few routines
  359. to open, make, and close files properly for them. First, for
  360. GETCH, we need a routine to open the file for reading:
  361.  
  362. ; Routine to open a file for GETCH (takes DE=FCB)
  363. ; Returns C if file not found
  364. ;
  365. GCOPEN: MVI    A,80H    ; Initialize GCPOS to 80H
  366.     STA    GCPOS    ; So first record will be read
  367.     SUB    A    ; And zero the EOF flag
  368.     STA    GCFLG
  369.     XCHG        ; Put FCB address in HL
  370.     LXI    D,GCFCB ; We'll move it into GCFCB
  371.     MVI    B,12    ; The drive/filename is 12 bytes
  372. ;
  373. GCL1:    MOV    A,M    ; Fetch a byte from FCB
  374.     STAX    D    ; Store it in GCFCB
  375.     INX    D    ; Point to next
  376.     INX    H    ; Iin both places
  377.     DCR    B    ; Count down on 12 bytes
  378.     JNZ    GCL1    ; Loop if more
  379.     SUB    A    ; Now get a 0
  380.     STAX    D    ; And put it into extent ("e")
  381.     STA    GCFCB+32 ;And also into record ("r")
  382.     LXI    D,GCFCB ; Point to start of GCFCB again
  383.     MVI    C,15    ; Function 15 opens a file
  384.     CALL    BDOS    ; Ask BDOS to do it
  385.     CPI    0FFH    ; Check return code
  386.     CMC        ; Now Carry is set if it was FFH
  387.     RET        ; Return with Carry set if error
  388.  
  389.      This routine simply copies the filename from the FCB address
  390. given to it in the D-E register, into the GCFCB that will be used
  391. by GETCH; then it tries to open the file, returning with the C
  392. flag set if it could not.
  393.      There is one new instruction here. STAX D (and its relative,
  394. STAX B) are just like MOV M,A, except that they use the D-E (or
  395. B-C) registers as pointers instead of H-L. Similarly, there are
  396. instructions LDAX D and LDAX B, which load values as does MOV
  397. A,M. (Unfortunately, these arbitrary names make them look
  398. completely different.)
  399.      The routine to make a new file for use by PUTCH will be very
  400. similar. For now, it simply erases any pre-existing file of the
  401. given name; later we may add code to preserve such a file
  402. instead, possibly renaming it to a ".BAK" file.
  403.  
  404. ; Routine to make a file for PUTCH (takes DE=FCB)
  405. ; Returns C if cannot make file
  406. ;
  407. PCOPEN: SUB    A    ; Initialize PCPOS to 0
  408.     STA    PCPOS    ; For beginning to write with PUTCH
  409.     PUSH    D    ; Preserve FCB address
  410.     MVI    C,19    ; Function 19 ERASES a file
  411.     CALL    BDOS    ; Ask BDOS to do it
  412.     POP    H    ; Rrecover FCB address into HL
  413.     LXI    D,PCFCB ; We'll move it into PCFCB
  414.     MVI    B,12    ; The drive/filename is 12 bytes
  415. ;
  416. PCL1:    MOV    A,M    ; Fetch a byte from FCB
  417.     STAX    D    ; Store it in PCFCB
  418.     INX    D    ; Point to next
  419.     INX    H    ; In both places
  420.     DCR    B    ; Count down on 12 bytes
  421.     JNZ    PCL1    ; Loop if mor
  422.     SUB    A    ; Now get a  0
  423.     STAX    D    ; And put it into extent ("e")
  424.     LXI    H,20    ; Point ahead 20 bytes
  425.     DAD    D    ; HL now points to record ("r")
  426.     MOV    M,A    ; Put 0 there, too
  427.     LXI    D,PCFCB ; Point to start of GCFCB again
  428.     MVI    C,22    ; Function 22 makes a file
  429.     CALL    BDOS    ; Ask BDOS to do it
  430.     CPI    0FFH    ; Check return code
  431.     CMC        ; Now Carry is set if it was FFH
  432.     RET        ; Return with Carry set if error
  433.  
  434.      Note the use of BDOS function 19 to delete any file with the
  435. same name before we try to make the new file. We don't care about
  436. any error that may result: if the file did exist, it's now gone;
  437. if it didn't, the attempt to erase it failed, but that's OK too.
  438. Use function 19 with caution; we'll say no more about it here.
  439.      Finally, we need a routine to clean up after PUTCH, when
  440. we're all done writing, and close the file. The task is
  441. complicated by the fact that there may still be characters
  442. sitting in the PCDMA buffer, that need to be written before we
  443. close the file. Here is the routine:
  444.  
  445. ; Routine to close a file for PUTCH (no arguments)
  446. ; Returns C if cannot close file
  447. ;
  448. PCLOSE: MVI    A,1AH    ; Get a 1AH (EOF char)
  449.     CALL    PUTCH    ; And write it to the file
  450.     LDA    PCPOS    ; Now look at position in PCDMA
  451.     ORA    A    ; Is it 00?
  452.     JZ    PCLFIL    ; If so, no data, just close file
  453.     LXI    D,PCDMA ; Need to write the last record out
  454.     MVI    C,26    ; Set the DMA to PCDMA
  455.     CALL    BDOS    ; Ask BDOS to do it
  456.     LXI    D,PCFCB ; Use the FCB at PCFCB
  457.     MVI    C,21    ; Write a record sequentially
  458.     CALL    BDOS    ; Ask BDOS to do it
  459.     ORA    A    ; Check return code
  460.     JNZ    PCLERR    ; Warn if error
  461. ;
  462. PCLFIL: LXI    D,PCFCB ; All set, point to the FCB
  463.     MVI    C,16    ; Function 16 closes a file
  464.     CALL    BDOS    ; Ask BDOS to do it
  465.     CPI    0FFH    ; Examine return code
  466.     CMC        ; Carry now set if was 0FFH
  467.     RET        ; Return
  468. ;
  469. PCLERR: STC        ; Set Carry for write error
  470.     RET        ; And return
  471.  
  472.      By now this should all be pretty self-explanatory.
  473.  
  474.  
  475. 10. Things To Come
  476.      Take a deep breath!  Next time we'll use these routines to
  477. construct "filter" programs that read and process text files.
  478.