!pr3
DOS 3.3 for the UniDisk 3.5 (RWTS 3.5)..............Bill Morgan

We finally got one of Apple's new UniDisk 3.5 drives for the //e, and let me tell you it's very nice.  This small but large addition to our favorite computer is about half the volume of a Disk II, but each disk stores almost six times as much infor- mation.  It's even a bit faster than the 5.25" drives, about 1.3 times the speed.

Of course there's a catch.  In line with Apple's policy of supporting ProDOS only, the new device doesn't use DOS 3.3, at least not as far as Apple is concerned.  There are already several different UniDisk versions of DOS, and we're about to build our own right here.  It's really quite easy.

There are two parts to the problem:  intercepting and handling RWTS calls to the UniDisk slot, and formatting a 3.5" disk with a DOS VTOC and Catalog.

There are a variety of ways to take over a call to RWTS.  When we call RWTS at $3D9 it jumps on to $B7B5, where interrupts are disabled before calling the real RWTS entry at $BD00.  Some programs take control at $B7B7 and others at $BD00.  I looked at the code at $BD00 and saw that it does a little housekeeping and then at $BD10 loads the accumulator with the slot*16 value from the IOB.  That looks like the ideal time to check to see if this call is for my slot, so $BD12 is where I patch in the jump to my code.  If you are using several nonstandard devices with DOS 3.3 (Sider or other hard disk, RAM disk, other drives) you will need to keep track of who's patching into RWTS where.

Now we come to the question of where to put our version of RWTS.  There's certainly no room inside DOS for almost a page of code plus two pages of buffer.  I thought I could probably squeeze the code into page three, but that still left that buffer (not to mention the crowd already living at that popular address!)  It occurred to me to throw INIT away and put the code inside the existing RWTS at $BEAF, but what about the buffer?  I finally decided to use the time-honored technique of moving the DOS buffers and HIMEM down and installing my program and buffer in there.  That's also crowded, but where isn't?  The first working version of RWTS 3.5 ran at $9900, with the buffer at $9B00-9CFF.  The installation routine checked to see if anyone else was using the space and returned an error if so.  Applesoft and the S-C Macro Assembler got along with this arrangement just fine, so I spent some time polishing the program and started to write this article.

That's when I was forcibly reminded that the S-C Word Processor sets its own HIMEM and is firmly convinced that $9900-99FF is the buffer for characters deleted off the screen.  In other words, the first time I tried to save some text to the UniDisk it blew sky high.  I had decided to live without the Word Processor on the UniDisk for the time being when I noticed a couple of interesting things in Beneath Apple DOS.  There is a 342-byte buffer inside RWTS at $BB00-BC55, and the code immediately after that buffer is called only by INIT!  There really are two full pages of available buffer space inside DOS along with room for the code.

So this edition of RWTS 3.5 runs at $BEAF, with its buffer at $BB00-BCFF.  I did hit one more snag when I went to use that buffer area; $BCDF-BCFF is officially unused, which means it's a popular place for other patches.  My system has part of our fast LOAD/BLOAD patch (AAL April 83) there, so I had to shave a few more bytes out of my program to make room to move the LOAD patch up to $BF97-BFB7.  You may have to make some such adjustment, so be sure to check for some other patch at $BCDF.

The UniDisk 3.5 uses a new software interface, called the Protocol Converter.  The PC is a sort of serial bus, which can have several devices daisy-chained to the same controller.  We program the PC with a calling structure very similar to the ProDOS MLI calls.  Here's an example:

!lm+10
CALL  JSR DISPATCH
      .DA #1        read command
      .DA PARMLIST
      BCS ERROR
      ... whatever code

PARMLIST
      .DA #3        3 parameters
      .DA #1        unit number
      .DA BUFFER    buffer address
      .DA <BLOCK    block number (3 bytes)
!lm-10

That's all it takes to read a 512-byte block into our buffer.  Notice that this standard specifies a 3-byte block number:  all current devices use only two bytes of the block number, but they're allowing for expansion beyond 32 megabytes.  The unit number isn't the same as a ProDOS unit; this is the position of the device in the PC chain.  We need to look up the value of DISPATCH in the card.  The byte at $CsFF (s = slot) contains the offset into the ROM of the ProDOS driver entry and the Protocol Converter entry is defined to be 3 bytes after that.  For example, in my UniDisk 3.5 controller in slot 5 the byte at $C5FF is $0A.  That means that the ProDOS entry to the card is $C50A and the PC entry is $C50D.

There's a quick look at the Protocol Converter.  We haven't seen much information published about it yet.  The new //c Technical Reference Manual has a good section, including a ROM listing, but the //e UniDisk 3.5 includes no programmer's documentation.  Bob is planning a more extensive article on its programming for next month's AAL.  Stay tuned...

Apple's new memory expansion card has a PC interface and this RWTS will work with that card as well, but some modification will be needed to use more than one PC at a time.  The installation code could scan all slots looking for PCs and build a table of valid slots and entry addresses.  Then the initial code at MY.RWTS could search that table and plug the appropriate PC.DISPATCH address into the calls.

The Protocol Converter sees the UniDisk as 1600 blocks of 512 bytes each, for a total of 819,200 (800K) bytes of storage.  We have no way to find out about actual tracks and sectors on the disk; this drive seems to use the Macintosh scheme of a variable number of blocks per track.  Therefore, we're going to translate DOS's tracks and sectors into some block number and ask the PC for that block, not worrying about where it actually comes from.

