Apple II Technical Notes _____________________________________________________________________________ Developer Technical Support Pascal #16: Driver to Have Two Volumes on One 3.5" Disk Revised by: Guillermo Ortiz, Cheryl Ewy & Dan Strnad November 1988 Written by: Guillermo Ortiz October 1986 This Technical Note discusses how to install a driver to have more than one volume on a 3.5" 800K disk under Apple II Pascal. _____________________________________________________________________________ For the sake of simplicity,.we will limit the discussion to the following case: we want to have two 400K volumes on the boot 3.5" disk. For such a scenario, Unit #4 occupies the first 800 blocks and Unit #20 uses blocks 800 to 1599 as shown here: First Volume Unit #4 Second Volume Unit #4 __|_________________________________|___________________________________|__ | Blocks (0 .. 799) | Blocks (800 .. 1599) | +-- Directory Unit #4 +-- Directory Unit #20 | blocks (2 .. 5) | blocks (802 .. 805) | | ___________________________________________________________________________ | | | | | | | |_|___|_____________________________|_|___|_____________________________|__ | \ | \ | \ Boot Blocks (0 .. 1) | \ Pseudo Boot Blocks (800 .. 801) Figure 1-Block Diagram for 3.5" Disk There are four calls a device driver has to handle, UNITCLEAR, UNITSTATUS, UNITREAD, and UNITWRITE. For the first one, our driver will only return since the device is already on-line. For a blocked device, UNITSTATUS returns the number of blocks available, in this case UNITSTATUS (20) = 800. In the case of UNITREAD and UNITWRITE, all the driver has to do is add the offset of 800 to the number of the block requested then jump to the BIOS routine with the unit number set to four. Our driver is basically a dispatcher that directs the disk access to the proper blocks. When this driver is present, the application must be very careful about making sure the right disk is in the drive when accessing the second volume; any access to Unit #20 could damage a normal volume present in the drive. Once the driver is ready, it is necessary to format a disk with the special directories. With the listings for the driver we have included the source of a sample formatting program. Once the disk is ready we proceed to transfer all system files to it including SYSTEM.ATTACH, ATTACH.DRIVERS (containing our driver), and ATTACH.DATA. This last file reflects the following information: Driver Name - FAKEDISK - Not Aligned Attached to #20 {Can change if desired} Unit #s to be init at boot time - 20 This driver CAN be placed in the first HiRes screen {Change if needed} This driver CAN be placed in the second HiRes screen{Change if needed} This driver does not use interrupts Driver does not have transient initialization code The code has comments that explain it fairly well; for more information on drivers in general and how to use the attach tools please refer to Apple II Pascal Device and Interrupt Support Tools. ; ; Disk Driver ; by Guillermo Ortiz ; 03/25/86 ; ; This driver will allow splitting a 3.5 disk in two pieces of 400K ; each, therefore permitting more than 77 files per disk. It ; is required to "format" the disk with two directories, one at ; block 0 .. 5 and the other at block 800 .. 805, each with a ; length of 800 blocks. Names must be different! ; The ancient admonition: ; ; This is a sample! ; No claims are made regarding the fitness of this code for ; any particular purpose. ROUTINE .EQU 02 ; For indirect jumping RETURN .EQU 04 ; Back to Pascal BUFF .EQU 06 ; Where to put stuff .PROC FAKEDISK ; At this level we could have some code to differentiate ; between different pseudo volumes if we had more than ; two pseudo-volumes per disk. ; In this example we use Unit # 20 for the second part. ; Using units 13 and up let us keep the "standard" drives available ; In any UNIT call X Register contains the type of call ; as follows: CPX #04 BEQ STATUS ; X = 4 CPX #02 BEQ INIT ; X = 2 STA TEMP1 STY TEMP1+1 ; Saving A, Y and X STX TEMP1+2 ; for future use ; We make the assumption that the disk split is the ; System Volume, so we get the logical volume number for ; Unit # 4 from the DISKNUM table; ; see Apple // Pascal Device and Interrupt ; Support Tools manual for details. TSX ; Gimmie the stack pointer LDA 0FEB6 ; Logical volume for boot disk STA 109,X ; so read from that disk ; Our fiddling is complete now let's finish checking ; the call in order to make the jump LDA TEMP1+2 ; X contains the call code BEQ READ ; X = 0 CMP #01 BEQ WRITE ; X = 1 ; Here we could have ; instructions to report some undefined control code. ; This driver will only CRASH!!! BRK ; Bumm!!! ; Now the real stuff READ .EQU * JSR SETUP ; Modify the stack LDY #19. ; Index for Reading from disk BNE GET ; Nice way of jumping WRITE .EQU * JSR SETUP ; Modify the stack LDY #16. ; Index for WRITE to CONSOLE GET LDA @0E2,Y ; $E2 contains a pointer to the jump vector STA ROUTINE ; Set low byte of address INY LDA @0E2,Y ; Get high byte of address STA ROUTINE+1 ; and set it off LDX TEMP1+2 ; Restore LDY TEMP1+1 ; all registers LDA TEMP1 ; before jump JMP @ROUTINE ; and Go! ; INIT will only pass back the no_error IORESULT INIT .EQU * LDX #00 ; No error RTS ; Go back STATUS PLA ; Get STA RETURN ; return PLA ; address STA RETURN+1 PLA ; Get STA BUFF ; Pascal PLA ; Buffer STA BUFF+1 ; address PLA ; Dump control PLA ; word LDY #00 LDA #20 ; Set STA @BUFF,Y ; the number of blocks INY ; to LDA #03 ; 800 STA @BUFF,Y LDX #00 LDA RETURN+1 ; and PHA LDA RETURN PHA RTS ; Return! ; To any request for READ/WRITE we'll add 800 to the ; number of the block needed. SETUP .EQU * LDA 103,X ; Get Block number low CLC ; Set up for addition ADC #20 ; Offset block count by 800 STA 103,X ; and restore LDA 104,X ; Get Block number high ADC #03 ; 800 = $320 STA 104,X ; and restore RTS ; Go back TEMP1 .BLOCK 3 ; Temporary storage area .END The driver requires that the disk be formatted in a special way. Run the following program to create your volume. program REFORMAT; {By Guillermo Ortiz 03/27/86 } {This program takes a newly formatted 3.5 disk and lays down two directories transforming the volume into two 400K pseudo-volumes to be used with the driver FAKEDISK which assigns Unit # 20 to the second part of the disk. } CONST MAXDIR = 77; {Max number of files per volume} VIDLENGTH = 7; {Max chars in volume name} TIDLENGTH = 15; {Max chars per file ID} FBLKSIZE = 512; {Number of bytes per block} DIRBLK = 2; {We are reading the directory} type daterec = packed record month:0..12; {0 --> Meaningless date} day: 0..31; {Day of month} year:0..100 {100 --> dated volume is temp} end; vid = string [vidlength]; {Volume ID} dirrange = 0 .. maxdir; {Number of files on disk} tid = string[tidlength]; {File ID} filekind = (untypedfile,xdskfile,codefile,textfile,infofile, datafile,graffile,fotofile,securdir); {Now the real directory layout} direntry = packed record dfirstblk:integer; {1st physical disk address} dlastblock:integer; {block after last used block} case dfkind:filekind of securdir,untypedfile: {Volume info only in dir[0]} (filler1: 0..2048; {Waste 13 bits} dvid: vid; {Name of volume} deovblk: integer; {Last block in volume} dnumfiles:dirrange; {Number of files in directory} dloadtime:integer; {Time of last access} dlastboot:daterec); {Most recent date setting} xdskfile,codefile,textfile,infofile,datafile, graffile,fotofile: {Regular file info} (filler2: 0..1024; {Waste 12 bits} status: boolean; {For filer wildcards} dtid: tid; {Name of file} dlastbyte:1..fblksize; {Bytes in last block of file} daccess: daterec) {Date of last modification} end; {Of the whole directory record} directory = array [dirrange] of direntry; var dirinfo:directory; {The directory goes here} UNITNUM:INTEGER; CH:CHAR; PROCEDURE DOSTUFF; {Function CHECK will read the directory from a freshly formatted 3.5 disk, then DOSTUFF will make changes so it has only 800 blocks and a name HALFONE: and will write it back to block 2; then we will change the name to HALFTWO: and will write to block 802 as the directory for our second pseudo-volume. } BEGIN with dirinfo[0] do begin deovblk:=800; {Cut it in half} dvid:='HALFONE'; end; unitwrite(UNITNUM,dirinfo,sizeof(dirinfo),dirblk); {Put back main directory} DIRINFO[0].DVID:='HALFTWO'; unitwrite(UNITNUM,dirinfo,sizeof(dirinfo),dirblk+800) {Write second dir.} end; {Of DOSTUFF} FUNCTION CHECK:BOOLEAN; {Reads the directory from the target disk, if possible, warns the user of the certain destruction of the current directory and checks the size of the volume so that the program doesn't use other than 3.5 disks. } BEGIN CHECK:=FALSE; DIRINFO[0].DLASTBLOCK:=-999; {Make sure we read from a disk} UNITREAD(UNITNUM,DIRINFO,SIZEOF(DIRINFO),DIRBLK); IF DIRINFO[0].DLASTBLOCK= 6 THEN {IS THIS A PASCAL DISK?} BEGIN IF DIRINFO[0].DEOVBLK <> 1600 THEN BEGIN WRITELN('SORRY THIS PROGRAM IS INTENDED FOR 3.5 DISKS ONLY'); EXIT(CHECK) END; WRITE('WE ARE ABOUT TO PERMANENTLY DESTROY '); WRITELN(DIRINFO[0].DVID,':'); WRITE('IS IT OK? --> '); REPEAT READ(KEYBOARD,CH) UNTIL CH IN ['Y','N','n','y']; WRITELN(CH); IF CH IN ['Y','y'] THEN CHECK:=TRUE END ELSE BEGIN WRITELN; WRITELN; WRITELN('CAN NOT READ DIRECTORY') END END {OF CHECK}; PROCEDURE GETNUM; {Prompts the user for the Unit Number of the target disk, checks the validity of the input and returns when provided with a reasonable value. } VAR I:INTEGER; BEGIN WRITELN; WRITELN('PLEASE ENTER THE NUMBER OF THE UNIT CONTAINING THE DISK'); WRITE('TO BE REFORMATTED (PRESS TO EXIT) --> '); UNITNUM:=0; REPEAT BEGIN WRITE(CHR(5)); {Cursor ON} READ(CH); {For the prompt} WRITE(CHR(6)); {and then OFF for speed and elegance(?)} IF EOLN THEN IF (UNITNUM IN [4,5,9..12]) THEN EXIT(GETNUM) ELSE FOR I:= 1 TO 32 - UNITNUM DO {Kind of crude but ...} WRITE(CHR(8)); {to go back to the same place} IF ORD(CH) = 27 THEN BEGIN WRITELN; WRITELN('YOU ASKED FOR IT!!!'); WRITE(CHR(5)); {Turn cursor ON before we exit} EXIT(PROGRAM) END; IF (ORD(CH) = 8) AND (UNITNUM > 0) THEN BEGIN IF UNITNUM < 10 THEN UNITNUM:=0 ELSE UNITNUM:=UNITNUM DIV 10; WRITE(CHR(8),' ',CHR(8)) {To delete previous entry} END ELSE BEGIN IF (UNITNUM = 0) AND (CH IN ['1','4','5','9']) THEN UNITNUM:=ORD(CH)-ORD('0') ELSE IF (UNITNUM=1) AND (CH IN ['0','1','2']) THEN UNITNUM:=10*UNITNUM+ORD(CH)-ORD('0') ELSE IF ORD(CH) > 31 THEN WRITE(CHR(8),' ',CHR(8)) {Unwanted stuff,so ...} END {get rid of it. } END UNTIL FALSE; {No Exit here.} WRITELN END {OF GETNUM}; BEGIN {main} WRITELN; WRITELN; WRITELN('WE ARE ABOUT TO REFORMAT A VOLUME SO IT WILL CONTAIN TWO'); WRITELN('400K PSEUDO-VOLUMES. MAKE SURE YOU MARK THE DISK CLEARLY'); WRITELN('SO YOU DON''T FORGET'); WRITELN; WRITELN; REPEAT GETNUM UNTIL CHECK; DOSTUFF; WRITE(CHR(5)); {Don't forget to turn cursor ON} writeln; WRITELN('AWAAAAAY!!!') end. If two volumes are not enough, you can modify this example to support more than two per disk; the key is to keep in mind that when the call comes to the driver, the accumulator contains the number of the Unit the for which the call is intended. After checking this number the driver could decide what offset it has to add to access the correct volume. Of course the formatter program would have to change accordingly, laying down the directories for the new volumes with the appropriate names and sizes. The same scheme can be applied to any device that Pascal can directly recognize (i.e., the Apple Memory Expansion Card, ProFile hard disk, etc.). Further Reference o Apple II Pascal Device and Interrupt Support Tools