INSIGHT: Atari
Bill Wilkinson
Learning How
A month or two ago, I stated that I couldn't possibly teach beginning machine language programming in this column—it would consume my entire output for a year or more. And yet I continue to get letters that ask me "How do you learn to write programs?"
I believe that those who ask the question are not asking for a tutorial on the foibles and pitfalls of the FOR-NEXT loop. Nor are they really asking about the intricacies of the 6502 instruction set. Most of them have already mastered the tutorial-level material on their chosen language. What these perplexed people are really asking is "What good is all this programming stuff, anyway?"
And that is not really surprising. So many tutorials tell you how to write a program to do such and such. So few discuss why. Too often, learning to program is approached like learning a foreign language. Memorize the conjugations and punctuation; put sentences together like this; and if someone asks you "G'dye moya k'neega?" you know what to answer (providing you were studying Russian instead of Spanish).
Computer Conversations
But the need to learn human languages is obvious: The first time you feel hungry in Paris, you can ask for directions to a restaurant in your best Berlitz French. You don't have to "design" a conversation. Not so with learning to program: "Okay, now I know all these neat keywords and syntax and punctuation. How do I start a conversation?" Well, as I hinted above, the secret is that you must design a program.
To some, this design process is simple and obvious. Others never really get the hang of it. (Would it surprise you to learn that many professional programmers never become expert at designing? They make their living implementing other people's designs.) And many, like myself, become somewhat proficient at a few kinds of designs while remaining incompetent at others. (My lament: I don't think I will ever achieve the level of creativity necessary to design a really good game.)
Now, all the above philosophizing surely has some purpose, you hope. Indeed, I think it does.
Kibitzing
I have been promising for a few months now that I would provide patches to allow the Atari 1050 drive to work in enhanced mode with good old Atari DOS 2.0s. Well, I finally gathered enough information to begin the task, and I thought you might enjoy looking over my shoulder while I tackle the problem.
This will be a kind of short diary of what I have gone through. There have been more sidetracks and bugs and flat-out boo-boos than I can find room for here. And I won't even tell you how many assemblies I have made (though I will say I made about 10 or 12 just looking for the best of several possibilities for a series of shift instructions).
Even though I admire and strive for a "clean" design, I am apt to take the course of least resistance if I am confident it will work properly. With that in mind, then, let us begin tackling our task.
Note: I will make frequent reference to the listing of Atari DOS 2.0s as published in the book Inside Atari DOS from COMPUTE! Books. Page numbers and line numbers in square brackets [131: 1350] refer to the book.
It will not be necessary to own the book to understand most of what is going on, but having the book available will make it easier. Also, if you do not understand machine language, neither the book nor my explanations will be easy to follow, but you can still use the results (which will appear next month).
The 1050 And DOS 2.0s
The first thing we must always do is define the task. Here, that is deceptively simple to do: Make the enhanced density mode of the Atari 1050 drive work with Atari DOS 2.0s.
The next step is much harder: Design the implementation of the task. And, actually, this single step consists of many substeps. For example, let's first investigate the facts which I knew when I started.
The drives:
Item: An Atari 810 drive has 40 tracks of 18 sectors of 128 bytes each. That's a total of 720 sectors.
Item: An Atari 1050 drive has 40 tracks of 26 sectors of 128 bytes each, for a total of 1040 sectors.
Item: A 1050 will automatically read either density diskette (single or enhanced), but it formats a new diskette according to the format command it receives. In particular, a ! command ($21) causes single-density formatting, while a" command ($22) causes enhanced density.
The software:
Item: DOS 2 is capable of accessing both 810 drives and their double-density equivalents (drives with 40 tracks of 18 sectors of 256 bytes each).
Item: There is an inherent limit of 1024 sectors in DOS 2, since it allows only a 10-bit sector number in the link field of each sector. Also, on a single density diskette, DOS 2 accesses only 719 of the 720 sectors.
Item: The listing of Atari DOS. Actually, this is not a "known" item, and much of what follows is a discussion of what I learned and applied from reading the listing several times.
Finding The Format
Armed with these knowns, let's tackle the unknowns. It seemed to me that the first point to attack was the disparity between what the 1050 was capable of and what DOS 2 would request of it. All of a sudden, DOS 2 must be able to understand three different kinds of disk formats. Question: How can DOS tell what format a particular diskette is?
The answer is to be found in the DOS listing [66: 2213–2222]. During initialization, a status request is made of each drive. When the drive responds, one of the bytes it returns to the computer describes the drive's type. In particular, the listing makes it clear that a double-density disk has bit 5 ($20) set on. DOS 2 uses this bit to differentiate between 128-byte and 256-byte sectors.
All very well, even assuming that an enhanced mode 1050 returns a zero bit here (which it does, thus properly indicating 128-byte sectors). But what distinguishes an enhanced density diskette? I confess that I obtained the answer to this question through a simple experiment: I simply booted a system with an Indus 1050-compatible drive as D2 and looked at the status value it returned during DOS initialization. Lo and behold, it returned $80. Not surprisingly, the high bit is off in 810 and double-density modes. Voilá.
Sector Limits
The second major question to investigate is "How many of the 1050's sectors can we make DOS 2 utilize?" Well, we already know that 1024 is an upper limit. Is there any other limiting factor? The answer is in the layout of the Volume Table Of Contents (VTOC) under DOS 2. The VTOC contains a single bit for each accessible sector on the disk (a scheme known as a bitmap, though Atari literature often uses VTOC and bitmap interchangeably). If a bit is on (1), the corresponding sector is available. If a bit is off (0), the sector is in use. With eight bits per byte, then, there must be 90 bytes in the bitmap.
DOS 2 allows only a single sector (in this case, 128 bytes) for the VTOC of each diskette. While we could circumvent this restriction, it would require a lot of work, and might cause some secondary problems. (I don't want to go into this subject more now, but it cost me four to six hours of investigation before I decided against a two-sector VTOC).
In 128 bytes, there are 1024 bits. So it would seem that the limit on number of sectors is indeed 1024. Alas, it is not to be. The description of the VTOC clearly calls out usages for the first six bytes (DOS type, maximum number of sectors, current number of sectors, write-required flag) and reserves the next four. So now we are down to 118 bytes and 944 sectors. Is that our limit?
A Final Of 976 Sectors
At first, I was inclined to say it is. But I pored over the listing a couple more times, checked every memory reference that was related, and finally concluded that we could use the four reserved bytes. Which gives us 122 bytes and a final maximum of 976 sectors. Well, that doesn't seem too bad. We are only 64 sectors away from the theoretical maximum and surely a lot better off than with a limit of 720 sectors.
So this is our plan: Use the upper bit ($80) of the drive status to recognize an enhanced density diskette; allow 975 sectors (DOS 2 always throws away the first possible sector); displace the bitmap in the VTOC by 4 bytes on the low end and lengthen it to 122 bytes.
Implementing Our Plan
By the time I had decided on a plan, over half the time I had allotted to this project had elapsed. As I write this, all the allotted time is gone, and I am not done yet. Sounds like a typical software project. Anyway, this month I will tell you of the difficulties I faced. Next month we can decide how well I faced them. In any case, let's begin the next step.
Before I could start the actual coding of the modifications, I had to find all the places in DOS which would be affected by my scheme. While many parts of DOS are affected by a change in density (from 128- to 256-byte sectors), there are only a few routines which actually care about such things as disk status, where the VTOC's bitmap is, and how many sectors are available.
Some of the routines I could successfully ignore. For example, when you delete a file and free up its sectors for later use, you must bump the count of free sectors. But if the rest of DOS is working, you don't have to check for validity of the bumped value. The same thing is true when we allocate a free sector and must decrement the count. And the boot process cares whether we are using 128- or 256-byte sectors, but it doesn't care how many sectors are on the disk.
Some Areas Need Patching
But there are several spots which definitely need attention, so let's discuss them now (next month we discuss the solutions).
- In the BSIO (Basic Sector Input Output) routine, there is a check for a format command [65: 2144]. DOS 2 simply compares the current command with $21 (!) and makes a decision according to an exact match. Now, though, we must allow for either $21 or $22 (") as format commands.
- In DOS initialization [66: 2218], each accessible drive is checked for its status. DOS 2 ignores all bits of the status except bit 5 ($20) and stores a 1 or 2 (single or double density) in the drive table (DRVTBL) for each drive so checked. We need to find a way to capture and use bit 7 ($80), preferably by keeping it in DRVTBL, also. Fortunately, the only other routine which accesses DRVTBL is SETUP, which we discuss below.
- In XFORMAT [79: 3510], the actual format command is stored in the DCB (for use by BSIO, as above). We need to allow for either $22 or $21, while DOS 2 allows only $21.
- Also in XFORMAT [79: 3547, 3552], the maximum number of sectors and number of sectors available are stored in the VTOC which is being created (for the newly formatted disk). Currently, DOS 2 simply uses LDA # (load immediate value) to store what it thinks is the only possible count (707). We must provide for the enhanced density count as well.
- Again in XFORMAT [80: 3559–3570], there are several assumptions made about how big the bitmap is and where the directory and boot sectors are to be represented in the map. Since we will move the base of the map down four bytes, we must provide for variable numbers here, as well.
- In FRESECT [90: 5166], the base of the bitmap is assumed to be byte 10 ($0A) of the VTOC. We must change the assumption.
- In GETSECTOR [91: 5199, 5202, 5239], similar assumptions about the bitmap are coded via immediate loads.
- In SETUP [92: 5288], which is called by every major routine in DOS 2, the type byte stored in DRVTBL (see item 2, above) is simply transferred to a global location (DRVTYP) for use by other routines. If we change what is stored in DRVTBL, we need to change how and what we store in DRVTYP.
Keeping The Patches Small
And that's it. Not too bad, right? If only that were true. Remember, our goal here is to patch the standard version of DOS without affecting its normal operations and without requiring a reassembly of the whole thing to make our patches fit. In general, then, the smaller and fewer the patches, the better.
The real problem here is the number of load immediate instructions, used to implement what are now to become invalid assumptions. If these were three-byte instructions (such as loads from a non-zero page memory location), we would have a simple task: Change the values in the locations being loaded.
Since they are load immediate instructions, though, our only choices are to either make large and cumbersome patches (generally JSRs to subroutines which will do the work, but remember that JSR occupies three bytes), use loads from zero page (a neat alternative, but we have no zero page available to us), or to continue to use load immediate.
Self-Modifying Routines
My choice? Continue to use load immediate. But how? By producing some (shudder at this next phrase, please) self-modifying routines. Remember how I said at the beginning that I sometimes took the path of least resistance? This is one of those sometimes.
The "trick" which allows my scheme to work is relatively simple: Every routine which needs a load immediate changed is only used by DOS 2 after a call has been made to SETUP. Basically, SETUP examines the disk number and drive type and produces various pointers and values in fixed locations for use by other, higher-level routines. What would be more appropriate than for SETUP to also set up the needed values which will be loaded in immediate mode?
And this is, indeed, the plan I tried. At the point where SETUP stores the drive type [92: 5288], I placed a JSR to my patch-it routine. And my patch-it routine used the disk type information to determine which of a pair of immediate values would be used in each of the cases noted above. It looked like it would work.
Fitting The Patch Into DOS.SYS
Except (You knew that was coming, didn't you?) where do I put the patch? I have discussed this subject before, so let me succinctly say that the only sizable patch area in DOS.SYS is at location $1501, in the gap between DOS.SYS and Mini-DUP (the root of DUP.SYS). There are exactly 63 bytes available there. And my routine was about 85 bytes long.
The story of how I pared my patch down to fit (just barely) will have to wait for next month. Fortunately, it is a short patch. Also fortunately, there are a couple of small patch spaces still floating around in DOS.
Incidentally, if you were looking for the continuation of my notes on how to load saved binary files, keep looking. It turns out that the subject has direct bearing on what we are doing here, so it seemed not inappropriate to postpone it a month (or possibly two).