The VTOC on a DOS disk has room for 50 tracks of 32 sectors each.  That adds up to 400K, or exactly half a UniDisk, so we should be able to set things up with 2 logical drives of 400K each.  The number of tracks per disk and the number of sectors per track are both stored as parameters in the VTOC as well, just to make things easier.  Two drives per disk means that we can put drive one in the lower 800 blocks and drive two in the upper 800.  Figuring that 32 sectors per track means 16 blocks per track and two sectors per block gives us this equation:

!lm+10
BLOCK = (DRIVE-1)*800 + TRACK*16 + SECTOR/2
!lm-10

An even-numbered sector is in the lower half of a block, odd in the upper half.

Since each sector is half of one block on the disk, we can't just write one sector.  We have to read a block, copy the new information into half of the buffer, then write that block back out.  This takes extra time, but simplifies some of the control logic because every call does a read first.

That first working version of RWTS 3.5 did a new read for every read call, and a new read and write for every write.  Well that proved to be much too slow, even slower than the old Disk II.  Then I realized that nearly all DOS operations are reading or writing consecutive sectors in a file, so I must be spending a lot of time reading a block that was already in my buffer just to get the sector in the other half of the block.  Sure enough, the performance almost doubled when I started keeping track of which block was in the buffer and skipping re-reads of the same block.  It does seem to be a good idea to make a special case of the VTOC sector and always re-read that one, just in case we change disks after writing the VTOC as the last operation on the old disk.


Line by Line

In the INSTALL routine we first make sure there is a Protocol Converter in the slot this RWTS expects.  If so, we patch in the JMP to our code near the beginning of the normal RWTS and disable INIT by patching an RTS instruction at the beginning of the command handler.  MOVE then puts our routine into place at $BEAF and looks up the PC entry point into the ROMS and installs that address into the instructions that call the interface card.  NO.PC provide an error message if we can't find a PC.  The ID.TABLE has the bytes which mark a PC interface, interspersed with $FFs so we can use the same index for the ROM and the table.

The meat of the program begins at MY.RWTS.  We enter here with slot*$10 in the A register so we can check to see if we need to handle this call.  If not we execute the instructions we overwrote with the JMP and go back to the normal RWTS.  If is is our call, the first thing we do at MINE is check to see if we handled the last RWTS call as well.  If so, all is well, but if normal RWTS was used last then it clobbered the buffer at $BB00.  We therefore trash LAST.BLOCK so the tests down at CHECK.FOR.RE.READ will be forced to read a new block.

SET.BLOCK tranforms the requested track and sector into a block number, in the process setting carry to indicate whether we want the high or low half of the block.  SET.POINTERS then creates two pointers for MY.BUFFER and IOB.BUFFER, using that carry bit along the way.  At SET.DRIVE we check which drive is called for and modify BLOCK to read the other half of the diskette if it says drive 2.  While we're at it, we plug the drive number into the volume number found, so it will appear as the volume number in a CATALOG.  SET.COMMAND gets the command and makes sure it's either READ or WRITE.  Anything else becomes a NOP.

At CHECK.FOR.RE.READ we compare the block number requested with the number of the block in the buffer and if they're different we go on to read the new block.  If we already have the block we need, CHECK.FOR.VTOC double-checks to see if it's a VTOC we're reading.  If so, we need to re-read it anyway, in case it's now a different disk in the drive.  Once all that rigamarole is out of the way, the eight bytes at READ are all it takes to actually read the block!

At SKIP.READ we get the command again.  (I just noticed that we can move the SET.COMMAND code to this point, since doing an extra READ won't hurt anything, even if the command is bad.  That way we can eliminate MY.COMMAND and its STA and LDA instructions.  Furthermore, changing the CMP #2 to an LSR and changing the BEQ to a BCC shaves out another byte, for a total of five fewer bytes.  There's always more space to be found!)  If the command is a READ then READ.MOVE.BUFFER copy MY.BUFFER into the IOB's buffer and we're done.  If it's a WRITE, WRITE.MOVE.BUFFER copies the other way, from the IOB buffer into mine, and then calls the ROM to write out the block.  Then GOOD.EXIT clears carry and loads a return code of zero before branching to the end.  ERROR.EXIT loads up either WRITE PROTECT or DRIVE ERROR and sets carry before returning to the caller.


FORMAT 3.5 ---

Since we threw away INIT to fit all this inside of DOS, and since the standard INIT wouldn't put enough VTOC or CATALOG space on the disk, we're also going to need a special FORMAT program.

There are two stages in the process of formatting a disk:  initializing all the tracks with address information; and writing the VTOC, empty catalog track, and boot program.  Initializing a Protocol Converter device is easy, just call the PC and let it do all the work.  Then we can use our nice new RWTS to write all the rest of the necessary data.  Just be sure that RWTS 3.5 is installed before calling FORMAT 3.5.

Since this catalog track is 31 sectors long there is room for 217 files instead of the normal 105.  Other than the length, the structure is exactly the same as a normal DOS catalog.  The differences in the VTOC are bytes $34-35, the number of tracks per disk and sectors per track, and the bitmap.  The bitmap skips tracks $0 and $11, fills all four bytes per track rather than alternate pairs, and extends all the way to the end of the sector.

The boot program here is just a quick message.  I hope to have a real boot loader ready for next month's AAL.
