CHAPTER 6 - USING DOS FROM ASSEMBLY LANGUAGE CAVEAT This chapter is aimed at the advanced assembly language programmer who wishes to access the disk without resorting to the PRINT statement scheme used with BASIC. Accordingly, the topics covered here may be beyond the comprehension (at least for the present) of a programmer who has never used assembly language. DIRECT USE OF DISK DRIVE It is often desirable or necessary to access the Apple's disk drives directly from assembly language, without the use of DOS. This is done using a section of 16 addresses that are latched toggles, interfacing directly to the hardware. There are eight two byte toggles that essentially represent pulling a TTL line high or low. Applications which could use direct disk access range from a user written operating system to DOS-independent utility programs. The device address assignments are given in Figure 6.1. ADDRESS LABEL DESCRIPTION --------------------------------------------------------------- $C080 PHASEOFF Stepper motor phase 0 off. $C081 PHASEON Stepper motor phase 0 on. $C082 PHASE1OFF Stepper motor phase 1 off. $C083 PHASE1ON Stepper motor phase 1 on. $C084 PHASE2OFF Stepper motor phase 2 off. $C085 PHASE2ON Stepper motor phase 2 on. $C086 PHASE3OFF Stepper motor phase 3 off. $C087 PHASE3ON Stepper motor phase 3 on. $C088 MOTOROFF Turn motor off. $C089 MOTORON Turn motor on. $C08A DRV0EN Engage drive 1. $C08B DRV1EN Engage drive 2. $C08C Q6L Strobe Data Latch for I/O. $C08D Q6H Load Data Latch. $C08E Q7L Prepare latch for input. $C08F Q7H Prepare latch for output. Q7L followed by Q6L = Read Q7L followed by Q6H = Sense Write Protect Q7H followed by Q6L = Write Q7H followed by Q6H = Load Write Latch *** figure 6.1 *** The addresses are slot dependent and the offsets are computed by multiplying the slot number by 16. In hexadecimal this works out nicely and we can add the value $s0 (where s is the slot number) to the base address. If we wanted to engage disk drive number 1 in slot number 6, for example, we would add $60 to $C08A (device address assignment for engaging drive 1) for a result of $C0EA. However, since it is generally desirable to write code that is not slot dependent, one would normally use $C08A,X (where the X register contains the value $s0). In general, the above addresses need only be accessed with any valid 6502 instruction. However, in the case of reading and writing bytes, care must be taken to insure that the data will be in an appropriate register. All of the following would engage drive number 1. (Assume slot number 6) LDA $C0EA BIT $C08A,X (where X-reg contains $60) CMP $C08A,X (where X-reg contains $60) Below are typical examples demonstrating the use of the device address assignments. For more examples, see APPENDIX A. Slot 6 is assumed and the X-register contains $60. STEPPER PHASE OFF/ON: Basically, each of the four phases (0-3) must be turned on and then off again. Done in ascending order, this moves the arm inward. In descending order, this moves the arm outward. The timing between accesses to these locations is critical, making this a non-trivial exercise. It is recommended that the SEEK command in RWTS be used to move the arm. See the section on using RWTS immediately following. MOTOR OFF/ON: LDA $C088,X Turn motor off. LDA $C089,X Turn motor on. NOTE: A sufficient delay should be provided to allow the motor time to come up to speed. Shugart recommends one second, but DOS is able to reduce this delay by watching the read latch until data starts to change. ENGAGE DRIVE 1/2: LDA $C08A,X Engage drive 1. LDA $C08B,X Engage drive 2. READ A BYTE: READ LDA $C08C,X BPL READ NOTE: $C08E,X must already have been accessed to assure Read mode. The loop is necessary to assure that the accumulator will contain valid data. If the data latch does not yet contain valid data the high bit will be zero. SENSE WRITE PROTECT: LDA $C08D,X LDA $C08E,X Sense write protect. BMI ERROR If high bit set, protected. WRITE LOAD AND WRITE A BYTE LDA DATA STA $C08D,X Write load. ORA $C08C,X Write byte. NOTE: $C08F,X must already have been accessed to insure Write mode and a 100 microsecond delay should be invoked before writing. Due to hardware constraints, data bytes must be written in 32 cycle loops. Below is an example for an immediate load of the accumulator, followed by a write. Timing is so critical that different routines may be necessary, depending on how the data is to be accessed, and code can not cross memory page boundaries without an adjustment. LDA #$D5 (3 cycles) JSR WRITE9 (6) LDA #$AA (3) JSR WRITE9 (6) . . . WRITE9 CLC (2) WRITE7 PHA (3) PLA (4) WRITE STA $C08D,X (5) ORA $C08C,X (4) RTS (6) CALLING READ/WRITE TRACK/SECTOR (RWTS) Read/Write Track/Sector (RWTS) exists in every version of DOS as a collection of subroutines, occupying roughly the top third of the DOS program. The interface to RWTS is standardized and thoroughly documented by Apple and may be called by a program running outside of DOS. There are two subroutines which must be called or whose function must be performed. JSR $3E3 - When this subroutine is called, the Y and A registers are loaded with the address of the Input/Output control Block (IOB) used by DOS when accessing RWTS. The low order part of the address is in Y and the high order part in A. This subroutine should be called to locate the IOB and the results may be stored in two zero page locations to allow storing values in the IOB and retrieving output values after a call to RWTS. Of course, you may set up your own IOB as long as the Y and A registers point to your IOB upon calling RWTS. JSR $3D9 - This is the main entry to the RWTS routine. Prior to making this call, the Y and A registers must be loaded with the address of an IOB describing the operation to be performed. This may be done by first calling $3E3 as described above. The IOB must contain appropriate information as defined in the list on the facing page (offsets are given in hexadecimal): INPUT/OUTPUT CONTROL BLOCK - GENERAL FORMAT BYTE DESCRIPTION 00 Table type, must be $01 01 Slot number times 16 (s0: s=slot. Example: $60) 02 Drive number ($01 or $02) 03 Volume number expected ($00 matches any volume) 04 Track number ($00 through $22) 05 Sector number ($00 through $0F) 06-07 Address (LO/HI) of the Device Characteristics Table 08-09 Address (LO/HI) of the 256 byte buffer for READ/WRITE 0A Not used 0B Byte count for partial sector ($00 for 256 bytes) 0C Command code $00 = SEEK $01 = READ $02 = WRITE $04 = FORMAT 0D Return code - The processor CARRY flag is set upon return from RWTS if there is a non-zero return code: $00 = No errors $08 = Error during initialization $10 = Write protect error $20 = Volume mismatch error $40 = Drive error $80 = Read error (obsolete) 0E Volume number of last access (must be initialized) 0F Slot number of last access*16 (must be initialized) 10 Drive number of last access (must be initialized) DEVICE CHARACTERISTICS TABLE BYTE DESCRIPTION 00 Device type (should be $00 for DISK II) 01 Phases per track (should be $01 for DISK II) 02-03 Motor on time count (should be $EFD8 for DISK II) RWTS IOB BY CALL TYPE SEEK Move disk arm to desired track Input: Byte 00 - Table type ($01) 01 - Slot number * 16 (s0: s=slot) 02 - Drive number ($01 or $02) 04 - Track number ($00 through $22) 06/07 - Pointer to the DCT 0C - Command code for SEEK ($00) 0F - Slot number of last access * 16 10 - Drive number of last access Output: Byte 0D - Return code (See previous definition) 0F - Current Slot number * 16 10 - Current Drive number READ Read a sector into a specified buffer Input: Byte 00 - Table type ($01) 01 - Slot number * 16 (s0: s=slot) 02 - Drive number ($01 or $02) 03 - Volume number ($00 matches any volume) 04 - Track number ($00 through $22) 05 - Sector number ($00 through $0F) 06/07 - Pointer to the DCT 08/09 - Pointer to 256 byte user data buffer 0B - Byte count per sector ($00) 0C - Command code for READ ($01) 0E - Volume number of last access 0F - Slot number of last access * 16 10 - Drive number of last access Output: Byte 0D - Return code (See previous definition) 0E - Current Volume number 0F - Current Slot number * 16 10 - Current Drive number WRITE Write a sector from a specified buffer Input: Byte 00 - Table type ($01) 01 - Slot number * 16 (s0: s=slot) 02 - Drive number ($01 or $02) 03 - Volume number ($00 matches any volume) 04 - Track number ($00 through $22) 05 - Sector number ($00 through $0F) 06/07 - Pointer to the DCT 08/09 - Pointer to 256 byte user data buffer 0B - Byte count per sector ($00) 0C - Command code for WRITE ($02) 0E - Volume number of last access 0F - Slot number of last access * 16 10 - Drive number of last access Output: Byte 0D - Return code (See previous definition) 0E - Current Volume number 0F - Current Slot number * 16 10 - Current Drive number FORMAT Initialize the diskette (does not put DOS on disk, create a VTOC/CATALOG, or store HELLO program) Input: Byte 00 - Table type ($01) 01 - Slot number * 16 (s0: s=slot) 02 - Drive number ($01 or $02) 03 - Volume number ($00 will default to 254) 06/07 - Pointer to the DCT 0C - Command code for FORMAT ($04) 0E - Volume number of last access 0F - Slot number of last access * 16 10 - Drive number of last access Output: Byte 0D - Return code (See previous definition) 0E - Current Volume number 0F - Current Slot number * 16 10 - Current Drive number CALLING THE DOS FILE MANAGER The DOS file manager exists in every version of DOS as a collection of subroutines occupying approximately the central third of the DOS program. The interface to these routines is generalized in such a way that they may be called by a program running outside of DOS. The definition of this interface has never been published by APPLE (or anyone else, for that manner) but since the calls can be made through fixed vectors, and, the format of the parameter lists passed have not changed in all the versions of DOS, these routines may be relied upon as "safe". Indeed, the new FID utility program uses these routines to process files on the diskette. There are two subroutines which must be called in order to access the file manager. JSR $3DC - When this subroutine is called, the Y and A registers are loaded with the address of the file manager parameter list. The low order part of the address is in Y and the high order part in A. This subroutine must be called at least once to locate this parameter list and the results may be stored in two zero page locations to allow the programmer to set input values in the parameter list and to locate output values there after file manager calls. JSR $3D6 - This is the main entry to the file manager. Prior to making this call the parameter list, located using the call described above, must be completed appropriately, depending upon the type of call, and the X register must be set to either zero or non-zero as follows: X = 0 - If file is not found, allocate it X # 0 - If file is not found, do not allocate one Normally, X should be zero on an OPEN call for a new file and non-zero for all other call types. Three buffers must be provided to the file manager by the programmer, allocated by him in his memory. These buffers, together, occupy 557 bytes of RAM, and must be passed to the file manager each time their associated file is used. A separate set of these buffers must be maintained for each open file. DOS maintains buffers for this purpose, as described in earlier chapters, in high RAM. These buffers may be "borrowed" from DOS if care is taken to let DOS know about it. A method for doing this will be outlined later. A chart giving the required inputs for each call type to the file manager is given in Figure 6.2. The general format of the file manager parameter list is as follows: FILE MANAGER PARAMETER LIST - GENERAL FORMAT BYTE DESCRIPTION 00 Call type: 01=OPEN 05=DELETE 09=RENAME 02=CLOSE 06=CATALOG 0A=POSITION 03=READ 07=LOCK 0B=INIT 04=WRITE 08=UNLOCK 0C=VERIFY 01 Sub-call type for READ or WRITE: 00=No operation (ignore call entirely) 01=READ or WRITE one byte 02=READ or WRITE a range of bytes 03=POSITION then READ or WRITE one byte 04=POSITION then READ/WRITE a range 02-09 Parameters specific to the call type used. See FILE MANAGER PARAMETER LIST BY CALL TYPE below. 0A Return code (note: not all return codes can occur for any call type). The processor CARRY flag is set upon return from the file manager if there is a non-zero return code: 00=No errors 01=Not used ("LANGUAGE NOT AVAILABLE") 02=Bad call type 03=Bad sub-call type (greater than four) 04=WRITE PROTECTED 05=END OF DATA 06=FILE NOT FOUND (was allocated if X=0) 07=VOLUME MISMATCH 08=DISK I/O ERROR 09=DISK FULL 0A=FILE LOCKED 0B Not used 0C-0D Address of a 45 byte buffer which will be used by the file manager to save its status between calls. This area is called the file manager workarea and need not be initialized by the caller but the space must be provided and this two byte address field initialized. (addresses are in low/high order format) 0E-0F Address of a 256 byte buffer which will be used by the file manager to maintain the current Track/Sector List sector for the open file. Buffer itself need not be initialized by the caller. 10-11 Address of a 256 byte buffer which will be used by the file manager to maintain the data sector buffer. Buffer need not be initialized by the caller. *** INSERT FIGURE 6.2 *** FILE MANAGER PARAMETER LIST BY CALL TYPE OPEN Locates or creates a file. A call to POSITION should follow every OPEN. Input: Byte 00 - 01 02/03 - Fixed record length or 0000 if variable 04 - Volume number or 00 for any volume 05 - Drive number to be used (01 or 02) 06 - Slot number to be used (01-07) 07 - File type (used only for new files) $00 = TEXT $01 = INTEGER BASIC $02 = APPLESOFT BASIC $04 = BINARY $08 = RELOCATABLE $10 = S TYPE FILE $20 = A TYPE FILE $40 = B TYPE FILE 08/09 - Address of file name (30 characters) (Low/high format) 0C/0D - Address of file manager workarea buffer 0E/0F - Address of T/S List sector buffer 10/11 - Address of data sector buffer Output: Byte 07 - File type of file which was OPENed 0A - Return code (see previous definitions) CLOSE Write out final sectors, update the Catalog. A CLOSE call is required eventually for every OPEN. Input: Byte 00 - 02 0C/0D - Address of file manager workarea buffer 0E/0F - Address of T/S List sector buffer 10/11 - Address of data sector buffer Output: Byte 0A - Return code READ Read one or a range of bytes from the file to memory. WRITE Write one or a range of bytes from memory to the file. Input: Byte 00 - 03 (READ) 04 (WRITE) 01 - Subcode: 00 = No operation 01 = READ or WRITE one byte only 02 = READ or WRITE a range of bytes 03 = POSITION then READ/WRITE one byte 04 = POSITION then READ/WRITE range 02/03 - (Subcodes 03 or 04) Record number 04/05 - (Subcodes 03 or 04) Byte offset 06/07 - (Subcodes 02 or 04) Number of bytes in range to be read or written. (Note: for WRITE, this length must be one less than the actual length to be written) 08/09 - (Subcodes 02 or 04) Address of range of bytes to be written or address of buffer to which bytes are to be read. 08 - (WRITE, Subcodes 01 or 03) Single byte to be written. 0C/0D - Address of file manager workarea buffer 0E/0F - Address of T/S List sector buffer 10/11 - Address of data sector buffer Output: Byte 02/03 - Record number of current file position 04/05 - Byte offset of current position in file 08 - (READ, Subcodes 01 or 03) Byte read 0A - Return code DELETE Locate and delete a file, freeing its sectors. Input: Byte 00 - 05 (remainder are the same as with OPEN call type) Output: Byte 0A - Return code CATALOG Produce a catalog listing on the output device. Input: Byte 00 - 06 05 - Drive 06 - Slot 0C/0D - Address of file manager workarea buffer 0E/0F - Address of T/S List sector buffer 10/11 - Address of data sector buffer Output: Byte 0A - Return code LOCK Lock a file. Input: Byte 00 - 07 (remainder are the same as with OPEN call type) Output: Byte 0A - Return code UNLOCK Unlock a file. Input: Byte 00 - 08 (remainder are the same as with OPEN call type) Output: Byte 0A - Return code RENAME Rename a file. Input: Byte 00 - 09 02/03 - Address of new file name (30 bytes) (remainder are the same as with OPEN call type) Output: Byte 0A - Return code POSITION Calculate the location of a record and/or byte offset in the file. Position such that next READ or WRITE will be at that location in the file. A call to POSITION (either explicitly or implictly using subcodes of READ or WRITE) is required prior to the first READ or WRITE. Bytes 02 through 05 should be set to zeros for a normal position to the beginning of the file. Input: Byte 00 - 0A 02/03 - Relative record number for files with a fixed length record size or zero. First record of file is record 0000. 04/05 - Relative byte offset into record or of entire file if record number is zero. 0C/0D - Address of file manager workarea buffer. 0E/0F - Address of T/S List sector buffer. 10/11 - Address of data sector buffer. Output: Byte 0A - Return code INIT Initialize a slave diskette. This function formats a diskette and writes a copy of DOS onto tracks 0-2. A VTOC and Catalog are also created. A HELLO program is not stored, however. Input: Byte 00 - 0B 01 - First page of DOS image to be copied to the diskette. Normally $9D for a 48K machine. 04 - Volume number of new diskette. 05 - Drive number (01 or 02) 06 - Slot number (01-07) 0C/0D - Address of file manager workarea buffer. 0E/0F - Address of T/S List sector buffer. 10/11 - Address of data sector buffer. Output: Byte 0A - Return code VERIFY Verify that there are no bad sectors in a file by reading every sector. Input: Byte 00 - 0C (remainder are the same as the OPEN call type) Output: Byte 0A - Return code DOS BUFFERS Usually it is desirable to use one of DOS's buffers when calling the file manager to save memory. DOS buffers consist of each of the three buffers used by the file manager (file manager workarea, T/S List sector, and data sector) as well as a 30 byte file name buffer and some link pointers. All together a DOS buffer occupies 595 bytes of memory. The address of the first DOS buffer is stored in the first two bytes of DOS ($9D00 on a 48K APPLE II). The address of the next buffer is stored in the first and so on in a chain of linked elements. The link address to the next buffer in the last buffer is zeros. If the buffer is not being used by DOS, the first byte of the file name field is a hex 00. Otherwise, it contains the first character of the name of the open file. The assembly language programmer should follow these conventions to avoid having DOS reuse the buffer while he is using it. This means that the name of the file should be stored in the buffer to reserve it for exclusive use (or at least a non-zero byte stored on the first character) and later, when the user is through with the buffer, a 00 should be stored on the file name to return it to DOS's use. If the later is not done, DOS will eventually run out of available buffers and will refuse even to do a CATALOG command. A diagram of the DOS buffers for MAXFILES 3 is given in Figure 6.3 and the format of a DOS buffer is given below. *** INSERT FIGURE 6.3 *** DOS BUFFER FORMAT BYTE DESCRIPTION 000/0FF Data sector buffer (256 bytes in length) 100/1FF T/S List sector buffer (256 bytes in length) 200/22C File manager workarea buffer (45 bytes in length) 22D/24A File name buffer (30 bytes in length) First byte indicates whether this DOS buffer is being used. If hex 00, buffer is free for use. 24B/24C Address (Lo/High) of file manager workarea buffer 24D/24E Address of T/S List sector buffer 24F/250 Address of data sector buffer 251/252 Address of the file name field of the next buffer on the chain of buffers. If this is the last buffer on the chain then this field contains zeros. THE FILE MANAGER WORKAREA The file manager workarea contains the variables which, taken together, constitute all of the information the file manager needs to deal with an open file. Each time the file manager finishes processing a call, it copies all of its important variables into the file manager workarea buffer provided by the caller. Each subsequent time the file manager is called, the first thing it does is to copy the contents of the file manager workarea buffer back into its variables so that it may resume processing for the file where it left off on the previous call. Ordinarily, the programmer will have no need to worry about the contents of this workarea, since most of the useful information is present in the parameter list anyway. Occasionally, it is handy to know more about the open file. For these cases, the format of the file manager workarea is given below: FILE MANAGER WORKAREA FORMAT BYTE DESCRIPTION 00/01 Track/Sector of first T/S List for file 02/03 Track/Sector of current T/S List for file 04 Flags: 80=T/S List buffer changed and needs writing 40=Data buffer has been changed and needs writing 02=Volume freespace map changed and needs writing 05/06 Track/Sector of current data sector 07 Sector offset into catalog to entry for this file 08 Byte offset into catalog sector to entry for file 09/0A Maximum data sectors represented by one T/S List 0B/0C Offset of first sector in current T/S List 0D/0E Offset of last sector in current T/S List 0F/10 Relative sector number last read 11/12 Sector size in bytes (256) 13/14 Current position in sectors (relative) 15 Current byte offset in this sector 16 Not used 17/18 Fixed record length 19/1A Current record number 1B/1C Byte offset into current record 1D/1E Length of file in sectors 1F Next sector to allocate on this track 20 Current track being allocated 21/24 Bit map of available sectors on this track (rotated) 25 File type (80=locked) 0,1,2,4=T,I,A,B 26 Slot number times 16 (example: $60=slot 6) 27 Drive number (01 or 02) 28 Volume number (complemented) 29 Track 2A/2C Not used COMMON ALGORITHMS Given below are several pieces of code which are used when working with DOS: LOCATE A FREE DOS BUFFER The following subroutine may be used to locate an unallocated DOS buffer for use with the DOS file manager. FBUFF LDA $3D2 LOCATE DOS LOAD POINT STA $1 LDY #0 STY $0 * GBUF0 LDA ($0),Y LOCATE NEXT DOS BUFFER PHA INY LDA ($0),Y STA $1 PLA STA $0 BNE GBUF GOT ONE LDA $1 BEQ NBUF NO BUFFERS FREE * GBUF LDY #0 GET FILENAME LDA ($0),Y BEQ GOTBUF ITS FREE LDY #36 ITS NOT FREE BNE GBUF0 GO GET NEXT BUFFER * GOTBUF CLC INDICATE-GOT A FREE BUFFER RTS RETURN TO CALLER NBUF SEC INDICATE-NO FREE BUFFERS RTS RETURN TO CALLER IS DOS IN THE MACHINE? The following series of instructions should be used prior to attempting to call RWTS or the file manager to insure that DOS is present on this machine. LDA $3D0 GET VECTOR JMP CMP #$4C IS IT A JUMP? BNE NODOS NO, DOS NOT LOADED WHICH VERSION OF DOS IS ACTIVE? In case the program has version dependent code, a check of the DOS version may be required: CLC LDA #0 ADD $16BE TO DOS LOAD POINT ADC #$BE STA $0 LDA $3D2 ADC #$16 STA $1 LDY #0 LDA ($0),Y GET DOS VERSION NUMBER (2 OR 3) WHICH BASIC IS SELECTED? Some programs depend upon either the INTEGER BASIC ROM or the APPLESOFT ROM. To find out which is active and select the one desired, the following subroutine can be called. First the A register is loaded with a code to indicate which BASIC is desired. $20 is used for INTEGER BASIC and $4C is used for APPLESOFT. To set up for APPLESOFT, for example: LDA #$4C CODE FOR APPLESOFT JSR SETBSC CALL SUBROUTINE BNE ERROR LANGUAGE NOT AVAILABLE . . . SETBSC CMP $E000 CORRECT BASIC ALREADY THERE? BEQ RTS YES STA $C080 NO, SELECT ROM CARD CMP $E000 NOW DO WE HAVE IT? BEQ RTS YES STA $C081 NO, TRY ROM CARD OUT CMP $EOOO GOT IT NOW? RTS RTS IN ANY CASE, EXIT TO CALLER SEE IF A BASIC PROGRAM IS IN EXECUTION To determine if there is a BASIC program running or if BASIC is in immediate command mode, use the following statements: ..IF INTEGER BASIC IS ACTIVE... LDA $D9 BMI EXEC PROGRAM EXECUTING BPL NOEXEC PROGRAM NOT EXECUTING ..IF APPLESOFT BASIC IS ACTIVE... LDX $76 GET LINE NUMBER INX BEQ NOEXEC PROGRAM NOT EXECUTING LDX $33 GET PROMPT CHARACTER CPX #$DD PROMPT IS A "]"? BEQ NOEXEC YES, NOT EXECUTING BNE EXEC ELSE, PROGRAM IS EXECUTING .nx ch7