!pr2
Little DOS RAM Disk in Language Card.......Bob Sander-Cederlof

For some reason, we have until now avoided this subject.  Many versions of RAM disks have been created and published in various magazines.  The programs always seemed to me to be rather long and involved for what they really had to do.  Recently a friend typed one in from Nibble, prompting me to try my hand.

The so-called "language card" is really the 16K RAM area.  In //e and //c computers it is not a separate card at all, just the top 16K of the motherboard RAM.  It received the monicker of "language card" because it was first sold as a separate card with the Pascal language system.  The RAM in this area is not directly addressable, because the top 16K of Apple's address space is normally allocated to I/O ($C000-CFFF) and ROM ($D000-FFFF).

By flipping a few software-controlled switches the address range from $D000 through $FFFF can be made to point at the 16K RAM instead of ROM.  Furthermore, the addresses from $D000 through $DFFF can be pointed at either of two 4K banks.  If you have an Apple II or II+ with a 16K RAM card you already know this, of course.

Some programs use the language card under DOS, and some do not.  Some which do are Integer BASIC, S-C Macro Assembler, Visicalc, Magicalc, Big Mac, and Merlin.  If you are just using Applesoft to run your own programs, the language card is not used.

If the card is otherwise idle, that RAM could be used to simulate a small disk drive.  My program sets it up as a 64 sector drive, with 60 sectors available for files.  One sector is used for the VTOC, and three sectors are used for the catalog.  You can save up to 21 files into the disk, or one file of up to 60 sectors.

One of the first questions I had to answer was where to put the program.  Naturally, it ended up at $300.  This is almost always my first choice, because it is so easy.  If I find some substantial reasons, I try harder and find some other place in RAM for my programs.  The ramdisk code could be placed inside DOS itself, on top of the RWTS format code.  Another choice might be to use up one page of the language card for the bulk of the code, using only a few lines of code inside RWTS to switch it on and off.  I like this idea, but it does deprive me of one sector out of 60.  Anyway, for now let's just leave it at $300.

Another choice to be made is how to link into DOS.  Many hard disks and other ramdisks do it by placing a JMP or JSR instruction at the beginning of RWTS ($BD00-BD02).  This works very well, but it would be nice to be able to use both our ramdisk and any hard disk also.  Therefore, I figured out a way to chain my ramdisk together with my Sider hard disk.  The method should be compatible with all the ramdisks and hard disks which patch in at $BD00.

The program is broken into two parts.  The first part installs the ramdisk, and the second part performs the reads and writes.  The installer loads and executes at $4000, but of course you could change it to whatever you wish.

I use six page zero locations.  These are all locations which are used by regular RWTS, so it is all right for me to use them.  I don't even need to save the original data and restore it when I am finished.

Lines 1090-1150 copy the read/write part down to $300-3B4.  I actually copy a few extra bytes, but no harm done.  I do have to be careful not to write any bytes above $3CF, because $3D0-3FF is already used by DOS and the monitor.

Lines 1160-1230 save the current contents of $BD00-BD02, and place a JMP to my ramdisk code there.  Any future calls to RWTS will be vectored to my code down in page 3.

Lines 1250 and 1260 may look ridiculous, if you have not tried programming the language card before.  The software-controlled switches ("soft switches") in the Apple are designed so that you have to make two references to address $C083 to turn it on and un-protect it.  Two references to $C08B turn on the card also, but with the other 4K bank at $D000.

Lines 1270-1340 store zeroes in every byte from $D000-D3FF.  In my scheme, those four pages are equivalent to four sectors (track $11, sectors 0-3).  Now that I have mentioned that, why not tell you how I have laid out the whole 16K?

       Bank  Addresses  Trk Sectors
       ----------------------------
       C083  D000-D3FF  $11   0-3
       C083  D400-DFFF  $01   4-F
       C08B  D000-DFFF  $02   0-F
             E000-EFFF  $03   0-F
             F000-FFFF  $04   0-F

Lines 1350-1420 chain the three catalog sectors together.  I have set up track $11 sector 3 as the first catalog sector, sector 2 as the second, and sector 1 as the third and last.  This is the same kind of chain DOS makes on a real disk, but shorter.

Lines 1430-1500, together with the two data lines at 1550 and 1560, fill in the non-zero bytes in the VTOC sector.  This table driven technique takes somewhat fewer bytes than direct code.  I know, because the first time I wrote it the direct way:  LDA, STA, LDA, STA, etc.  The code as it now is plus the tables takes 45 bytes.  The other way it takes 42 bytes just for the STA instructions.  If I use LDA #$xx for each of the different values, that is another 16 bytes.  So, I saved about 13 bytes.  The TBLX line gives the offsets into the $D000 page, and the TBLA line gives the data value which should be stored at each one.  I use a 00 offset to indicate the end of the list.

Line 1580 tells the assembler to start assembling code to be executed at $300, but to keep putting the object code bytes in a continuous stream.  Since we are writing the code on a target file (see line 1030), the whole program is on one file.  RAMDISK.IMAG gets the value $4076, which is what the program counter is BEFORE the .PH directive takes effect.  At line 1590 RAMDISK.REAL gets the value $300.

When a program calls RWTS, it is usually through as JSR $B7B5 instruction.  The code at $B7B5 disables the interrupts and then does a JSR $BD00.  We put our hook at $BD00, so the code jumps to $306, my label LITTLE.RAM.DISK.  Lines 1650 and 1660 are the code which normally is executed at $BD00-BD03.  They store the IOB address.

Lines 1670-1700 pick up the slot number out of the IOB.  This is actually the slot number times 16.  If the caller has specified slot 3, he wants to read or write the ramdisk.  Any other slot, we need to let regular RWTS do the work.  Lines 1710-1750 copy the original contents back to $BD00-BD02.  Then I can call RWTS again, and this time it won't come back until it has done its job.  Lines 1760-1780 restore Y and A as they were before we got involved, and re-call RWTS.  When RWTS is finished, lines 1790-1830 put my hook back into $BD00-BD02.  You might wonder if I should be saving and restoring the Y- and A-registers here.  I originally did, saving them before line 1790 and restoring them before 1840.  Then I realized that the normal contents of Y and A after visiting RWTS are not meaningful.  Only the carry status bit is important, as it signifies whether there was an error or not.

If the caller specified slot 3, he wants to talk to our ramdisk.  Lines 1860-1900 check to make sure he specified drive 1.  If not, we call it an error.  I funneled all of the messages through .99, setting the error byte in the IOB to $40.  This causes DOS to say there was an I/O error.

I used an EOR #1 rather than CMP #1 at line 189~ so that if the drive was correct, we would also have 0 in the A-register.  At some point I need to store 0 into RAMP, and this saves me a LDA #0 instruction.  Then line 1910 can set RAMP to 0.

Lines 1930-1970 pick up the sector number the caller specified, and checks it for proper range.  It must be from 0 to 15 to be valid.  For the time being I save it in a handier location, RAMP+1.

Lines 1980-2020 and 2110-2120 check the track value.  I will accept tracks 1-4 and $11, but no others.  I have to accept $11, because that is where DOS always expects the VTOC to be, and where the catalog almost always is.  The other four tracks could be anything I want, just so they are not $11.  Since I am only using 4 sectors of track 11 for VTOC and catalog, I want the others to be usable for files.  DOS refuses to allocate any sectors to files in track 11 unless we patch some code in the file manager, so I just put the rest of that bank of ram in another track.

Lines 2040-2060 make sure that if the caller wants track $11, his sector number is not bigger than 3.  Lines 2130-2170 make sure that if the caller wants track 1, his sector number is not less than 4.  If the track is either $11 or 1, lines 2070-2090 set us up to use the $C083 bank at $D000, with the sector specifying which page in that bank to use.

If the caller wants track 2, 3, or 4 then lines 2250-2310 set up the $C08B side, and compute the page number according to the table given above.

All this may be academic, because we have yet to look at the opcode.  We are only implementing read and write, so if the opcode is something else we give an error.  Lines 2340-2390 check the opcode, and also set the carry status for read or clear carry for write.

Lines 2400-2420 write enable the ramcard and select the proper $D000 bank.  The value in the X-register is either 0 or 8, so we are either addressing $C083 or $C08B twice.  We don't really need to write enable it unless the opcode was WRITE, but it doesn't hurt anything.

Lines 2430-2460 clear the error byte in the IOB.  I could save two bytes by doing this above, just after line 1910.

Lines 2470-2530 pick up the caller's buffer address and store it in a pointer in page zero.  I don't do any range checking on the buffer address, but then neither does RWTS.

Lines 2540-2550 set Y=0 to start the read or write loop, and then branch to the read loop if carry was set.  Lines 2570-2610 comprise the write loop, and lines 2620-2660 the read loop.

Finally, line 2670 turns the language card back off.  Then we clear carry status to indicate no errors, and return.

And that is how you make a ramdisk.  If you have a bigger RAM card, it probably came with a ramdisk program.  But if not, you ought to be able to see how to extend this program to handle larger amounts of memory.
